-
Notifications
You must be signed in to change notification settings - Fork 428
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add maxResultBuffer property #1431
Changes from 10 commits
af25295
363ed65
ca8223a
e417451
33f1791
e9db643
460a829
52f0696
290f031
306f5ad
5329a2b
9f27069
3a86682
0efd5dc
b4ff616
989d362
8f2336d
2e5a03e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
/* | ||
* Microsoft JDBC Driver for SQL Server Copyright(c) Microsoft Corporation All rights reserved. This program is made | ||
* available under the terms of the MIT License. See the LICENSE file in the project root for more information. | ||
*/ | ||
|
||
package com.microsoft.sqlserver.jdbc; | ||
|
||
/** | ||
* Interface for MaxResultBufferCounter | ||
*/ | ||
public interface ICounter { | ||
|
||
/** | ||
* Increases the state of Counter | ||
* | ||
* @param bytes | ||
* Number of bytes to increase state | ||
* @throws SQLServerException | ||
* Exception is thrown, when limit of Counter is exceeded | ||
*/ | ||
void increaseCounter(long bytes) throws SQLServerException; | ||
|
||
/** | ||
* Resets the state of Counter | ||
*/ | ||
void resetCounter(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -51,6 +51,7 @@ | |
import java.util.Locale; | ||
import java.util.Map; | ||
import java.util.Map.Entry; | ||
import java.util.Properties; | ||
import java.util.Set; | ||
import java.util.SimpleTimeZone; | ||
import java.util.TimeZone; | ||
|
@@ -3155,6 +3156,15 @@ boolean isEOMSent() { | |
traceID = "TDSWriter@" + Integer.toHexString(hashCode()) + " (" + con.toString() + ")"; | ||
} | ||
|
||
/** | ||
* Checks If tdsMessageType is RPC or QUERY | ||
* | ||
* @return boolean | ||
*/ | ||
boolean checkIfTdsMessageTypeIsBatchOrRPC() { | ||
return tdsMessageType == TDS.PKT_QUERY || tdsMessageType == TDS.PKT_RPC; | ||
} | ||
|
||
// TDS message start/end operations | ||
|
||
void preparePacket() throws SQLServerException { | ||
|
@@ -6545,6 +6555,11 @@ private boolean nextPacket() throws SQLServerException { | |
// This action must be synchronized against against another thread calling | ||
// readAllPackets() to read in ALL of the remaining packets of the current response. | ||
if (null == consumedPacket.next) { | ||
// if the read comes from getNext() and responseBuffering is Adaptive (in this place is), then reset Counter | ||
// State | ||
if (command.getTDSWriter().checkIfTdsMessageTypeIsBatchOrRPC()) { | ||
command.getCounter().resetCounter(); | ||
} | ||
readPacket(); | ||
|
||
if (null == consumedPacket.next) | ||
|
@@ -6640,6 +6655,11 @@ synchronized final boolean readPacket() throws SQLServerException { | |
System.arraycopy(newPacket.header, 0, logBuffer, 0, TDS.PACKET_HEADER_SIZE); | ||
} | ||
|
||
// if messageType is RPC or QUERY, then increment Counter's state | ||
if (tdsChannel.getWriter().checkIfTdsMessageTypeIsBatchOrRPC()) { | ||
command.getCounter().increaseCounter(packetLength); | ||
} | ||
|
||
// Now for the payload... | ||
for (int payloadBytesRead = 0; payloadBytesRead < newPacket.payloadLength;) { | ||
int bytesRead = tdsChannel.read(newPacket.payload, payloadBytesRead, | ||
|
@@ -7344,6 +7364,23 @@ final boolean readingResponse() { | |
|
||
protected ArrayList<byte[]> enclaveCEKs; | ||
|
||
//Counter reference, so maxResultBuffer property can by acknowledged | ||
private ICounter counter; | ||
|
||
public ICounter getCounter() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Package-private please. |
||
return counter; | ||
} | ||
|
||
public void createCounter(ICounter previousCounter, Properties activeConnectionProperties) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. package-private |
||
if (null == previousCounter) { | ||
String maxResultBuffer = activeConnectionProperties | ||
.getProperty(SQLServerDriverStringProperty.MAX_RESULT_BUFFER.toString()); | ||
counter = new MaxResultBufferCounter(Long.parseLong(maxResultBuffer)); | ||
} else { | ||
counter = previousCounter; | ||
} | ||
} | ||
|
||
/** | ||
* Creates this command with an optional timeout. | ||
* | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
/* | ||
* Microsoft JDBC Driver for SQL Server Copyright(c) Microsoft Corporation All rights reserved. This program is made | ||
* available under the terms of the MIT License. See the LICENSE file in the project root for more information. | ||
*/ | ||
|
||
package com.microsoft.sqlserver.jdbc; | ||
|
||
import java.text.MessageFormat; | ||
import java.util.logging.Level; | ||
import java.util.logging.Logger; | ||
|
||
|
||
/** | ||
* Implementation of ICounter for 'maxResultBuffer' property. | ||
*/ | ||
public class MaxResultBufferCounter implements ICounter { | ||
|
||
private final Logger logger = Logger.getLogger("com.microsoft.sqlserver.jdbc.MaxResultBufferCounter"); | ||
|
||
private long counter = 0; | ||
private final long maxResultBuffer; | ||
|
||
public MaxResultBufferCounter(long maxResultBuffer) { | ||
this.maxResultBuffer = maxResultBuffer; | ||
} | ||
|
||
public void increaseCounter(long bytes) throws SQLServerException { | ||
if (maxResultBuffer > 0) { | ||
counter += bytes; | ||
checkForMaxResultBufferOverflow(counter); | ||
} | ||
} | ||
|
||
public void resetCounter() { | ||
counter = 0; | ||
} | ||
|
||
private void checkForMaxResultBufferOverflow(long number) throws SQLServerException { | ||
if (number > maxResultBuffer) { | ||
logger.log(Level.WARNING, "MaxResultBuffer exceeded: {0}. Property was set to {1}.", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please change to :
|
||
new Object[] {number, maxResultBuffer}); | ||
throwExceededMaxResultBufferException(counter, maxResultBuffer); | ||
} | ||
} | ||
|
||
private void throwExceededMaxResultBufferException(Object... arguments) throws SQLServerException { | ||
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_maxResultBufferPropertyDescription")); | ||
throw new SQLServerException(form.format(arguments), null); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
/* | ||
* Microsoft JDBC Driver for SQL Server Copyright(c) Microsoft Corporation All rights reserved. This program is made | ||
* available under the terms of the MIT License. See the LICENSE file in the project root for more information. | ||
*/ | ||
|
||
package com.microsoft.sqlserver.jdbc; | ||
|
||
import java.lang.management.ManagementFactory; | ||
import java.text.MessageFormat; | ||
import java.util.logging.Level; | ||
import java.util.logging.Logger; | ||
|
||
|
||
/** | ||
* Parser created to parse String value from Connection String to equivalent number of bytes for JDBC Driver to work on. | ||
*/ | ||
public class MaxResultBufferParser { | ||
|
||
private static final Logger logger = Logger.getLogger("com.microsoft.sqlserver.jdbc.MaxResultBufferParser"); | ||
private static final String[] PERCENT_PHRASES = {"percent", "pct", "p"}; | ||
private static final String ERROR_MESSAGE = "maxResultBuffer property is badly formatted: {0}"; | ||
|
||
private MaxResultBufferParser() {} | ||
|
||
/** | ||
* | ||
* Returns number of bytes for maxResultBuffer property | ||
* | ||
* @param input | ||
* String value for maxResultProperty provided in Connection String | ||
* @return 'maxResultBuffer' property as number of bytes | ||
* @throws SQLServerException | ||
* Is Thrown when maxResultProperty's syntax is wrong | ||
*/ | ||
public static long validateMaxResultBuffer(String input) throws SQLServerException { | ||
String numberString; | ||
long number = -1; | ||
|
||
// check for null values and empty String "", if so return -1 | ||
if (StringUtils.isEmpty(input)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Temporary solution would be to change this line to if (StringUtils.isEmpty(input) || "-1".equalsIgnoreCase(input)) { But we should ask whether it makes sense to allow users to even input something like -1 at all. -1 is also used as a default value by XAConnection at times. |
||
return number; | ||
} | ||
// check PERCENT_PHRASES | ||
for (String percentPhrase : PERCENT_PHRASES) { | ||
if (input.endsWith(percentPhrase)) { | ||
numberString = input.substring(0, input.length() - percentPhrase.length()); | ||
try { | ||
number = Long.parseLong(numberString); | ||
} catch (NumberFormatException e) { | ||
logger.log(Level.INFO, ERROR_MESSAGE, new Object[] {input}); | ||
throwNewInvalidMaxResultBufferParameterException(e, numberString); | ||
} | ||
return adjustMemory(number); | ||
} | ||
} | ||
// check if only number was supplied | ||
long multiplier = 1; | ||
if (StringUtils.isNumeric(input)) { | ||
number = Long.parseLong(input); | ||
return adjustMemory(number, multiplier); | ||
} | ||
// check if prefix was supplied | ||
multiplier = getMultiplier(input); | ||
|
||
numberString = input.substring(0, input.length() - 1); | ||
|
||
try { | ||
number = Long.parseLong(numberString); | ||
} catch (NumberFormatException e) { | ||
logger.log(Level.INFO, ERROR_MESSAGE, new Object[] {input}); | ||
throwNewInvalidMaxResultBufferParameterException(e, numberString); | ||
} | ||
return adjustMemory(number, multiplier); | ||
} | ||
|
||
private static long getMultiplier(String input) throws SQLServerException { | ||
long multiplier = 1; | ||
switch (Character.toUpperCase(input.charAt(input.length() - 1))) { | ||
case 'K': | ||
multiplier = 1_000L; | ||
break; | ||
case 'M': | ||
multiplier = 1_000_000L; | ||
break; | ||
case 'G': | ||
multiplier = 1_000_000_000L; | ||
break; | ||
case 'T': | ||
multiplier = 1_000_000_000_000L; | ||
break; | ||
default: | ||
logger.log(Level.INFO, ERROR_MESSAGE, new Object[] {input}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
throwNewInvalidMaxResultBufferParameterException(null, input); | ||
} | ||
return multiplier; | ||
} | ||
|
||
private static long adjustMemory(long percentage) { | ||
if (percentage > 90) | ||
return (long) (0.9 * getMaxMemory()); | ||
else | ||
return (long) ((percentage) / 100.0 * getMaxMemory()); | ||
} | ||
|
||
private static long adjustMemory(long size, long multiplier) { | ||
if (size * multiplier > 0.9 * getMaxMemory()) | ||
return (long) (0.9 * getMaxMemory()); | ||
else | ||
return size * multiplier; | ||
} | ||
|
||
private static long getMaxMemory() { | ||
return ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getMax(); | ||
} | ||
|
||
private static void throwNewInvalidMaxResultBufferParameterException(Throwable cause, | ||
Object... arguments) throws SQLServerException { | ||
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_maxResultBufferInvalidSyntax")); | ||
Object[] msgArgs = {arguments}; | ||
throw new SQLServerException(form.format(msgArgs), cause); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -649,5 +649,8 @@ protected Object[][] getContents() { | |
{"R_unassignableError", "The class specified by the {0} property must be assignable to {1}."}, | ||
{"R_InvalidCSVQuotes", | ||
"Failed to parse the CSV file, verify that the fields are correctly enclosed in double quotes."}, | ||
{"R_TokenRequireUrl", "Token credentials require a URL using the HTTPS protocol scheme."},}; | ||
}; | ||
{"R_TokenRequireUrl", "Token credentials require a URL using the HTTPS protocol scheme."}, | ||
{"R_maxResultBufferPropertyDescription", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
"MaxResultBuffer property exceeded: {0}. MaxResultBuffer was set to: {1}."}, | ||
{"R_maxResultBufferInvalidSyntax", "Invalid syntax: {0} in maxResultBuffer parameter"},}; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please make this interface package-private.