From 9cecaa8768dc548e58ad06b8263eed4496bb079b Mon Sep 17 00:00:00 2001 From: jono-coder <114485052+jono-coder@users.noreply.github.com> Date: Wed, 5 Oct 2022 13:57:47 +0100 Subject: [PATCH 1/6] removed unnecessary "synchronized" -- an AtomicLong is already thread-safe --- .../com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java index f867186b9..cecf363a9 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerEnclaveProvider.java @@ -503,7 +503,7 @@ byte[] getSessionSecret() { return sessionSecret; } - synchronized long getCounter() { + long getCounter() { return counter.getAndIncrement(); } } From 671050a85299489b0484da125c23cebce85af9a5 Mon Sep 17 00:00:00 2001 From: jono-coder <114485052+jono-coder@users.noreply.github.com> Date: Wed, 5 Oct 2022 14:39:58 +0100 Subject: [PATCH 2/6] replace synchronized with ReentrantLock --- .../sqlserver/jdbc/FailOverInfo.java | 51 +- .../sqlserver/jdbc/FailOverMapSingleton.java | 13 +- .../microsoft/sqlserver/jdbc/IOBuffer.java | 521 +++++++++------- .../PersistentTokenCacheAccessAspect.java | 29 +- .../sqlserver/jdbc/SQLServerConnection.java | 570 +++++++++++------- .../sqlserver/jdbc/SQLServerDataTable.java | 84 ++- .../jdbc/SQLServerDatabaseMetaData.java | 67 +- .../jdbc/SQLServerPooledConnection.java | 52 +- .../sqlserver/jdbc/SQLServerStatement.java | 166 ++--- .../jdbc/SQLServerSymmetricKeyCache.java | 15 +- .../sqlserver/jdbc/SQLServerXAConnection.java | 42 +- .../sqlserver/jdbc/SQLServerXAResource.java | 165 ++--- .../microsoft/sqlserver/jdbc/SharedTimer.java | 15 +- .../com/microsoft/sqlserver/jdbc/Util.java | 44 +- 14 files changed, 1079 insertions(+), 755 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/FailOverInfo.java b/src/main/java/com/microsoft/sqlserver/jdbc/FailOverInfo.java index d46d5bb3c..e0c769c38 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/FailOverInfo.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/FailOverInfo.java @@ -6,6 +6,8 @@ package com.microsoft.sqlserver.jdbc; import java.text.MessageFormat; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; @@ -19,6 +21,7 @@ final class FailoverInfo { private int portNumber; private String failoverInstance; private boolean setUpInfocalled; + private final Lock lock = new ReentrantLock(); // This member is exposed outside for reading, we need to know in advance if the // failover partner is the currently active server before making a DNS resolution and a connect attempt. @@ -80,29 +83,37 @@ private void setupInfo(SQLServerConnection con) throws SQLServerException { setUpInfocalled = true; } - synchronized ServerPortPlaceHolder failoverPermissionCheck(SQLServerConnection con, - boolean link) throws SQLServerException { - setupInfo(con); - return new ServerPortPlaceHolder(failoverPartner, portNumber, failoverInstance, link); + ServerPortPlaceHolder failoverPermissionCheck(SQLServerConnection con, boolean link) throws SQLServerException { + lock.lock(); + try { + setupInfo(con); + return new ServerPortPlaceHolder(failoverPartner, portNumber, failoverInstance, link); + } finally { + lock.unlock(); + } } // Add/replace the failover server, - synchronized void failoverAdd(SQLServerConnection connection, boolean actualUseFailoverPartner, - String actualFailoverPartner) throws SQLServerException { - if (useFailoverPartner != actualUseFailoverPartner) { - if (connection.getConnectionLogger().isLoggable(Level.FINE)) - connection.getConnectionLogger() - .fine(connection.toString() + " Failover detected. failover partner=" + actualFailoverPartner); - useFailoverPartner = actualUseFailoverPartner; - } - // The checking for actualUseFailoverPartner may look weird but this is required - // We only change the failoverpartner info when we connect to the primary - // if we connect to the secondary and it sends a failover partner - // we wont store that information. - if (!actualUseFailoverPartner && !failoverPartner.equals(actualFailoverPartner)) { - failoverPartner = actualFailoverPartner; - // new FO partner need to setup again. - setUpInfocalled = false; + void failoverAdd(SQLServerConnection connection, boolean actualUseFailoverPartner, String actualFailoverPartner) { + lock.lock(); + try { + if (useFailoverPartner != actualUseFailoverPartner) { + if (connection.getConnectionLogger().isLoggable(Level.FINE)) + connection.getConnectionLogger() + .fine(connection.toString() + " Failover detected. failover partner=" + actualFailoverPartner); + useFailoverPartner = actualUseFailoverPartner; + } + // The checking for actualUseFailoverPartner may look weird but this is required + // We only change the failoverpartner info when we connect to the primary + // if we connect to the secondary and it sends a failover partner + // we wont store that information. + if (!actualUseFailoverPartner && !failoverPartner.equals(actualFailoverPartner)) { + failoverPartner = actualFailoverPartner; + // new FO partner need to setup again. + setUpInfocalled = false; + } + } finally { + lock.unlock(); } } } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/FailOverMapSingleton.java b/src/main/java/com/microsoft/sqlserver/jdbc/FailOverMapSingleton.java index 734405f5f..76fb5f5da 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/FailOverMapSingleton.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/FailOverMapSingleton.java @@ -6,12 +6,15 @@ package com.microsoft.sqlserver.jdbc; import java.util.HashMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; final class FailoverMapSingleton { private static int initialHashmapSize = 5; private static HashMap failoverMap = new HashMap<>(initialHashmapSize); + private static final Lock LOCK = new ReentrantLock(); private FailoverMapSingleton() { /* hide the constructor to stop the instantiation of this class. */} @@ -30,7 +33,8 @@ private static String concatPrimaryDatabase(String primary, String instance, Str static FailoverInfo getFailoverInfo(SQLServerConnection connection, String primaryServer, String instance, String database) { - synchronized (FailoverMapSingleton.class) { + LOCK.lock(); + try { if (failoverMap.isEmpty()) { return null; } else { @@ -43,6 +47,8 @@ static FailoverInfo getFailoverInfo(SQLServerConnection connection, String prima fo.log(connection); return fo; } + } finally { + LOCK.unlock(); } } @@ -54,7 +60,8 @@ static void putFailoverInfo(SQLServerConnection connection, String primaryServer String failoverPartner) throws SQLServerException { FailoverInfo fo; - synchronized (FailoverMapSingleton.class) { + LOCK.lock(); + try { // one more check to make sure someone already did not do this if (null == (fo = getFailoverInfo(connection, primaryServer, instance, database))) { if (connection.getConnectionLogger().isLoggable(Level.FINE)) @@ -64,6 +71,8 @@ static void putFailoverInfo(SQLServerConnection connection, String primaryServer } else // if the class exists make sure the latest info is updated fo.failoverAdd(connection, actualuseFailover, failoverPartner); + } finally { + LOCK.unlock(); } } } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java index fce652e4d..bdc29df64 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java @@ -5,28 +5,16 @@ package com.microsoft.sqlserver.jdbc; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.Reader; -import java.io.Serializable; -import java.io.UnsupportedEncodingException; +import com.microsoft.sqlserver.jdbc.SQLServerConnection.FedAuthTokenCommand; +import com.microsoft.sqlserver.jdbc.dataclassification.SensitivityClassification; + +import javax.net.SocketFactory; +import javax.net.ssl.*; +import java.io.*; import java.math.BigDecimal; import java.math.BigInteger; import java.math.RoundingMode; -import java.net.Inet4Address; -import java.net.Inet6Address; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.net.SocketAddress; -import java.net.SocketException; -import java.net.SocketOption; -import java.net.SocketTimeoutException; +import java.net.*; import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -42,43 +30,22 @@ import java.time.LocalDate; import java.time.OffsetDateTime; import java.time.OffsetTime; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Calendar; -import java.util.GregorianCalendar; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.Locale; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; -import java.util.Properties; -import java.util.Set; -import java.util.SimpleTimeZone; -import java.util.TimeZone; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; import java.util.logging.Logger; -import javax.net.SocketFactory; -import javax.net.ssl.KeyManager; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLParameters; -import javax.net.ssl.SSLSocket; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509TrustManager; - -import com.microsoft.sqlserver.jdbc.SQLServerConnection.FedAuthTokenCommand; -import com.microsoft.sqlserver.jdbc.dataclassification.SensitivityClassification; - /** * ExtendedSocketOptions provides methods to keep track of keep alive and socket information. - * */ final class ExtendedSocketOptions { private static class ExtSocketOption implements SocketOption { @@ -716,7 +683,9 @@ final TDSReader getReader(TDSCommand command) { // the SSL socket above. They wrap the underlying TCP streams. // For unencrypted connections, they are just the TCP streams themselves. private ProxyInputStream inputStream; + private final Lock inputStreamLock = new ReentrantLock(); private OutputStream outputStream; + private final Lock outputStreamLock = new ReentrantLock(); /** TDS packet payload logger */ private static Logger packetLogger = Logger.getLogger("com.microsoft.sqlserver.jdbc.internals.TDS.DATA"); @@ -730,6 +699,8 @@ final boolean isLoggingPackets() { int numMsgsSent = 0; int numMsgsRcvd = 0; + private final Lock lock = new ReentrantLock(); + // Last SPID received from the server. Used for logging and to tag subsequent outgoing // packets to facilitate diagnosing problems from the server side. private int spid = 0; @@ -797,73 +768,79 @@ final InetSocketAddress open(String host, int port, int timeoutMillis, boolean u /** * Disables SSL on this TDS channel. */ - synchronized void disableSSL() { - if (logger.isLoggable(Level.FINER)) + void disableSSL() { + if (logger.isLoggable(Level.FINER)) { logger.finer(toString() + " Disabling SSL..."); - - // Guard in case of disableSSL being called before enableSSL - if (proxySocket == null) { - if (logger.isLoggable(Level.INFO)) - logger.finer(toString() + " proxySocket is null, exit early"); - return; } - /* - * The mission: To close the SSLSocket and release everything that it is holding onto other than the TCP/IP - * socket and streams. The challenge: Simply closing the SSLSocket tries to do additional, unnecessary shutdown - * I/O over the TCP/IP streams that are bound to the socket proxy, resulting in a not responding and confusing - * SQL Server. Solution: Rewire the ProxySocket's input and output streams (one more time) to closed streams. - * SSLSocket sees that the streams are already closed and does not attempt to do any further I/O on them before - * closing itself. - */ - - // Create a couple of cheap closed streams - InputStream is = new ByteArrayInputStream(new byte[0]); + lock.lock(); try { - is.close(); - } catch (IOException e) { - // No reason to expect a brand new ByteArrayInputStream not to close, - // but just in case... - logger.fine("Ignored error closing InputStream: " + e.getMessage()); - } + // Guard in case of disableSSL being called before enableSSL + if (proxySocket == null) { + if (logger.isLoggable(Level.INFO)) + logger.finer(toString() + " proxySocket is null, exit early"); + return; + } - OutputStream os = new ByteArrayOutputStream(); - try { - os.close(); - } catch (IOException e) { - // No reason to expect a brand new ByteArrayOutputStream not to close, - // but just in case... - logger.fine("Ignored error closing OutputStream: " + e.getMessage()); - } + /* + * The mission: To close the SSLSocket and release everything that it is holding onto other than the TCP/IP + * socket and streams. The challenge: Simply closing the SSLSocket tries to do additional, unnecessary shutdown + * I/O over the TCP/IP streams that are bound to the socket proxy, resulting in a not responding and confusing + * SQL Server. Solution: Rewire the ProxySocket's input and output streams (one more time) to closed streams. + * SSLSocket sees that the streams are already closed and does not attempt to do any further I/O on them before + * closing itself. + */ - // Rewire the proxy socket to the closed streams - if (logger.isLoggable(Level.FINEST)) - logger.finest(toString() + " Rewiring proxy streams for SSL socket close"); - proxySocket.setStreams(is, os); + // Create a couple of cheap closed streams + InputStream is = new ByteArrayInputStream(new byte[0]); + try { + is.close(); + } catch (IOException e) { + // No reason to expect a brand new ByteArrayInputStream not to close, + // but just in case... + logger.fine("Ignored error closing InputStream: " + e.getMessage()); + } - // Now close the SSL socket. It will see that the proxy socket's streams - // are closed and not try to do any further I/O over them. - try { - if (logger.isLoggable(Level.FINER)) - logger.finer(toString() + " Closing SSL socket"); + OutputStream os = new ByteArrayOutputStream(); + try { + os.close(); + } catch (IOException e) { + // No reason to expect a brand new ByteArrayOutputStream not to close, + // but just in case... + logger.fine("Ignored error closing OutputStream: " + e.getMessage()); + } - sslSocket.close(); - } catch (IOException e) { - // Don't care if we can't close the SSL socket. We're done with it anyway. - logger.fine("Ignored error closing SSLSocket: " + e.getMessage()); - } + // Rewire the proxy socket to the closed streams + if (logger.isLoggable(Level.FINEST)) + logger.finest(toString() + " Rewiring proxy streams for SSL socket close"); + proxySocket.setStreams(is, os); + + // Now close the SSL socket. It will see that the proxy socket's streams + // are closed and not try to do any further I/O over them. + try { + if (logger.isLoggable(Level.FINER)) + logger.finer(toString() + " Closing SSL socket"); - // Do not close the proxy socket. Doing so would close our TCP socket - // to which the proxy socket is bound. Instead, just null out the reference - // to free up the few resources it holds onto. - proxySocket = null; + sslSocket.close(); + } catch (IOException e) { + // Don't care if we can't close the SSL socket. We're done with it anyway. + logger.fine("Ignored error closing SSLSocket: " + e.getMessage()); + } - // Finally, with all of the SSL support out of the way, put the TDSChannel - // back to using the TCP/IP socket and streams directly. - inputStream = tcpInputStream; - outputStream = tcpOutputStream; - channelSocket = tcpSocket; - sslSocket = null; + // Do not close the proxy socket. Doing so would close our TCP socket + // to which the proxy socket is bound. Instead, just null out the reference + // to free up the few resources it holds onto. + proxySocket = null; + + // Finally, with all of the SSL support out of the way, put the TDSChannel + // back to using the TCP/IP socket and streams directly. + inputStream = tcpInputStream; + outputStream = tcpOutputStream; + channelSocket = tcpSocket; + sslSocket = null; + } finally { + lock.unlock(); + } if (logger.isLoggable(Level.FINER)) logger.finer(toString() + " SSL disabled"); @@ -1096,8 +1073,9 @@ final void setFilteredStream(InputStream is) { * @throws IOException * If an I/O exception occurs. */ - public synchronized boolean poll() { - synchronized (this) { + public boolean poll() { + lock.lock(); + try { int b; try { b = filteredStream.read(); @@ -1130,6 +1108,8 @@ public synchronized boolean poll() { cachedLength++; return true; + } finally { + lock.unlock(); } } @@ -1144,7 +1124,8 @@ private int getOneFromCache() { } public long skip(long n) throws IOException { - synchronized (this) { + lock.lock(); + try { long bytesSkipped = 0; if (logger.isLoggable(Level.FINEST)) @@ -1163,6 +1144,8 @@ public long skip(long n) throws IOException { logger.finest(toString() + " Skipped " + n + " bytes"); return bytesSkipped; + } finally { + lock.unlock(); } } @@ -1195,7 +1178,8 @@ public int read(byte[] b, int offset, int maxBytes) throws IOException { } private int readInternal(byte[] b, int offset, int maxBytes) throws IOException { - synchronized (this) { + lock.lock(); + try { int bytesRead; if (logger.isLoggable(Level.FINEST)) @@ -1242,6 +1226,8 @@ private int readInternal(byte[] b, int offset, int maxBytes) throws IOException logger.finest(toString() + " Read " + bytesRead + " bytes"); return bytesRead; + } finally { + lock.unlock(); } } @@ -1255,20 +1241,27 @@ public boolean markSupported() { } public void mark(int readLimit) { - synchronized (this) { - if (logger.isLoggable(Level.FINEST)) - logger.finest(toString() + " Marking next " + readLimit + " bytes"); + if (logger.isLoggable(Level.FINEST)) + logger.finest(toString() + " Marking next " + readLimit + " bytes"); + lock.lock(); + try { filteredStream.mark(readLimit); + } finally { + lock.unlock(); } } public void reset() throws IOException { - synchronized (this) { - if (logger.isLoggable(Level.FINEST)) - logger.finest(toString() + " Resetting to previous mark"); + if (logger.isLoggable(Level.FINEST)) + logger.finest(toString() + " Resetting to previous mark"); + + lock.lock(); + try { filteredStream.reset(); + } finally { + lock.unlock(); } } @@ -2050,8 +2043,10 @@ else if (null != (trustStoreFileName = System.getProperty("javax.net.ssl.trustSt */ final Boolean networkSocketStillConnected() { int origSoTimeout; - synchronized (inputStream) { - synchronized (outputStream) { + inputStreamLock.lock(); + try { + outputStreamLock.lock(); + try { if (logger.isLoggable(Level.FINEST)) { logger.finest(toString() + "(networkSocketStillConnected) Checking for socket disconnect."); } @@ -2089,15 +2084,22 @@ final Boolean networkSocketStillConnected() { } return false; } + } finally { + outputStreamLock.unlock(); } + } finally { + inputStreamLock.unlock(); } } final int read(byte[] data, int offset, int length) throws SQLServerException { try { - synchronized (inputStream) { + inputStreamLock.lock(); + try { con.idleNetworkTracker.markNetworkActivity(); return inputStream.read(data, offset, length); + } finally { + inputStreamLock.unlock(); } } catch (IOException e) { if (logger.isLoggable(Level.FINE)) @@ -2115,9 +2117,12 @@ final int read(byte[] data, int offset, int length) throws SQLServerException { final void write(byte[] data, int offset, int length) throws SQLServerException { try { - synchronized (outputStream) { + outputStreamLock.lock(); + try { con.idleNetworkTracker.markNetworkActivity(); outputStream.write(data, offset, length); + } finally { + outputStreamLock.unlock(); } } catch (IOException e) { if (logger.isLoggable(Level.FINER)) @@ -2333,11 +2338,12 @@ enum Result { // lock used for synchronization while updating // data within a socketFinder object - private final Object socketFinderlock = new Object(); + private final Lock socketFinderlock = new ReentrantLock(); // lock on which the parent thread would wait // after spawning threads. - private final Object parentThreadLock = new Object(); + private final Lock parentThreadLock = new ReentrantLock(); + private final Condition parentCondition = parentThreadLock.newCondition(); // indicates whether the socketFinder has succeeded or failed // in finding a socket or is still trying to find a socket @@ -2371,6 +2377,7 @@ enum Result { // list of addresses for ip selection by type preference private static ArrayList addressList = new ArrayList<>(); + private static final Lock ADDRESS_LIST_LOCK = new ReentrantLock(); /** * Constructs a new SocketFinder object with appropriate traceId @@ -2473,13 +2480,16 @@ Socket findSocket(String hostName, int portNumber, int timeoutInMilliSeconds, bo // for both IPv4 and IPv6. // Using double-checked locking for performance reasons. if (result.equals(Result.UNKNOWN)) { - synchronized (socketFinderlock) { + socketFinderlock.lock(); + try { if (result.equals(Result.UNKNOWN)) { result = Result.FAILURE; if (logger.isLoggable(Level.FINER)) { logger.finer(this.toString() + " The parent thread updated the result to failure"); } } + } finally { + socketFinderlock.unlock(); } } @@ -2716,7 +2726,8 @@ private SocketFactory getSocketFactory() throws IOException { */ private InetSocketAddress getInetAddressByIPPreference(String hostName, int portNumber) throws IOException, SQLServerException { - synchronized (addressList) { + ADDRESS_LIST_LOCK.lock(); + try { InetSocketAddress addr = InetSocketAddress.createUnresolved(hostName, portNumber); for (int i = 0; i < addressList.size(); i++) { addr = new InetSocketAddress(addressList.get(i), portNumber); @@ -2724,6 +2735,8 @@ private InetSocketAddress getInetAddressByIPPreference(String hostName, return addr; } return addr; + } finally { + ADDRESS_LIST_LOCK.unlock(); } } @@ -2807,7 +2820,8 @@ private Socket getSocketByIPPreference(String hostName, int portNumber, int time * Boolean switch for IPv6 first */ private void fillAddressList(InetAddress[] addresses, boolean ipv6first) { - synchronized (addressList) { + ADDRESS_LIST_LOCK.lock(); + try { addressList.clear(); if (ipv6first) { for (InetAddress addr : addresses) { @@ -2822,6 +2836,8 @@ private void fillAddressList(InetAddress[] addresses, boolean ipv6first) { } } } + } finally { + ADDRESS_LIST_LOCK.unlock(); } } @@ -2867,7 +2883,8 @@ private void findSocketUsingThreading(InetAddress[] inetAddrs, int portNumber, } // acquire parent lock and spawn all threads - synchronized (parentThreadLock) { + parentThreadLock.lock(); + try { for (SocketConnector sc : socketConnectors) { threadPoolExecutor.execute(sc); } @@ -2899,7 +2916,7 @@ private void findSocketUsingThreading(InetAddress[] inetAddrs, int portNumber, if (timeRemaining <= 0 || (!result.equals(Result.UNKNOWN))) break; - parentThreadLock.wait(timeRemaining); + parentCondition.await(timeRemaining, TimeUnit.MILLISECONDS); if (logger.isLoggable(Level.FINER)) { logger.finer(this.toString() + " The parent thread wokeup."); @@ -2907,7 +2924,8 @@ private void findSocketUsingThreading(InetAddress[] inetAddrs, int portNumber, timerNow = System.currentTimeMillis(); } - + } finally { + parentThreadLock.unlock(); } } finally { @@ -3001,7 +3019,8 @@ void updateResult(Socket socket, IOException exception, String threadId) { logger.finer("The following child thread is waiting for socketFinderLock:" + threadId); } - synchronized (socketFinderlock) { + socketFinderlock.lock(); + try { if (logger.isLoggable(Level.FINER)) { logger.finer("The following child thread acquired socketFinderLock:" + threadId); } @@ -3058,12 +3077,15 @@ void updateResult(Socket socket, IOException exception, String threadId) { logger.finer("The following child thread is waiting for parentThreadLock:" + threadId); } - synchronized (parentThreadLock) { + parentThreadLock.lock(); + try { if (logger.isLoggable(Level.FINER)) { logger.finer("The following child thread acquired parentThreadLock:" + threadId); } - parentThreadLock.notifyAll(); + parentCondition.signalAll(); + } finally { + parentThreadLock.unlock(); } if (logger.isLoggable(Level.FINER)) { @@ -3072,6 +3094,8 @@ void updateResult(Socket socket, IOException exception, String threadId) { + threadId); } } + } finally { + socketFinderlock.unlock(); } if (logger.isLoggable(Level.FINER)) { @@ -3150,6 +3174,7 @@ final class SocketConnector implements Runnable { // a counter used to give unique IDs to each connector thread. // this will have the id of the thread that was last created. private static long lastThreadID = 0; + private static final Lock LOCK = new ReentrantLock(); /** * Constructs a new SocketConnector object with the associated socket and socketFinder @@ -3207,15 +3232,20 @@ public String toString() { /** * Generates the next unique thread id. */ - private static synchronized long nextThreadID() { - if (lastThreadID == Long.MAX_VALUE) { - if (logger.isLoggable(Level.FINER)) - logger.finer("Resetting the Id count"); - lastThreadID = 1; - } else { - lastThreadID++; + private static long nextThreadID() { + LOCK.lock(); + try { + if (lastThreadID == Long.MAX_VALUE) { + if (logger.isLoggable(Level.FINER)) + logger.finer("Resetting the Id count"); + lastThreadID = 1; + } else { + lastThreadID++; + } + return lastThreadID; + } finally { + LOCK.unlock(); } - return lastThreadID; } } @@ -6648,6 +6678,7 @@ final SQLServerConnection getConnection() { private boolean serverSupportsColumnEncryption = false; private boolean serverSupportsDataClassification = false; private byte serverSupportedDataClassificationVersion = TDS.DATA_CLASSIFICATION_NOT_ENABLED; + private final Lock lock = new ReentrantLock(); private ColumnEncryptionVersion columnEncryptionVersion; @@ -6769,114 +6800,119 @@ private boolean nextPacket() throws SQLServerException { * This method is synchronized to guard against simultaneously reading packets from one thread that is processing * the response and another thread that is trying to buffer it with TDSCommand.detach(). */ - synchronized final boolean readPacket() throws SQLServerException { - if (null != command && !command.readingResponse()) - return false; - - // Number of packets in should always be less than number of packets out. - // If the server has been notified for an interrupt, it may be less by - // more than one packet. - assert tdsChannel.numMsgsRcvd < tdsChannel.numMsgsSent : "numMsgsRcvd:" + tdsChannel.numMsgsRcvd - + " should be less than numMsgsSent:" + tdsChannel.numMsgsSent; - - TDSPacket newPacket = new TDSPacket(con.getTDSPacketSize()); - if (null != command) { - // if cancelQueryTimeout is set, we should wait for the total amount of - // queryTimeout + cancelQueryTimeout to - // terminate the connection. - if ((command.getCancelQueryTimeoutSeconds() > 0 && command.getQueryTimeoutSeconds() > 0)) { - // if a timeout is configured with this object, add it to the timeout poller - int seconds = command.getCancelQueryTimeoutSeconds() + command.getQueryTimeoutSeconds(); - this.timeout = con.getSharedTimer().schedule(new TDSTimeoutTask(command, con), seconds); - } - } - // First, read the packet header. - for (int headerBytesRead = 0; headerBytesRead < TDS.PACKET_HEADER_SIZE;) { - int bytesRead = tdsChannel.read(newPacket.header, headerBytesRead, - TDS.PACKET_HEADER_SIZE - headerBytesRead); - if (bytesRead < 0) { - if (logger.isLoggable(Level.FINER)) - logger.finer(toString() + " Premature EOS in response. packetNum:" + packetNum + " headerBytesRead:" - + headerBytesRead); + final boolean readPacket() throws SQLServerException { + lock.lock(); + try { + if (null != command && !command.readingResponse()) + return false; - con.terminate(SQLServerException.DRIVER_ERROR_IO_FAILED, - ((0 == packetNum && 0 == headerBytesRead) ? SQLServerException.getErrString( - "R_noServerResponse") : SQLServerException.getErrString("R_truncatedServerResponse"))); + // Number of packets in should always be less than number of packets out. + // If the server has been notified for an interrupt, it may be less by + // more than one packet. + assert tdsChannel.numMsgsRcvd < tdsChannel.numMsgsSent : "numMsgsRcvd:" + tdsChannel.numMsgsRcvd + + " should be less than numMsgsSent:" + tdsChannel.numMsgsSent; + + TDSPacket newPacket = new TDSPacket(con.getTDSPacketSize()); + if (null != command) { + // if cancelQueryTimeout is set, we should wait for the total amount of + // queryTimeout + cancelQueryTimeout to + // terminate the connection. + if ((command.getCancelQueryTimeoutSeconds() > 0 && command.getQueryTimeoutSeconds() > 0)) { + // if a timeout is configured with this object, add it to the timeout poller + int seconds = command.getCancelQueryTimeoutSeconds() + command.getQueryTimeoutSeconds(); + this.timeout = con.getSharedTimer().schedule(new TDSTimeoutTask(command, con), seconds); + } } + // First, read the packet header. + for (int headerBytesRead = 0; headerBytesRead < TDS.PACKET_HEADER_SIZE;) { + int bytesRead = tdsChannel.read(newPacket.header, headerBytesRead, + TDS.PACKET_HEADER_SIZE - headerBytesRead); + if (bytesRead < 0) { + if (logger.isLoggable(Level.FINER)) + logger.finer(toString() + " Premature EOS in response. packetNum:" + packetNum + " headerBytesRead:" + + headerBytesRead); - headerBytesRead += bytesRead; - } + con.terminate(SQLServerException.DRIVER_ERROR_IO_FAILED, + ((0 == packetNum && 0 == headerBytesRead) ? SQLServerException.getErrString( + "R_noServerResponse") : SQLServerException.getErrString("R_truncatedServerResponse"))); + } - // if execution was subject to timeout then stop timing - if (this.timeout != null) { - this.timeout.cancel(false); - this.timeout = null; - } - // Header size is a 2 byte unsigned short integer in big-endian order. - int packetLength = Util.readUnsignedShortBigEndian(newPacket.header, TDS.PACKET_HEADER_MESSAGE_LENGTH); + headerBytesRead += bytesRead; + } - // Make header size is properly bounded and compute length of the packet payload. - if (packetLength < TDS.PACKET_HEADER_SIZE || packetLength > con.getTDSPacketSize()) { - if (logger.isLoggable(Level.WARNING)) { - logger.warning(toString() + " TDS header contained invalid packet length:" + packetLength - + "; packet size:" + con.getTDSPacketSize()); + // if execution was subject to timeout then stop timing + if (this.timeout != null) { + this.timeout.cancel(false); + this.timeout = null; + } + // Header size is a 2 byte unsigned short integer in big-endian order. + int packetLength = Util.readUnsignedShortBigEndian(newPacket.header, TDS.PACKET_HEADER_MESSAGE_LENGTH); + + // Make header size is properly bounded and compute length of the packet payload. + if (packetLength < TDS.PACKET_HEADER_SIZE || packetLength > con.getTDSPacketSize()) { + if (logger.isLoggable(Level.WARNING)) { + logger.warning(toString() + " TDS header contained invalid packet length:" + packetLength + + "; packet size:" + con.getTDSPacketSize()); + } + throwInvalidTDS(); } - throwInvalidTDS(); - } - newPacket.payloadLength = packetLength - TDS.PACKET_HEADER_SIZE; + newPacket.payloadLength = packetLength - TDS.PACKET_HEADER_SIZE; - // Just grab the SPID for logging (another big-endian unsigned short). - tdsChannel.setSPID(Util.readUnsignedShortBigEndian(newPacket.header, TDS.PACKET_HEADER_SPID)); + // Just grab the SPID for logging (another big-endian unsigned short). + tdsChannel.setSPID(Util.readUnsignedShortBigEndian(newPacket.header, TDS.PACKET_HEADER_SPID)); - // Packet header looks good enough. - // When logging, copy the packet header to the log buffer. - byte[] logBuffer = null; - if (tdsChannel.isLoggingPackets()) { - logBuffer = new byte[packetLength]; - System.arraycopy(newPacket.header, 0, logBuffer, 0, TDS.PACKET_HEADER_SIZE); - } + // Packet header looks good enough. + // When logging, copy the packet header to the log buffer. + byte[] logBuffer = null; + if (tdsChannel.isLoggingPackets()) { + logBuffer = new byte[packetLength]; + 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); - } + // 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, - newPacket.payloadLength - payloadBytesRead); - if (bytesRead < 0) - con.terminate(SQLServerException.DRIVER_ERROR_IO_FAILED, - SQLServerException.getErrString("R_truncatedServerResponse")); + // Now for the payload... + for (int payloadBytesRead = 0; payloadBytesRead < newPacket.payloadLength;) { + int bytesRead = tdsChannel.read(newPacket.payload, payloadBytesRead, + newPacket.payloadLength - payloadBytesRead); + if (bytesRead < 0) + con.terminate(SQLServerException.DRIVER_ERROR_IO_FAILED, + SQLServerException.getErrString("R_truncatedServerResponse")); - payloadBytesRead += bytesRead; - } + payloadBytesRead += bytesRead; + } - ++packetNum; + ++packetNum; - lastPacket.next = newPacket; - lastPacket = newPacket; + lastPacket.next = newPacket; + lastPacket = newPacket; - // When logging, append the payload to the log buffer and write out the whole thing. - if (tdsChannel.isLoggingPackets() && logBuffer != null) { - System.arraycopy(newPacket.payload, 0, logBuffer, TDS.PACKET_HEADER_SIZE, newPacket.payloadLength); - tdsChannel.logPacket(logBuffer, 0, packetLength, - this.toString() + " received Packet:" + packetNum + " (" + newPacket.payloadLength + " bytes)"); - } + // When logging, append the payload to the log buffer and write out the whole thing. + if (tdsChannel.isLoggingPackets() && logBuffer != null) { + System.arraycopy(newPacket.payload, 0, logBuffer, TDS.PACKET_HEADER_SIZE, newPacket.payloadLength); + tdsChannel.logPacket(logBuffer, 0, packetLength, + this.toString() + " received Packet:" + packetNum + " (" + newPacket.payloadLength + " bytes)"); + } - // If end of message, then bump the count of messages received and disable - // interrupts. If an interrupt happened prior to disabling, then expect - // to read the attention ack packet as well. - if (newPacket.isEOM()) { - ++tdsChannel.numMsgsRcvd; + // If end of message, then bump the count of messages received and disable + // interrupts. If an interrupt happened prior to disabling, then expect + // to read the attention ack packet as well. + if (newPacket.isEOM()) { + ++tdsChannel.numMsgsRcvd; - // Notify the command (if any) that we've reached the end of the response. - if (null != command) - command.onResponseEOM(); - } + // Notify the command (if any) that we've reached the end of the response. + if (null != command) + command.onResponseEOM(); + } - return true; + return true; + } finally { + lock.unlock(); + } } final TDSReaderMark mark() { @@ -7478,7 +7514,7 @@ protected TDSWriter getTDSWriter() { // Lock to ensure atomicity when manipulating more than one of the following // shared interrupt state variables below. - private final Object interruptLock = new Object(); + private final Lock interruptLock = new ReentrantLock(); // Flag set when this command starts execution, indicating that it is // ready to respond to interrupts; and cleared when its last response packet is @@ -7492,8 +7528,11 @@ protected boolean getInterruptsEnabled() { } protected void setInterruptsEnabled(boolean interruptsEnabled) { - synchronized (interruptLock) { + interruptLock.lock(); + try { this.interruptsEnabled = interruptsEnabled; + } finally { + interruptLock.unlock(); } } @@ -7518,8 +7557,11 @@ protected boolean getRequestComplete() { } protected void setRequestComplete(boolean requestComplete) { - synchronized (interruptLock) { + interruptLock.lock(); + try { this.requestComplete = requestComplete; + } finally { + interruptLock.unlock(); } } @@ -7542,8 +7584,11 @@ protected boolean getProcessedResponse() { } protected void setProcessedResponse(boolean processedResponse) { - synchronized (interruptLock) { + interruptLock.lock(); + try { this.processedResponse = processedResponse; + } finally { + interruptLock.unlock(); } } @@ -7768,7 +7813,8 @@ final void close() { void interrupt(String reason) throws SQLServerException { // Multiple, possibly simultaneous, interrupts may occur. // Only the first one should be recognized and acted upon. - synchronized (interruptLock) { + interruptLock.lock(); + try { if (interruptsEnabled && !wasInterrupted()) { if (logger.isLoggable(Level.FINEST)) logger.finest(this + ": Raising interrupt for reason:" + reason); @@ -7782,6 +7828,8 @@ void interrupt(String reason) throws SQLServerException { this.correspondingThread = null; } } + } finally { + interruptLock.unlock(); } } @@ -7830,7 +7878,8 @@ final void checkForInterrupt() throws SQLServerException { * completes after being interrupted (0 or more packets sent with no EOM bit). */ final void onRequestComplete() throws SQLServerException { - synchronized (interruptLock) { + interruptLock.lock(); + try { assert !requestComplete; if (logger.isLoggable(Level.FINEST)) @@ -7864,6 +7913,8 @@ final void onRequestComplete() throws SQLServerException { assert !processedResponse; readingResponse = true; } + } finally { + interruptLock.unlock(); } } @@ -7883,7 +7934,8 @@ final void onResponseEOM() throws SQLServerException { // Atomically disable interrupts and check for a previous interrupt requiring // an attention ack to be read. - synchronized (interruptLock) { + interruptLock.lock(); + try { if (interruptsEnabled) { if (logger.isLoggable(Level.FINEST)) logger.finest(this + ": disabling interrupts"); @@ -7897,6 +7949,8 @@ final void onResponseEOM() throws SQLServerException { interruptsEnabled = false; } + } finally { + interruptLock.unlock(); } // If an attention packet needs to be read then read it. This should @@ -7956,7 +8010,8 @@ final TDSWriter startRequest(byte tdsMessageType) throws SQLServerException { // (Re)initialize this command's interrupt state for its current execution. // To ensure atomically consistent behavior, do not leave the interrupt lock // until interrupts have been (re)enabled. - synchronized (interruptLock) { + interruptLock.lock(); + try { requestComplete = false; readingResponse = false; processedResponse = false; @@ -7964,6 +8019,8 @@ final TDSWriter startRequest(byte tdsMessageType) throws SQLServerException { wasInterrupted = false; interruptReason = null; interruptsEnabled = true; + } finally { + interruptLock.unlock(); } return tdsWriter; diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/PersistentTokenCacheAccessAspect.java b/src/main/java/com/microsoft/sqlserver/jdbc/PersistentTokenCacheAccessAspect.java index 310cdf87d..091c5a5e8 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/PersistentTokenCacheAccessAspect.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/PersistentTokenCacheAccessAspect.java @@ -8,6 +8,9 @@ import com.microsoft.aad.msal4j.ITokenCacheAccessAspect; import com.microsoft.aad.msal4j.ITokenCacheAccessContext; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + /** * Access aspect for accessing the token cache. @@ -21,6 +24,8 @@ public class PersistentTokenCacheAccessAspect implements ITokenCacheAccessAspect { private static PersistentTokenCacheAccessAspect instance = new PersistentTokenCacheAccessAspect(); + private final Lock lock = new ReentrantLock(); + private PersistentTokenCacheAccessAspect() {} static PersistentTokenCacheAccessAspect getInstance() { @@ -33,17 +38,27 @@ static PersistentTokenCacheAccessAspect getInstance() { private String cache = null; @Override - public synchronized void beforeCacheAccess(ITokenCacheAccessContext iTokenCacheAccessContext) { - if (null != cache && null != iTokenCacheAccessContext && null != iTokenCacheAccessContext.tokenCache()) { - iTokenCacheAccessContext.tokenCache().deserialize(cache); + public void beforeCacheAccess(ITokenCacheAccessContext iTokenCacheAccessContext) { + lock.lock(); + try { + if (null != cache && null != iTokenCacheAccessContext && null != iTokenCacheAccessContext.tokenCache()) { + iTokenCacheAccessContext.tokenCache().deserialize(cache); + } + } finally { + lock.unlock(); } } @Override - public synchronized void afterCacheAccess(ITokenCacheAccessContext iTokenCacheAccessContext) { - if (null != iTokenCacheAccessContext && iTokenCacheAccessContext.hasCacheChanged() - && null != iTokenCacheAccessContext.tokenCache()) - cache = iTokenCacheAccessContext.tokenCache().serialize(); + public void afterCacheAccess(ITokenCacheAccessContext iTokenCacheAccessContext) { + lock.lock(); + try { + if (null != iTokenCacheAccessContext && iTokenCacheAccessContext.hasCacheChanged() && null != iTokenCacheAccessContext.tokenCache()) + cache = iTokenCacheAccessContext.tokenCache().serialize(); + } finally { + lock.unlock(); + } + } /** diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index 715a6fce1..4605d2021 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -5,58 +5,32 @@ package com.microsoft.sqlserver.jdbc; -import static java.nio.charset.StandardCharsets.UTF_16LE; +import com.microsoft.sqlserver.jdbc.SQLServerError.TransientError; +import mssql.googlecode.cityhash.CityHash; +import mssql.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap; +import mssql.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap.Builder; +import mssql.googlecode.concurrentlinkedhashmap.EvictionListener; +import org.ietf.jgss.GSSCredential; +import javax.sql.XAConnection; import java.io.IOException; import java.io.Serializable; -import java.net.DatagramPacket; -import java.net.DatagramSocket; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.SocketException; -import java.net.UnknownHostException; -import java.sql.CallableStatement; -import java.sql.Connection; -import java.sql.DatabaseMetaData; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLClientInfoException; -import java.sql.SQLException; -import java.sql.SQLPermission; -import java.sql.SQLWarning; -import java.sql.SQLXML; -import java.sql.Savepoint; -import java.sql.Statement; +import java.net.*; +import java.sql.*; import java.text.MessageFormat; import java.time.Duration; import java.time.Instant; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Properties; -import java.util.UUID; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; -import javax.sql.XAConnection; - -import org.ietf.jgss.GSSCredential; - -import com.microsoft.sqlserver.jdbc.SQLServerError.TransientError; - -import mssql.googlecode.cityhash.CityHash; -import mssql.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap; -import mssql.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap.Builder; -import mssql.googlecode.concurrentlinkedhashmap.EvictionListener; +import static java.nio.charset.StandardCharsets.UTF_16LE; /** @@ -224,6 +198,15 @@ public class SQLServerConnection implements ISQLServerConnection, java.io.Serial /** cached MSI token time-to-live */ private int cachedMsiTokenTtl = 0; + /** + * lock instance for "this" + **/ + private final Lock lock = new ReentrantLock(); + /** + * static lock instance for the class + **/ + private static final Lock LOCK = new ReentrantLock(); + /** * Return an existing cached SharedTimer associated with this Connection or create a new one. * @@ -924,15 +907,17 @@ IdleConnectionResiliency getSessionRecovery() { * @throws SQLServerException * when an error occurs */ - public static synchronized void registerColumnEncryptionKeyStoreProviders( + public static void registerColumnEncryptionKeyStoreProviders( Map clientKeyStoreProviders) throws SQLServerException { loggerExternal.entering(loggingClassNameBase, "registerColumnEncryptionKeyStoreProviders", "Registering Column Encryption Key Store Providers"); - if (null == clientKeyStoreProviders) { - throw new SQLServerException(null, SQLServerException.getErrString("R_CustomKeyStoreProviderMapNull"), null, - 0, false); - } + LOCK.lock(); + try { + if (null == clientKeyStoreProviders) { + throw new SQLServerException(null, SQLServerException.getErrString("R_CustomKeyStoreProviderMapNull"), + null, 0, false); + } if (null != globalCustomColumnEncryptionKeyStoreProviders && !globalCustomColumnEncryptionKeyStoreProviders.isEmpty()) { @@ -962,9 +947,12 @@ public static synchronized void registerColumnEncryptionKeyStoreProviders( null, 0, false); } - // Global providers should not use their own CEK caches. - provider.setColumnEncryptionCacheTtl(Duration.ZERO); - globalCustomColumnEncryptionKeyStoreProviders.put(providerName, provider); + // Global providers should not use their own CEK caches. + provider.setColumnEncryptionCacheTtl(Duration.ZERO); + globalCustomColumnEncryptionKeyStoreProviders.put(providerName, provider); + } + } finally { + LOCK.unlock(); } loggerExternal.exiting(loggingClassNameBase, "registerColumnEncryptionKeyStoreProviders", @@ -976,111 +964,158 @@ public static synchronized void registerColumnEncryptionKeyStoreProviders( * Unregisters all the custom key store providers from the globalCustomColumnEncryptionKeyStoreProviders by clearing * the map and setting it to null. */ - public static synchronized void unregisterColumnEncryptionKeyStoreProviders() { + public static void unregisterColumnEncryptionKeyStoreProviders() { loggerExternal.entering(loggingClassNameBase, "unregisterColumnEncryptionKeyStoreProviders", "Removing Column Encryption Key Store Provider"); - if (null != globalCustomColumnEncryptionKeyStoreProviders) { - globalCustomColumnEncryptionKeyStoreProviders.clear(); - globalCustomColumnEncryptionKeyStoreProviders = null; + LOCK.lock(); + try { + if (null != globalCustomColumnEncryptionKeyStoreProviders) { + globalCustomColumnEncryptionKeyStoreProviders.clear(); + globalCustomColumnEncryptionKeyStoreProviders = null; + } + } finally { + LOCK.unlock(); } loggerExternal.exiting(loggingClassNameBase, "unregisterColumnEncryptionKeyStoreProviders", "Number of Key store providers that are registered: 0"); } - synchronized SQLServerColumnEncryptionKeyStoreProvider getGlobalSystemColumnEncryptionKeyStoreProvider( - String providerName) { - return (null != globalSystemColumnEncryptionKeyStoreProviders && globalSystemColumnEncryptionKeyStoreProviders - .containsKey(providerName)) ? globalSystemColumnEncryptionKeyStoreProviders.get(providerName) : null; + SQLServerColumnEncryptionKeyStoreProvider getGlobalSystemColumnEncryptionKeyStoreProvider(String providerName) { + lock.lock(); + try { + return (null != globalSystemColumnEncryptionKeyStoreProviders && globalSystemColumnEncryptionKeyStoreProviders.containsKey( + providerName)) ? globalSystemColumnEncryptionKeyStoreProviders.get(providerName) : null; + } finally { + lock.unlock(); + } } - synchronized String getAllGlobalCustomSystemColumnEncryptionKeyStoreProviders() { - return (null != globalCustomColumnEncryptionKeyStoreProviders) ? globalCustomColumnEncryptionKeyStoreProviders - .keySet().toString() : null; + String getAllGlobalCustomSystemColumnEncryptionKeyStoreProviders() { + lock.lock(); + try { + return (null != globalCustomColumnEncryptionKeyStoreProviders) + ? globalCustomColumnEncryptionKeyStoreProviders.keySet().toString() : null; + } finally { + lock.unlock(); + } } - synchronized String getAllSystemColumnEncryptionKeyStoreProviders() { - String keyStores = ""; - if (0 != systemColumnEncryptionKeyStoreProvider.size()) - keyStores = systemColumnEncryptionKeyStoreProvider.keySet().toString(); - if (0 != SQLServerConnection.globalSystemColumnEncryptionKeyStoreProviders.size()) - keyStores += "," + SQLServerConnection.globalSystemColumnEncryptionKeyStoreProviders.keySet().toString(); - return keyStores; + String getAllSystemColumnEncryptionKeyStoreProviders() { + lock.lock(); + try { + String keyStores = ""; + if (0 != systemColumnEncryptionKeyStoreProvider.size()) { + keyStores = systemColumnEncryptionKeyStoreProvider.keySet().toString(); + } + if (0 != SQLServerConnection.globalSystemColumnEncryptionKeyStoreProviders.size()) { + keyStores += "," + SQLServerConnection.globalSystemColumnEncryptionKeyStoreProviders.keySet() + .toString(); + } + return keyStores; + } finally { + lock.unlock(); + } } - synchronized SQLServerColumnEncryptionKeyStoreProvider getGlobalCustomColumnEncryptionKeyStoreProvider( - String providerName) { - return (null != globalCustomColumnEncryptionKeyStoreProviders && globalCustomColumnEncryptionKeyStoreProviders - .containsKey(providerName)) ? globalCustomColumnEncryptionKeyStoreProviders.get(providerName) : null; + SQLServerColumnEncryptionKeyStoreProvider getGlobalCustomColumnEncryptionKeyStoreProvider(String providerName) { + lock.lock(); + try { + return (null != globalCustomColumnEncryptionKeyStoreProviders && globalCustomColumnEncryptionKeyStoreProviders.containsKey( + providerName)) ? globalCustomColumnEncryptionKeyStoreProviders.get(providerName) : null; + } finally { + lock.unlock(); + } } - synchronized SQLServerColumnEncryptionKeyStoreProvider getSystemColumnEncryptionKeyStoreProvider( - String providerName) { - return (null != systemColumnEncryptionKeyStoreProvider && systemColumnEncryptionKeyStoreProvider - .containsKey(providerName)) ? systemColumnEncryptionKeyStoreProvider.get(providerName) : null; + SQLServerColumnEncryptionKeyStoreProvider getSystemColumnEncryptionKeyStoreProvider(String providerName) { + lock.lock(); + try { + return (null != systemColumnEncryptionKeyStoreProvider && systemColumnEncryptionKeyStoreProvider.containsKey( + providerName)) ? systemColumnEncryptionKeyStoreProvider.get(providerName) : null; + } finally { + lock.unlock(); + } } - synchronized SQLServerColumnEncryptionKeyStoreProvider getSystemOrGlobalColumnEncryptionKeyStoreProvider( + SQLServerColumnEncryptionKeyStoreProvider getSystemOrGlobalColumnEncryptionKeyStoreProvider( String providerName) throws SQLServerException { + lock.lock(); + try { + // check for global system providers + keystoreProvider = getGlobalSystemColumnEncryptionKeyStoreProvider(providerName); - // check for global system providers - keystoreProvider = getGlobalSystemColumnEncryptionKeyStoreProvider(providerName); + // Check for the connection system provider. + if (null == keystoreProvider) { + keystoreProvider = getSystemColumnEncryptionKeyStoreProvider(providerName); + } - // Check for the connection system provider. - if (null == keystoreProvider) { - keystoreProvider = getSystemColumnEncryptionKeyStoreProvider(providerName); - } + // There is no global system provider of this name, check for the global custom providers. + if (null == keystoreProvider) { + keystoreProvider = getGlobalCustomColumnEncryptionKeyStoreProvider(providerName); + } - // There is no global system provider of this name, check for the global custom providers. - if (null == keystoreProvider) { - keystoreProvider = getGlobalCustomColumnEncryptionKeyStoreProvider(providerName); - } + // No provider was found of this name. + if (null == keystoreProvider) { + String systemProviders = getAllSystemColumnEncryptionKeyStoreProviders(); + String customProviders = getAllGlobalCustomSystemColumnEncryptionKeyStoreProviders(); + MessageFormat form = new MessageFormat( + SQLServerException.getErrString("R_UnrecognizedKeyStoreProviderName")); + Object[] msgArgs = {providerName, systemProviders, customProviders}; + throw new SQLServerException(form.format(msgArgs), null); + } - // No provider was found of this name. - if (null == keystoreProvider) { - String systemProviders = getAllSystemColumnEncryptionKeyStoreProviders(); - String customProviders = getAllGlobalCustomSystemColumnEncryptionKeyStoreProviders(); - MessageFormat form = new MessageFormat( - SQLServerException.getErrString("R_UnrecognizedKeyStoreProviderName")); - Object[] msgArgs = {providerName, systemProviders, customProviders}; - throw new SQLServerException(form.format(msgArgs), null); + return keystoreProvider; + } finally { + lock.unlock(); } - - return keystoreProvider; } - synchronized boolean hasConnectionColumnEncryptionKeyStoreProvidersRegistered() { - return null != connectionColumnEncryptionKeyStoreProvider - && connectionColumnEncryptionKeyStoreProvider.size() > 0; + boolean hasConnectionColumnEncryptionKeyStoreProvidersRegistered() { + lock.lock(); + try { + return null != connectionColumnEncryptionKeyStoreProvider && connectionColumnEncryptionKeyStoreProvider.size() > 0; + } finally { + lock.unlock(); + } } - synchronized String getAllConnectionColumnEncryptionKeyStoreProviders() { - String keyStores = ""; - if (0 != connectionColumnEncryptionKeyStoreProvider.size()) - keyStores = connectionColumnEncryptionKeyStoreProvider.keySet().toString(); - return keyStores; + String getAllConnectionColumnEncryptionKeyStoreProviders() { + lock.lock(); + try { + String keyStores = ""; + if (0 != connectionColumnEncryptionKeyStoreProvider.size()) { + keyStores = connectionColumnEncryptionKeyStoreProvider.keySet().toString(); + } + return keyStores; + } finally { + lock.unlock(); + } } - synchronized SQLServerColumnEncryptionKeyStoreProvider getColumnEncryptionKeyStoreProviderOnConnection( + SQLServerColumnEncryptionKeyStoreProvider getColumnEncryptionKeyStoreProviderOnConnection( String providerName) throws SQLServerException { - - // Check for a connection-level provider first - if (null != connectionColumnEncryptionKeyStoreProvider - && connectionColumnEncryptionKeyStoreProvider.size() > 0) { - // If any connection-level providers are registered, we don't fall back to system/global providers - if (connectionColumnEncryptionKeyStoreProvider.containsKey(providerName)) { - return connectionColumnEncryptionKeyStoreProvider.get(providerName); - } else { - MessageFormat form = new MessageFormat( - SQLServerException.getErrString("R_UnrecognizedConnectionKeyStoreProviderName")); - Object[] msgArgs = {providerName, getAllConnectionColumnEncryptionKeyStoreProviders()}; - throw new SQLServerException(form.format(msgArgs), null); + lock.lock(); + try { + // Check for a connection-level provider first + if (null != connectionColumnEncryptionKeyStoreProvider && connectionColumnEncryptionKeyStoreProvider.size() > 0) { + // If any connection-level providers are registered, we don't fall back to system/global providers + if (connectionColumnEncryptionKeyStoreProvider.containsKey(providerName)) { + return connectionColumnEncryptionKeyStoreProvider.get(providerName); + } else { + MessageFormat form = new MessageFormat( + SQLServerException.getErrString("R_UnrecognizedConnectionKeyStoreProviderName")); + Object[] msgArgs = {providerName, getAllConnectionColumnEncryptionKeyStoreProviders()}; + throw new SQLServerException(form.format(msgArgs), null); + } } - } - // No connection-level providers registered, so return system/global provider - return getSystemOrGlobalColumnEncryptionKeyStoreProvider(providerName); + // No connection-level providers registered, so return system/global provider + return getSystemOrGlobalColumnEncryptionKeyStoreProvider(providerName); + } finally { + lock.unlock(); + } } /** This is a user-defined per-connection store provider. */ @@ -1088,46 +1123,50 @@ synchronized SQLServerColumnEncryptionKeyStoreProvider getColumnEncryptionKeySto /** * Registers connection-level key store providers, replacing all existing providers. - * + * * @param clientKeyStoreProviders - * a map containing the store providers information. + * a map containing the store providers information. * @throws SQLServerException * when an error occurs */ - public synchronized void registerColumnEncryptionKeyStoreProvidersOnConnection( + public void registerColumnEncryptionKeyStoreProvidersOnConnection( Map clientKeyStoreProviders) throws SQLServerException { loggerExternal.entering(loggingClassName, "registerColumnEncryptionKeyStoreProvidersOnConnection", "Registering Column Encryption Key Store Providers on Connection"); + lock.lock(); + try { + if (null == clientKeyStoreProviders) { + throw new SQLServerException(null, SQLServerException.getErrString("R_CustomKeyStoreProviderMapNull"), + null, 0, false); + } - if (null == clientKeyStoreProviders) { - throw new SQLServerException(null, SQLServerException.getErrString("R_CustomKeyStoreProviderMapNull"), null, - 0, false); - } + connectionColumnEncryptionKeyStoreProvider.clear(); - connectionColumnEncryptionKeyStoreProvider.clear(); + for (Map.Entry entry : clientKeyStoreProviders.entrySet()) { + String providerName = entry.getKey(); + if (null == providerName || 0 == providerName.trim().length()) { + throw new SQLServerException(null, + SQLServerException.getErrString("R_EmptyCustomKeyStoreProviderName"), null, 0, false); + } - for (Map.Entry entry : clientKeyStoreProviders.entrySet()) { - String providerName = entry.getKey(); - if (null == providerName || 0 == providerName.trim().length()) { - throw new SQLServerException(null, SQLServerException.getErrString("R_EmptyCustomKeyStoreProviderName"), - null, 0, false); - } + // MSSQL_CERTIFICATE_STORE not allowed on connection level + if ((providerName.equalsIgnoreCase(WINDOWS_KEY_STORE_NAME))) { + MessageFormat form = new MessageFormat( + SQLServerException.getErrString("R_InvalidCustomKeyStoreProviderName")); + Object[] msgArgs = {providerName, WINDOWS_KEY_STORE_NAME}; + throw new SQLServerException(null, form.format(msgArgs), null, 0, false); + } - // MSSQL_CERTIFICATE_STORE not allowed on connection level - if ((providerName.equalsIgnoreCase(WINDOWS_KEY_STORE_NAME))) { - MessageFormat form = new MessageFormat( - SQLServerException.getErrString("R_InvalidCustomKeyStoreProviderName")); - Object[] msgArgs = {providerName, WINDOWS_KEY_STORE_NAME}; - throw new SQLServerException(null, form.format(msgArgs), null, 0, false); - } + if (null == entry.getValue()) { + throw new SQLServerException(null, + String.format(SQLServerException.getErrString("R_CustomKeyStoreProviderValueNull"), + providerName), null, 0, false); + } - if (null == entry.getValue()) { - throw new SQLServerException(null, String - .format(SQLServerException.getErrString("R_CustomKeyStoreProviderValueNull"), providerName), - null, 0, false); + connectionColumnEncryptionKeyStoreProvider.put(entry.getKey(), entry.getValue()); } - - connectionColumnEncryptionKeyStoreProvider.put(entry.getKey(), entry.getValue()); + } finally { + lock.unlock(); } loggerExternal.exiting(loggingClassName, "registerColumnEncryptionKeyStoreProvidersOnConnection", @@ -1145,15 +1184,19 @@ public synchronized void registerColumnEncryptionKeyStoreProvidersOnConnection( * @param trustedKeyPaths * all master key paths that are trusted */ - public static synchronized void setColumnEncryptionTrustedMasterKeyPaths( - Map> trustedKeyPaths) { + public static void setColumnEncryptionTrustedMasterKeyPaths(Map> trustedKeyPaths) { loggerExternal.entering(loggingClassNameBase, "setColumnEncryptionTrustedMasterKeyPaths", "Setting Trusted Master Key Paths"); - // Use upper case for server and instance names. - columnEncryptionTrustedMasterKeyPaths.clear(); - for (Map.Entry> entry : trustedKeyPaths.entrySet()) { - columnEncryptionTrustedMasterKeyPaths.put(entry.getKey().toUpperCase(), entry.getValue()); + LOCK.lock(); + try { + // Use upper case for server and instance names. + columnEncryptionTrustedMasterKeyPaths.clear(); + for (Map.Entry> entry : trustedKeyPaths.entrySet()) { + columnEncryptionTrustedMasterKeyPaths.put(entry.getKey().toUpperCase(), entry.getValue()); + } + } finally { + LOCK.unlock(); } loggerExternal.exiting(loggingClassNameBase, "setColumnEncryptionTrustedMasterKeyPaths", @@ -1168,13 +1211,17 @@ public static synchronized void setColumnEncryptionTrustedMasterKeyPaths( * @param trustedKeyPaths * all master key paths that are trusted */ - public static synchronized void updateColumnEncryptionTrustedMasterKeyPaths(String server, - List trustedKeyPaths) { + public static void updateColumnEncryptionTrustedMasterKeyPaths(String server, List trustedKeyPaths) { loggerExternal.entering(loggingClassNameBase, "updateColumnEncryptionTrustedMasterKeyPaths", "Updating Trusted Master Key Paths"); - // Use upper case for server and instance names. - columnEncryptionTrustedMasterKeyPaths.put(server.toUpperCase(), trustedKeyPaths); + LOCK.lock(); + try { + // Use upper case for server and instance names. + columnEncryptionTrustedMasterKeyPaths.put(server.toUpperCase(), trustedKeyPaths); + } finally { + LOCK.unlock(); + } loggerExternal.exiting(loggingClassNameBase, "updateColumnEncryptionTrustedMasterKeyPaths", "Number of Trusted Master Key Paths: " + columnEncryptionTrustedMasterKeyPaths.size()); @@ -1186,12 +1233,17 @@ public static synchronized void updateColumnEncryptionTrustedMasterKeyPaths(Stri * @param server * String server name */ - public static synchronized void removeColumnEncryptionTrustedMasterKeyPaths(String server) { + public static void removeColumnEncryptionTrustedMasterKeyPaths(String server) { loggerExternal.entering(loggingClassNameBase, "removeColumnEncryptionTrustedMasterKeyPaths", "Removing Trusted Master Key Paths"); - // Use upper case for server and instance names. - columnEncryptionTrustedMasterKeyPaths.remove(server.toUpperCase()); + LOCK.lock(); + try { + // Use upper case for server and instance names. + columnEncryptionTrustedMasterKeyPaths.remove(server.toUpperCase()); + } finally { + LOCK.unlock(); + } loggerExternal.exiting(loggingClassNameBase, "removeColumnEncryptionTrustedMasterKeyPaths", "Number of Trusted Master Key Paths: " + columnEncryptionTrustedMasterKeyPaths.size()); @@ -1199,32 +1251,42 @@ public static synchronized void removeColumnEncryptionTrustedMasterKeyPaths(Stri /** * Returns the Trusted Master Key Paths. - * + * * @return columnEncryptionTrustedMasterKeyPaths. */ - public static synchronized Map> getColumnEncryptionTrustedMasterKeyPaths() { + public static Map> getColumnEncryptionTrustedMasterKeyPaths() { loggerExternal.entering(loggingClassNameBase, "getColumnEncryptionTrustedMasterKeyPaths", "Getting Trusted Master Key Paths"); - Map> masterKeyPathCopy = new HashMap<>(); + LOCK.lock(); + try { + Map> masterKeyPathCopy = new HashMap<>(); - for (Map.Entry> entry : columnEncryptionTrustedMasterKeyPaths.entrySet()) { - masterKeyPathCopy.put(entry.getKey(), entry.getValue()); - } + for (Map.Entry> entry : columnEncryptionTrustedMasterKeyPaths.entrySet()) { + masterKeyPathCopy.put(entry.getKey(), entry.getValue()); + } - loggerExternal.exiting(loggingClassNameBase, "getColumnEncryptionTrustedMasterKeyPaths", - "Number of Trusted Master Key Paths: " + masterKeyPathCopy.size()); + loggerExternal.exiting(loggingClassNameBase, "getColumnEncryptionTrustedMasterKeyPaths", + "Number of Trusted Master Key Paths: " + masterKeyPathCopy.size()); - return masterKeyPathCopy; + return masterKeyPathCopy; + } finally { + LOCK.unlock(); + } } - static synchronized List getColumnEncryptionTrustedMasterKeyPaths(String server, Boolean[] hasEntry) { - if (columnEncryptionTrustedMasterKeyPaths.containsKey(server)) { - hasEntry[0] = true; - return columnEncryptionTrustedMasterKeyPaths.get(server); - } else { - hasEntry[0] = false; - return null; + static List getColumnEncryptionTrustedMasterKeyPaths(String server, Boolean[] hasEntry) { + LOCK.lock(); + try { + if (columnEncryptionTrustedMasterKeyPaths.containsKey(server)) { + hasEntry[0] = true; + return columnEncryptionTrustedMasterKeyPaths.get(server); + } else { + hasEntry[0] = false; + return null; + } + } finally { + LOCK.unlock(); } } @@ -1232,8 +1294,13 @@ static synchronized List getColumnEncryptionTrustedMasterKeyPaths(String * Clears User token cache. This will clear all account info so interactive login will be required on the next * request to acquire an access token. */ - public static synchronized void clearUserTokenCache() { - PersistentTokenCacheAccessAspect.clearUserTokenCache(); + public static void clearUserTokenCache() { + LOCK.lock(); + try { + PersistentTokenCacheAccessAspect.clearUserTokenCache(); + } finally { + LOCK.unlock(); + } } /** the active set of connection properties */ @@ -3820,7 +3887,7 @@ final void terminate(int driverErrorCode, String message, Throwable throwable) t throw ex; } - private final transient Object schedulerLock = new Object(); + private final transient Lock schedulerLock = new ReentrantLock(); /** * Executes a command through the scheduler. @@ -3829,7 +3896,8 @@ final void terminate(int driverErrorCode, String message, Throwable throwable) t * the command to execute */ boolean executeCommand(TDSCommand newCommand) throws SQLServerException { - synchronized (schedulerLock) { + schedulerLock.lock(); + try { ICounter previousCounter = null; /* * Detach (buffer) the response from any previously executing command so that we can execute the new @@ -3925,6 +3993,8 @@ boolean executeCommand(TDSCommand newCommand) throws SQLServerException { } return commandComplete; + } finally { + schedulerLock.unlock(); } } @@ -3946,7 +4016,9 @@ boolean isConnectionDead() throws SQLServerException { } // Only one thread should ever try to perform an idle check on a // disconnected connection at a time. - synchronized (this) { + + lock.lock(); + try { // check again if connection is reset already. if (!idleNetworkTracker.isIdle()) { if (connectionlogger.isLoggable(Level.FINEST)) { @@ -3963,41 +4035,49 @@ boolean isConnectionDead() throws SQLServerException { } return !tdsChannel.networkSocketStillConnected(); + } finally { + lock.unlock(); } } /** * executeCommand without reconnection logic. Only used by the reconnect thread to avoid a lock. */ - synchronized boolean executeReconnectCommand(TDSCommand newCommand) throws SQLServerException { - /* - * Detach (buffer) the response from any previously executing command so that we can execute the new command. - * Note that detaching the response does not process it. Detaching just buffers the response off of the wire to - * clear the TDS channel. - */ - if (null != currentCommand) { - currentCommand.detach(); - currentCommand = null; - } - - /* - * The implementation of this scheduler is pretty simple... Since only one command at a time may use a - * connection (to avoid TDS protocol errors), just synchronize to serialize command execution. - */ - boolean commandComplete = false; + boolean executeReconnectCommand(TDSCommand newCommand) throws SQLServerException { + lock.lock(); try { - commandComplete = newCommand.execute(tdsChannel.getWriter(), tdsChannel.getReader(newCommand)); - } finally { /* - * We should never displace an existing currentCommand assert null == currentCommand; If execution of the - * new command left response bytes on the wire (e.g. a large ResultSet or complex response with multiple - * results) then remember it as the current command so that any subsequent call to executeCommand will - * detach it before executing another new command. + * Detach (buffer) the response from any previously executing command so that we can execute the new command. + * Note that detaching the response does not process it. Detaching just buffers the response off of the wire to + * clear the TDS channel. + */ + if (null != currentCommand) { + currentCommand.detach(); + currentCommand = null; + } + + /* + * The implementation of this scheduler is pretty simple... Since only one command at a time may use a + * connection (to avoid TDS protocol errors), just synchronize to serialize command execution. */ - if (!commandComplete && !isSessionUnAvailable()) - currentCommand = newCommand; + boolean commandComplete = false; + try { + commandComplete = newCommand.execute(tdsChannel.getWriter(), tdsChannel.getReader(newCommand)); + } finally { + /* + * We should never displace an existing currentCommand assert null == currentCommand; If execution of the + * new command left response bytes on the wire (e.g. a large ResultSet or complex response with multiple + * results) then remember it as the current command so that any subsequent call to executeCommand will + * detach it before executing another new command. + */ + if (!commandComplete && !isSessionUnAvailable()) { + currentCommand = newCommand; + } + } + return commandComplete; + } finally { + lock.unlock(); } - return commandComplete; } /* @@ -4440,7 +4520,7 @@ public int getTransactionIsolation() throws SQLServerException { volatile SQLWarning sqlWarnings; /** warnings synchronization object */ - private final Object warningSynchronization = new Object(); + private final Lock warningSynchronization = new ReentrantLock(); // Think about returning a copy when we implement additional warnings. @Override @@ -4454,7 +4534,8 @@ public SQLWarning getWarnings() throws SQLServerException { // Any changes to SQLWarnings should be synchronized. void addWarning(String warningString) { - synchronized (warningSynchronization) { + warningSynchronization.lock(); + try { SQLWarning warning = new SQLWarning(warningString); if (null == sqlWarnings) { @@ -4462,16 +4543,21 @@ void addWarning(String warningString) { } else { sqlWarnings.setNextWarning(warning); } + } finally { + warningSynchronization.unlock(); } } @Override public void clearWarnings() throws SQLServerException { - synchronized (warningSynchronization) { + warningSynchronization.lock(); + try { loggerExternal.entering(loggingClassName, "clearWarnings"); checkClosed(); sqlWarnings = null; loggerExternal.exiting(loggingClassName, "clearWarnings"); + } finally { + warningSynchronization.unlock(); } } @@ -6958,7 +7044,8 @@ public T unwrap(Class iface) throws SQLException { void beginRequestInternal() throws SQLException { loggerExternal.entering(loggingClassName, "beginRequest", this); - synchronized (this) { + lock.lock(); + try { if (!requestStarted) { originalDatabaseAutoCommitMode = databaseAutoCommitMode; originalTransactionIsolationLevel = transactionIsolationLevel; @@ -6977,13 +7064,16 @@ void beginRequestInternal() throws SQLException { originalDelayLoadingLobs = delayLoadingLobs; requestStarted = true; } + } finally { + lock.unlock(); } loggerExternal.exiting(loggingClassName, "beginRequest", this); } void endRequestInternal() throws SQLException { loggerExternal.entering(loggingClassName, "endRequest", this); - synchronized (this) { + lock.lock(); + try { if (requestStarted) { if (!databaseAutoCommitMode) { rollback(); @@ -7036,6 +7126,8 @@ void endRequestInternal() throws SQLException { } requestStarted = false; } + } finally { + lock.unlock(); } loggerExternal.exiting(loggingClassName, "endRequest", this); } @@ -7123,18 +7215,23 @@ static int makeParamName(int nParam, char[] name, int offset) { * should be removed from the pool. */ void notifyPooledConnection(SQLServerException e) { - synchronized (this) { + lock.lock(); + try { if (null != pooledConnectionParent) { pooledConnectionParent.notifyEvent(e); } + } finally { + lock.unlock(); } - } // Detaches this connection from connection pool. void DetachFromPool() { - synchronized (this) { + lock.lock(); + try { pooledConnectionParent = null; + } finally { + lock.unlock(); } } @@ -7291,18 +7388,29 @@ void doSecurityCheck() { * @throws SQLServerException * when an error occurs */ - public static synchronized void setColumnEncryptionKeyCacheTtl(int columnEncryptionKeyCacheTTL, + public static void setColumnEncryptionKeyCacheTtl(int columnEncryptionKeyCacheTTL, TimeUnit unit) throws SQLServerException { - if (columnEncryptionKeyCacheTTL < 0 || unit.equals(TimeUnit.MILLISECONDS) || unit.equals(TimeUnit.MICROSECONDS) - || unit.equals(TimeUnit.NANOSECONDS)) { - throw new SQLServerException(null, SQLServerException.getErrString("R_invalidCEKCacheTtl"), null, 0, false); - } + LOCK.lock(); + try { + if (columnEncryptionKeyCacheTTL < 0 || unit.equals(TimeUnit.MILLISECONDS) || unit.equals( + TimeUnit.MICROSECONDS) || unit.equals(TimeUnit.NANOSECONDS)) { + throw new SQLServerException(null, SQLServerException.getErrString("R_invalidCEKCacheTtl"), null, 0, + false); + } - columnEncryptionKeyCacheTtl = TimeUnit.SECONDS.convert(columnEncryptionKeyCacheTTL, unit); + columnEncryptionKeyCacheTtl = TimeUnit.SECONDS.convert(columnEncryptionKeyCacheTTL, unit); + } finally { + LOCK.unlock(); + } } - static synchronized long getColumnEncryptionKeyCacheTtl() { - return columnEncryptionKeyCacheTtl; + static long getColumnEncryptionKeyCacheTtl() { + LOCK.lock(); + try { + return columnEncryptionKeyCacheTtl; + } finally { + LOCK.unlock(); + } } /** @@ -7659,9 +7767,14 @@ boolean isAzureMI() { * @param st * Statement to add to openStatements */ - final synchronized void addOpenStatement(ISQLServerStatement st) { - if (null != openStatements) { - openStatements.add(st); + final void addOpenStatement(ISQLServerStatement st) { + lock.lock(); + try { + if (null != openStatements) { + openStatements.add(st); + } + } finally { + lock.unlock(); } } @@ -7671,9 +7784,14 @@ final synchronized void addOpenStatement(ISQLServerStatement st) { * @param st * Statement to remove from openStatements */ - final synchronized void removeOpenStatement(ISQLServerStatement st) { - if (null != openStatements) { - openStatements.remove(st); + final void removeOpenStatement(ISQLServerStatement st) { + lock.lock(); + try { + if (null != openStatements) { + openStatements.remove(st); + } + } finally { + lock.unlock(); } } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataTable.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataTable.java index 9f718d400..11beb9da9 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataTable.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataTable.java @@ -9,16 +9,10 @@ import java.text.MessageFormat; import java.time.OffsetDateTime; import java.time.OffsetTime; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; -import java.util.Set; -import java.util.UUID; - +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; /** * Represents the data table for SQL Server. @@ -31,6 +25,7 @@ public final class SQLServerDataTable { Set columnNames = null; Map rows = null; private String tvpName = null; + private final Lock lock = new ReentrantLock(); /** * The constant in the Java programming language, sometimes referred to as a type code, that identifies the type @@ -49,12 +44,17 @@ public SQLServerDataTable() throws SQLServerException { /** * Clears this data table. */ - public synchronized void clear() { - rowCount = 0; - columnCount = 0; - columnMetadata.clear(); - columnNames.clear(); - rows.clear(); + public void clear() { + lock.lock(); + try { + rowCount = 0; + columnCount = 0; + columnMetadata.clear(); + columnNames.clear(); + rows.clear(); + } finally { + lock.unlock(); + } } /** @@ -62,11 +62,16 @@ public synchronized void clear() { * * @return an iterator on the rows of the data table. */ - public synchronized Iterator> getIterator() { - if ((null != rows) && (null != rows.entrySet())) { - return rows.entrySet().iterator(); + public Iterator> getIterator() { + lock.lock(); + try { + if (null != rows) { + return rows.entrySet().iterator(); + } + return null; + } finally { + lock.unlock(); } - return null; } /** @@ -79,10 +84,15 @@ public synchronized Iterator> getIterator() { * @throws SQLServerException * when an error occurs */ - public synchronized void addColumnMetadata(String columnName, int sqlType) throws SQLServerException { - // column names must be unique - Util.checkDuplicateColumnName(columnName, columnNames); - columnMetadata.put(columnCount++, new SQLServerDataColumn(columnName, sqlType)); + public void addColumnMetadata(String columnName, int sqlType) throws SQLServerException { + lock.lock(); + try { + // column names must be unique + Util.checkDuplicateColumnName(columnName, columnNames); + columnMetadata.put(columnCount++, new SQLServerDataColumn(columnName, sqlType)); + } finally { + lock.unlock(); + } } /** @@ -93,10 +103,15 @@ public synchronized void addColumnMetadata(String columnName, int sqlType) throw * @throws SQLServerException * when an error occurs */ - public synchronized void addColumnMetadata(SQLServerDataColumn column) throws SQLServerException { - // column names must be unique - Util.checkDuplicateColumnName(column.columnName, columnNames); - columnMetadata.put(columnCount++, column); + public void addColumnMetadata(SQLServerDataColumn column) throws SQLServerException { + lock.lock(); + try { + // column names must be unique + Util.checkDuplicateColumnName(column.columnName, columnNames); + columnMetadata.put(columnCount++, column); + } finally { + lock.unlock(); + } } /** @@ -107,7 +122,8 @@ public synchronized void addColumnMetadata(SQLServerDataColumn column) throws SQ * @throws SQLServerException * when an error occurs */ - public synchronized void addRow(Object... values) throws SQLServerException { + public void addRow(Object... values) throws SQLServerException { + lock.lock(); try { int columnCount = columnMetadata.size(); @@ -136,8 +152,9 @@ public synchronized void addRow(Object... values) throws SQLServerException { throw new SQLServerException(SQLServerException.getErrString("R_TVPInvalidColumnValue"), e); } catch (ClassCastException e) { throw new SQLServerException(SQLServerException.getErrString("R_TVPInvalidColumnValue"), e); + } finally { + lock.unlock(); } - } /** @@ -313,8 +330,13 @@ private void internalAddrow(JDBCType jdbcType, Object val, Object[] rowValues, * * @return Map */ - public synchronized Map getColumnMetadata() { - return columnMetadata; + public Map getColumnMetadata() { + lock.lock(); + try { + return columnMetadata; + } finally { + lock.unlock(); + } } /** diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDatabaseMetaData.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDatabaseMetaData.java index 5cc2e2fd3..efa2f49e5 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDatabaseMetaData.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDatabaseMetaData.java @@ -6,22 +6,13 @@ package com.microsoft.sqlserver.jdbc; import java.io.Serializable; -import java.sql.CallableStatement; -import java.sql.Connection; -import java.sql.DriverPropertyInfo; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.RowIdLifetime; -import java.sql.SQLException; -import java.sql.SQLTimeoutException; +import java.sql.*; import java.text.MessageFormat; -import java.util.EnumMap; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; -import java.util.Properties; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; @@ -272,7 +263,8 @@ private void checkClosed() throws SQLServerException { // Use LinkedHashMap to force retrieve elements in order they were inserted private static LinkedHashMap getColumnsDWColumns = null; - private static LinkedHashMap getImportedKeysDWColumns = null; + private static volatile LinkedHashMap getImportedKeysDWColumns; + private static final Lock LOCK = new ReentrantLock(); /** * Returns the result from a simple query. This is to be used only for internal queries without any user input. @@ -692,7 +684,8 @@ public java.sql.ResultSet getColumns(String catalog, String schema, String table * when user provides a different catalog than the one they're currently connected to. Will throw exception * when it's different and do nothing if it's the same/null. */ - synchronized (SQLServerDatabaseMetaData.class) { + LOCK.lock(); + try { if (null == getColumnsDWColumns) { getColumnsDWColumns = new LinkedHashMap<>(); getColumnsDWColumns.put(1, TABLE_CAT); @@ -732,6 +725,8 @@ public java.sql.ResultSet getColumns(String catalog, String schema, String table getColumnsDWColumns.put(27, SS_XML_SCHEMACOLLECTION_SCHEMA_NAME); getColumnsDWColumns.put(28, SS_XML_SCHEMACOLLECTION_NAME); } + } finally { + LOCK.unlock(); } try (PreparedStatement storedProcPstmt = this.connection @@ -1087,23 +1082,31 @@ private ResultSet executeSPFkeys(String[] procParams) throws SQLException, SQLTi ResultSet userRs = null; PreparedStatement pstmt = null; StringBuilder azureDwSelectBuilder = new StringBuilder(); - synchronized (SQLServerDatabaseMetaData.class) { - if (null == getImportedKeysDWColumns) { - getImportedKeysDWColumns = new LinkedHashMap<>(); - getImportedKeysDWColumns.put(1, PKTABLE_CAT); - getImportedKeysDWColumns.put(2, PKTABLE_SCHEM); - getImportedKeysDWColumns.put(3, PKTABLE_NAME); - getImportedKeysDWColumns.put(4, PKCOLUMN_NAME); - getImportedKeysDWColumns.put(5, FKTABLE_CAT); - getImportedKeysDWColumns.put(6, FKTABLE_SCHEM); - getImportedKeysDWColumns.put(7, FKTABLE_NAME); - getImportedKeysDWColumns.put(8, FKCOLUMN_NAME); - getImportedKeysDWColumns.put(9, KEY_SEQ); - getImportedKeysDWColumns.put(10, UPDATE_RULE); - getImportedKeysDWColumns.put(11, DELETE_RULE); - getImportedKeysDWColumns.put(12, FK_NAME); - getImportedKeysDWColumns.put(13, PK_NAME); - getImportedKeysDWColumns.put(14, DEFERRABILITY); + + LinkedHashMap importedKeysDWColumns = getImportedKeysDWColumns; + if (null == importedKeysDWColumns) { + LOCK.lock(); + try { + importedKeysDWColumns = getImportedKeysDWColumns; + if (null == importedKeysDWColumns) { + getImportedKeysDWColumns = importedKeysDWColumns = new LinkedHashMap<>(14, 1.0F); + importedKeysDWColumns.put(1, PKTABLE_CAT); + importedKeysDWColumns.put(2, PKTABLE_SCHEM); + importedKeysDWColumns.put(3, PKTABLE_NAME); + importedKeysDWColumns.put(4, PKCOLUMN_NAME); + importedKeysDWColumns.put(5, FKTABLE_CAT); + importedKeysDWColumns.put(6, FKTABLE_SCHEM); + importedKeysDWColumns.put(7, FKTABLE_NAME); + importedKeysDWColumns.put(8, FKCOLUMN_NAME); + importedKeysDWColumns.put(9, KEY_SEQ); + importedKeysDWColumns.put(10, UPDATE_RULE); + importedKeysDWColumns.put(11, DELETE_RULE); + importedKeysDWColumns.put(12, FK_NAME); + importedKeysDWColumns.put(13, PK_NAME); + importedKeysDWColumns.put(14, DEFERRABILITY); + } + } finally { + LOCK.unlock(); } } azureDwSelectBuilder.append(generateAzureDWEmptyRS(getImportedKeysDWColumns)); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPooledConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPooledConnection.java index c022210a0..c286ad1b7 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPooledConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPooledConnection.java @@ -5,19 +5,19 @@ package com.microsoft.sqlserver.jdbc; +import javax.sql.ConnectionEvent; +import javax.sql.ConnectionEventListener; +import javax.sql.PooledConnection; +import javax.sql.StatementEventListener; import java.io.Serializable; import java.sql.Connection; import java.sql.SQLException; import java.util.Vector; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; -import javax.sql.ConnectionEvent; -import javax.sql.ConnectionEventListener; -import javax.sql.PooledConnection; -import javax.sql.StatementEventListener; - - /** * Represents a physical database connection in a connection pool. If provides methods for the connection pool manager * to manage the connection pool. Applications typically do not instantiate these connections directly. @@ -52,6 +52,8 @@ public class SQLServerPooledConnection implements PooledConnection, Serializable // Unique id generator for each PooledConnection instance (used for logging). static private final AtomicInteger basePooledConnectionID = new AtomicInteger(0); + private final Lock lock = new ReentrantLock(); + private final Lock listenersLock = new ReentrantLock(); SQLServerPooledConnection(SQLServerDataSource ds, String user, String password) throws SQLException { listeners = new Vector<>(); @@ -104,7 +106,8 @@ private SQLServerConnection createNewConnection() throws SQLException { public Connection getConnection() throws SQLException { if (pcLogger.isLoggable(Level.FINER)) pcLogger.finer(toString() + " user:(default)."); - synchronized (this) { + lock.lock(); + try { // If physical connection is closed, throw exception per spec, this PooledConnection is dead. if (physicalConnection == null) { SQLServerException.makeFromDriverError(null, this, @@ -150,6 +153,8 @@ public Connection getConnection() throws SQLException { pcLogger.fine(toString() + " proxy " + lastProxyConnection.toString() + " is returned."); return lastProxyConnection; + } finally { + lock.unlock(); } } @@ -164,16 +169,20 @@ void notifyEvent(SQLServerException e) { // close the proxy on fatal error event. Note exception is null then the event comes from the proxy close. if (null != e) { - synchronized (this) { + lock.lock(); + try { if (null != lastProxyConnection) { lastProxyConnection.internalClose(); lastProxyConnection = null; } + } finally { + lock.unlock(); } } // A connection handle issued from this pooled connection is closing or an error occurred in the connection - synchronized (listeners) { + listenersLock.lock(); + try { for (int i = 0; i < listeners.size(); i++) { ConnectionEventListener listener = listeners.elementAt(i); @@ -191,6 +200,8 @@ void notifyEvent(SQLServerException e) { listener.connectionErrorOccurred(ev); } } + } finally { + listenersLock.unlock(); } } @@ -198,8 +209,11 @@ void notifyEvent(SQLServerException e) { public void addConnectionEventListener(ConnectionEventListener listener) { if (pcLogger.isLoggable(Level.FINER)) pcLogger.finer(toString() + safeCID()); - synchronized (listeners) { + listenersLock.lock(); + try { listeners.add(listener); + } finally { + listenersLock.unlock(); } } @@ -207,29 +221,37 @@ public void addConnectionEventListener(ConnectionEventListener listener) { public void close() throws SQLException { if (pcLogger.isLoggable(Level.FINER)) pcLogger.finer(toString() + " Closing physical connection, " + safeCID()); - synchronized (this) { + lock.lock(); + try { // First close the last proxy if (null != lastProxyConnection) - // use internal close so there wont be an event due to us closing the connection, if not closed already. + // use internal close so there wont be an event due to us closing the connection, if not closed already. lastProxyConnection.internalClose(); if (null != physicalConnection) { physicalConnection.DetachFromPool(); physicalConnection.close(); } physicalConnection = null; + } finally { + lock.unlock(); } - synchronized (listeners) { + listenersLock.lock(); + try { listeners.clear(); + } finally { + listenersLock.unlock(); } - } @Override public void removeConnectionEventListener(ConnectionEventListener listener) { if (pcLogger.isLoggable(Level.FINER)) pcLogger.finer(toString() + safeCID()); - synchronized (listeners) { + listenersLock.lock(); + try { listeners.remove(listener); + } finally { + listenersLock.unlock(); } } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java index 8d3744d27..658832a4b 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java @@ -5,28 +5,20 @@ package com.microsoft.sqlserver.jdbc; -import static com.microsoft.sqlserver.jdbc.SQLServerConnection.getCachedParsedSQL; -import static com.microsoft.sqlserver.jdbc.SQLServerConnection.parseAndCacheSQL; +import com.microsoft.sqlserver.jdbc.SQLServerConnection.CityHash128Key; -import java.sql.BatchUpdateException; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.SQLTimeoutException; -import java.sql.SQLWarning; -import java.sql.Statement; +import java.sql.*; import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; -import java.util.Stack; -import java.util.StringTokenizer; -import java.util.Vector; +import java.util.*; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; import java.util.regex.Matcher; import java.util.regex.Pattern; -import com.microsoft.sqlserver.jdbc.SQLServerConnection.CityHash128Key; +import static com.microsoft.sqlserver.jdbc.SQLServerConnection.getCachedParsedSQL; +import static com.microsoft.sqlserver.jdbc.SQLServerConnection.parseAndCacheSQL; /** @@ -287,20 +279,30 @@ final void executeCommand(TDSCommand newCommand) throws SQLServerException { /** * Increment opened result set counter */ - synchronized void incrResultSetCount() { - resultSetCount++; + void incrResultSetCount() { + lock.lock(); + try { + resultSetCount++; + } finally { + lock.unlock(); + } } /** * Decrement opened result set counter. */ - synchronized void decrResultSetCount() { - resultSetCount--; - assert resultSetCount >= 0; + void decrResultSetCount() { + lock.lock(); + try { + resultSetCount--; + assert resultSetCount >= 0; - // close statement if no more result sets opened - if (isCloseOnCompletion && !(EXECUTE_BATCH == executeMethod && moreResults) && resultSetCount == 0) { - closeInternal(); + // close statement if no more result sets opened + if (isCloseOnCompletion && !(EXECUTE_BATCH == executeMethod && moreResults) && resultSetCount == 0) { + closeInternal(); + } + } finally { + lock.unlock(); } } @@ -2401,6 +2403,7 @@ public final String getResponseBuffering() throws SQLServerException { /** This is a per-statement store provider. */ Map statementColumnEncryptionKeyStoreProviders = new HashMap<>(); + private final Lock lock = new ReentrantLock(); /** * Registers statement-level key store providers, replacing all existing providers. @@ -2410,42 +2413,47 @@ public final String getResponseBuffering() throws SQLServerException { * @throws SQLServerException * when an error occurs */ - public synchronized void registerColumnEncryptionKeyStoreProvidersOnStatement( + public void registerColumnEncryptionKeyStoreProvidersOnStatement( Map clientKeyStoreProviders) throws SQLServerException { loggerExternal.entering(loggingClassName, "registerColumnEncryptionKeyStoreProvidersOnStatement", "Registering Column Encryption Key Store Providers on Statement"); - checkClosed(); - - if (null == clientKeyStoreProviders) { - throw new SQLServerException(null, SQLServerException.getErrString("R_CustomKeyStoreProviderMapNull"), null, - 0, false); - } - - statementColumnEncryptionKeyStoreProviders.clear(); + lock.lock(); + try { + checkClosed(); - for (Map.Entry entry : clientKeyStoreProviders.entrySet()) { - String providerName = entry.getKey(); - if (null == providerName || 0 == providerName.trim().length()) { - throw new SQLServerException(null, SQLServerException.getErrString("R_EmptyCustomKeyStoreProviderName"), + if (null == clientKeyStoreProviders) { + throw new SQLServerException(null, SQLServerException.getErrString("R_CustomKeyStoreProviderMapNull"), null, 0, false); } - // MSSQL_CERTIFICATE_STORE not allowed on statement level - if ((providerName.equalsIgnoreCase(WINDOWS_KEY_STORE_NAME))) { - MessageFormat form = new MessageFormat( - SQLServerException.getErrString("R_InvalidCustomKeyStoreProviderName")); - Object[] msgArgs = {providerName, WINDOWS_KEY_STORE_NAME}; - throw new SQLServerException(null, form.format(msgArgs), null, 0, false); - } + statementColumnEncryptionKeyStoreProviders.clear(); - if (null == entry.getValue()) { - throw new SQLServerException(null, String - .format(SQLServerException.getErrString("R_CustomKeyStoreProviderValueNull"), providerName), - null, 0, false); - } + for (Map.Entry entry : clientKeyStoreProviders.entrySet()) { + String providerName = entry.getKey(); + if (null == providerName || 0 == providerName.trim().length()) { + throw new SQLServerException(null, + SQLServerException.getErrString("R_EmptyCustomKeyStoreProviderName"), null, 0, false); + } - statementColumnEncryptionKeyStoreProviders.put(entry.getKey(), entry.getValue()); + // MSSQL_CERTIFICATE_STORE not allowed on statement level + if ((providerName.equalsIgnoreCase(WINDOWS_KEY_STORE_NAME))) { + MessageFormat form = new MessageFormat( + SQLServerException.getErrString("R_InvalidCustomKeyStoreProviderName")); + Object[] msgArgs = {providerName, WINDOWS_KEY_STORE_NAME}; + throw new SQLServerException(null, form.format(msgArgs), null, 0, false); + } + + if (null == entry.getValue()) { + throw new SQLServerException(null, + String.format(SQLServerException.getErrString("R_CustomKeyStoreProviderValueNull"), + providerName), null, 0, false); + } + + statementColumnEncryptionKeyStoreProviders.put(entry.getKey(), entry.getValue()); + } + } finally { + lock.unlock(); } loggerExternal.exiting(loggingClassName, "registerColumnEncryptionKeyStoreProvidersOnStatement", @@ -2453,36 +2461,50 @@ public synchronized void registerColumnEncryptionKeyStoreProvidersOnStatement( + statementColumnEncryptionKeyStoreProviders.size()); } - synchronized String getAllStatementColumnEncryptionKeyStoreProviders() { - String keyStores = ""; - if (0 != statementColumnEncryptionKeyStoreProviders.size()) - keyStores = statementColumnEncryptionKeyStoreProviders.keySet().toString(); - return keyStores; + String getAllStatementColumnEncryptionKeyStoreProviders() { + lock.lock(); + try { + String keyStores = ""; + if (0 != statementColumnEncryptionKeyStoreProviders.size()) { + keyStores = statementColumnEncryptionKeyStoreProviders.keySet().toString(); + } + return keyStores; + } finally { + lock.unlock(); + } } - synchronized boolean hasColumnEncryptionKeyStoreProvidersRegistered() { - return null != statementColumnEncryptionKeyStoreProviders - && statementColumnEncryptionKeyStoreProviders.size() > 0; + boolean hasColumnEncryptionKeyStoreProvidersRegistered() { + lock.lock(); + try { + return null != statementColumnEncryptionKeyStoreProviders && statementColumnEncryptionKeyStoreProviders.size() > 0; + } finally { + lock.unlock(); + } } - synchronized SQLServerColumnEncryptionKeyStoreProvider getColumnEncryptionKeyStoreProvider( + SQLServerColumnEncryptionKeyStoreProvider getColumnEncryptionKeyStoreProvider( String providerName) throws SQLServerException { - - // Check for a statement-level provider first - if (null != statementColumnEncryptionKeyStoreProviders - && statementColumnEncryptionKeyStoreProviders.size() > 0) { - // If any statement-level providers are registered, we don't fall back to connection-level providers - if (statementColumnEncryptionKeyStoreProviders.containsKey(providerName)) { - return statementColumnEncryptionKeyStoreProviders.get(providerName); - } else { - MessageFormat form = new MessageFormat( - SQLServerException.getErrString("R_UnrecognizedStatementKeyStoreProviderName")); - Object[] msgArgs = {providerName, getAllStatementColumnEncryptionKeyStoreProviders()}; - throw new SQLServerException(form.format(msgArgs), null); + lock.lock(); + try { + // Check for a statement-level provider first + if (null != statementColumnEncryptionKeyStoreProviders + && statementColumnEncryptionKeyStoreProviders.size() > 0) { + // If any statement-level providers are registered, we don't fall back to connection-level providers + if (statementColumnEncryptionKeyStoreProviders.containsKey(providerName)) { + return statementColumnEncryptionKeyStoreProviders.get(providerName); + } else { + MessageFormat form = new MessageFormat( + SQLServerException.getErrString("R_UnrecognizedStatementKeyStoreProviderName")); + Object[] msgArgs = {providerName, getAllStatementColumnEncryptionKeyStoreProviders()}; + throw new SQLServerException(form.format(msgArgs), null); + } } - } - return null; + return null; + } finally { + lock.unlock(); + } } } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSymmetricKeyCache.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSymmetricKeyCache.java index c88490b88..0b8e02f24 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSymmetricKeyCache.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSymmetricKeyCache.java @@ -5,14 +5,14 @@ package com.microsoft.sqlserver.jdbc; -import static java.nio.charset.StandardCharsets.UTF_8; -import static java.util.concurrent.TimeUnit.SECONDS; - import java.text.MessageFormat; import java.time.Duration; import java.util.Base64; import java.util.List; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import static java.nio.charset.StandardCharsets.UTF_8; /** * @@ -20,7 +20,7 @@ * */ final class SQLServerSymmetricKeyCache { - static final Object lock = new Object(); + static final Lock lock = new ReentrantLock(); private final SimpleTtlCache cache; private static final SQLServerSymmetricKeyCache instance = new SQLServerSymmetricKeyCache(); @@ -49,7 +49,8 @@ SimpleTtlCache getCache() { */ SQLServerSymmetricKey getKey(EncryptionKeyInfo keyInfo, SQLServerConnection connection) throws SQLServerException { SQLServerSymmetricKey encryptionKey = null; - synchronized (lock) { + lock.lock(); + try { String serverName = connection.getTrustedServerNameAE(); assert null != serverName : "serverName should not be null in getKey."; @@ -114,7 +115,9 @@ SQLServerSymmetricKey getKey(EncryptionKeyInfo keyInfo, SQLServerConnection conn } else { encryptionKey = cache.get(keyLookupValue); } + return encryptionKey; + } finally { + lock.unlock(); } - return encryptionKey; } } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerXAConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerXAConnection.java index 5f356fc64..2e089cb91 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerXAConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerXAConnection.java @@ -5,15 +5,15 @@ package com.microsoft.sqlserver.jdbc; +import javax.sql.XAConnection; +import javax.transaction.xa.XAResource; import java.sql.SQLException; import java.util.Properties; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; import java.util.logging.Logger; -import javax.sql.XAConnection; -import javax.transaction.xa.XAResource; - - /** * Provides JDBC connections that can participate in distributed (XA) transactions. */ @@ -29,13 +29,16 @@ public final class SQLServerXAConnection extends SQLServerPooledConnection imple * transactional processing to the application. That app server is the one who should restrict commit/rollback on * the connections it issues to applications, not the driver. These instances can and must commit/rollback */ - private SQLServerXAResource XAResource; - - /** physical connection */ + private volatile SQLServerXAResource XAResource; + + /** + * physical connection + */ private SQLServerConnection physicalControlConnection; /** logger */ private Logger xaLogger; + private final Lock lock = new ReentrantLock(); SQLServerXAConnection(SQLServerDataSource ds, String user, String pwd) throws java.sql.SQLException { super(ds, user, pwd); @@ -101,13 +104,23 @@ public final class SQLServerXAConnection extends SQLServerPooledConnection imple } @Override - public synchronized XAResource getXAResource() throws java.sql.SQLException { + public XAResource getXAResource() throws java.sql.SQLException { // All connections handed out from this physical connection have a common XAResource // for transaction control. IE the XAResource is one to one with the physical connection. - - if (XAResource == null) - XAResource = new SQLServerXAResource(getPhysicalConnection(), physicalControlConnection, toString()); - return XAResource; + SQLServerXAResource result = XAResource; + if (result == null) { + lock.lock(); + try { + result = XAResource; + if (result == null) { + XAResource = result = new SQLServerXAResource(getPhysicalConnection(), physicalControlConnection, + toString()); + } + } finally { + lock.unlock(); + } + } + return result; } /** @@ -115,7 +128,8 @@ public synchronized XAResource getXAResource() throws java.sql.SQLException { */ @Override public void close() throws SQLException { - synchronized (this) { + lock.lock(); + try { if (XAResource != null) { XAResource.close(); XAResource = null; @@ -124,6 +138,8 @@ public void close() throws SQLException { physicalControlConnection.close(); physicalControlConnection = null; } + } finally { + lock.unlock(); } super.close(); } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerXAResource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerXAResource.java index 3e6491f71..3e1aae9c2 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerXAResource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerXAResource.java @@ -5,24 +5,19 @@ package com.microsoft.sqlserver.jdbc; -import java.sql.CallableStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.SQLTimeoutException; -import java.sql.Statement; -import java.sql.Types; +import javax.transaction.xa.XAException; +import javax.transaction.xa.XAResource; +import javax.transaction.xa.Xid; +import java.sql.*; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Properties; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; import java.util.logging.Logger; -import javax.transaction.xa.XAException; -import javax.transaction.xa.XAResource; -import javax.transaction.xa.Xid; - - /** * Implements Transaction id used to recover transactions. */ @@ -155,7 +150,7 @@ public final class SQLServerXAResource implements javax.transaction.xa.XAResourc private int architectureMSSQL, architectureOS; private static boolean xaInitDone; - private static final Object xaInitLock; + private static final Lock xaInitLock = new ReentrantLock(); private String sResourceManagerId; private int enlistedTransactionCount; final private Logger xaLogger; @@ -176,9 +171,7 @@ public final class SQLServerXAResource implements javax.transaction.xa.XAResourc * Variable that shows how many times we attempt the recovery, e.g in case of MSDTC restart */ private int recoveryAttempt = 0; - static { - xaInitLock = new Object(); - } + private final Lock lock = new ReentrantLock(); @Override public String toString() { @@ -211,73 +204,89 @@ public String toString() { } - private synchronized SQLServerCallableStatement getXACallableStatementHandle(int number) throws SQLServerException { - assert number >= XA_START && number <= XA_FORGET_EX; - assert number < xaStatements.length; - if (null != xaStatements[number]) - return xaStatements[number]; + private SQLServerCallableStatement getXACallableStatementHandle(int number) throws SQLServerException { + lock.lock(); + try { + assert number >= XA_START && number <= XA_FORGET_EX; + assert number < xaStatements.length; + if (null != xaStatements[number]) + return xaStatements[number]; - CallableStatement CS = null; - - switch (number) { - case SQLServerXAResource.XA_START: - CS = controlConnection.prepareCall( - "{call master..xp_sqljdbc_xa_start(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)}"); - break; - case SQLServerXAResource.XA_END: - CS = controlConnection.prepareCall("{call master..xp_sqljdbc_xa_end(?, ?, ?, ?, ?, ?, ?)}"); - break; - case SQLServerXAResource.XA_PREPARE: - CS = controlConnection.prepareCall("{call master..xp_sqljdbc_xa_prepare(?, ?, ?, ?, ?)}"); - break; - case SQLServerXAResource.XA_COMMIT: - CS = controlConnection.prepareCall("{call master..xp_sqljdbc_xa_commit(?, ?, ?, ?, ?, ?)}"); - break; - case SQLServerXAResource.XA_ROLLBACK: - CS = controlConnection.prepareCall("{call master..xp_sqljdbc_xa_rollback(?, ?, ?, ?, ?)}"); - break; - case SQLServerXAResource.XA_FORGET: - CS = controlConnection.prepareCall("{call master..xp_sqljdbc_xa_forget(?, ?, ?, ?, ?)}"); - break; - case SQLServerXAResource.XA_RECOVER: - CS = controlConnection.prepareCall("{call master..xp_sqljdbc_xa_recover(?, ?, ?, ?)}"); - break; - case SQLServerXAResource.XA_PREPARE_EX: - CS = controlConnection.prepareCall("{call master..xp_sqljdbc_xa_prepare_ex(?, ?, ?, ?, ?, ?)}"); - break; - case SQLServerXAResource.XA_ROLLBACK_EX: - CS = controlConnection.prepareCall("{call master..xp_sqljdbc_xa_rollback_ex(?, ?, ?, ?, ?, ?)}"); - break; - case SQLServerXAResource.XA_FORGET_EX: - CS = controlConnection.prepareCall("{call master..xp_sqljdbc_xa_forget_ex(?, ?, ?, ?, ?, ?)}"); - break; - default: - assert false : "Bad handle request:" + number; - break; - } + CallableStatement CS = null; + + switch (number) { + case SQLServerXAResource.XA_START: + CS = controlConnection.prepareCall( + "{call master..xp_sqljdbc_xa_start(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)}"); + break; + case SQLServerXAResource.XA_END: + CS = controlConnection.prepareCall("{call master..xp_sqljdbc_xa_end(?, ?, ?, ?, ?, ?, ?)}"); + break; + case SQLServerXAResource.XA_PREPARE: + CS = controlConnection.prepareCall("{call master..xp_sqljdbc_xa_prepare(?, ?, ?, ?, ?)}"); + break; + case SQLServerXAResource.XA_COMMIT: + CS = controlConnection.prepareCall("{call master..xp_sqljdbc_xa_commit(?, ?, ?, ?, ?, ?)}"); + break; + case SQLServerXAResource.XA_ROLLBACK: + CS = controlConnection.prepareCall("{call master..xp_sqljdbc_xa_rollback(?, ?, ?, ?, ?)}"); + break; + case SQLServerXAResource.XA_FORGET: + CS = controlConnection.prepareCall("{call master..xp_sqljdbc_xa_forget(?, ?, ?, ?, ?)}"); + break; + case SQLServerXAResource.XA_RECOVER: + CS = controlConnection.prepareCall("{call master..xp_sqljdbc_xa_recover(?, ?, ?, ?)}"); + break; + case SQLServerXAResource.XA_PREPARE_EX: + CS = controlConnection.prepareCall("{call master..xp_sqljdbc_xa_prepare_ex(?, ?, ?, ?, ?, ?)}"); + break; + case SQLServerXAResource.XA_ROLLBACK_EX: + CS = controlConnection.prepareCall("{call master..xp_sqljdbc_xa_rollback_ex(?, ?, ?, ?, ?, ?)}"); + break; + case SQLServerXAResource.XA_FORGET_EX: + CS = controlConnection.prepareCall("{call master..xp_sqljdbc_xa_forget_ex(?, ?, ?, ?, ?, ?)}"); + break; + default: + assert false : "Bad handle request:" + number; + break; + } - xaStatements[number] = (SQLServerCallableStatement) CS; - return xaStatements[number]; + xaStatements[number] = (SQLServerCallableStatement) CS; + return xaStatements[number]; + } finally { + lock.unlock(); + } } - private synchronized void closeXAStatements() throws SQLServerException { - for (int i = 0; i < xaStatements.length; i++) - if (null != xaStatements[i]) { - xaStatements[i].close(); - xaStatements[i] = null; + private void closeXAStatements() throws SQLServerException { + lock.lock(); + try { + for (int i = 0; i < xaStatements.length; i++) { + if (null != xaStatements[i]) { + xaStatements[i].close(); + xaStatements[i] = null; + } } + } finally { + lock.unlock(); + } } - final synchronized void close() throws SQLServerException { + final void close() throws SQLServerException { + lock.lock(); try { - closeXAStatements(); - } catch (Exception e) { - if (xaLogger.isLoggable(Level.WARNING)) - xaLogger.warning(toString() + "Closing exception ignored: " + e); - } + try { + closeXAStatements(); + } catch (Exception e) { + if (xaLogger.isLoggable(Level.WARNING)) + xaLogger.warning(toString() + "Closing exception ignored: " + e); + } - if (null != controlConnection) - controlConnection.close(); + if (null != controlConnection) + controlConnection.close(); + } finally { + lock.unlock(); + } } // Returns displayable representation of XID flags for logging purposes. @@ -387,10 +396,12 @@ private XAReturnValue DTC_XA_Interface(int nType, Xid xid, int xaFlags) throws X SQLServerCallableStatement cs = null; try { - synchronized (this) { + lock.lock(); + try { if (!xaInitDone) { try { - synchronized (xaInitLock) { + xaInitLock.lock(); + try { SQLServerCallableStatement initCS = null; initCS = (SQLServerCallableStatement) controlConnection @@ -441,6 +452,8 @@ private XAReturnValue DTC_XA_Interface(int nType, Xid xid, int xaFlags) throws X xaLogger.finer(toString() + " exception:" + xex); throw xex; } + } finally { + xaInitLock.unlock(); } } catch (SQLServerException e1) { MessageFormat form = new MessageFormat( @@ -452,6 +465,8 @@ private XAReturnValue DTC_XA_Interface(int nType, Xid xid, int xaFlags) throws X } xaInitDone = true; } + } finally { + lock.unlock(); } switch (nType) { diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SharedTimer.java b/src/main/java/com/microsoft/sqlserver/jdbc/SharedTimer.java index 3641ffada..d761e584e 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SharedTimer.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SharedTimer.java @@ -10,7 +10,8 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; - +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; /** * Provides timeout handling for basic and bulk TDS commands to use a shared timer class. SharedTimer provides a static @@ -46,7 +47,7 @@ class SharedTimer implements Serializable { static final String CORE_THREAD_PREFIX = "mssql-jdbc-shared-timer-core-"; private static final AtomicLong CORE_THREAD_COUNTER = new AtomicLong(); - private static final Object lock = new Object(); + private static final Lock LOCK = new ReentrantLock(); /** * Unique ID of this SharedTimer */ @@ -85,7 +86,8 @@ static boolean isRunning() { * If the reference count reaches zero then the underlying executor will be shutdown so that its thread stops. */ public void removeRef() { - synchronized (lock) { + LOCK.lock(); + try { if (refCount.get() <= 0) { throw new IllegalStateException("removeRef() called more than actual references"); } @@ -95,6 +97,8 @@ public void removeRef() { executor = null; instance = null; } + } finally { + LOCK.unlock(); } } @@ -106,13 +110,16 @@ public void removeRef() { * When the caller is finished with the SharedTimer it must be released via {@link#removeRef} */ public static SharedTimer getTimer() { - synchronized (lock) { + LOCK.lock(); + try { if (instance == null) { // No shared object exists so create a new one instance = new SharedTimer(); } instance.refCount.getAndIncrement(); return instance; + } finally { + LOCK.unlock(); } } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Util.java b/src/main/java/com/microsoft/sqlserver/jdbc/Util.java index ca91b2a82..74b0059f3 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Util.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Util.java @@ -13,11 +13,9 @@ import java.net.UnknownHostException; import java.text.DecimalFormat; import java.text.MessageFormat; -import java.util.Date; -import java.util.Locale; -import java.util.Properties; -import java.util.Set; -import java.util.UUID; +import java.util.*; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; import java.util.logging.LogManager; import java.util.logging.Logger; @@ -37,6 +35,7 @@ final class Util { // The JRE is identified by the string below so that the driver can make // any vendor or version specific decisions static final String SYSTEM_JRE = System.getProperty("java.vendor") + " " + System.getProperty("java.version"); + private static final Lock LOCK = new ReentrantLock(); static boolean isIBM() { return SYSTEM_JRE.startsWith("IBM"); @@ -952,27 +951,32 @@ else if (("" + value).contains("E")) { // If the token is expiring within the next 45 mins, try to fetch a new token if there is no thread already doing // it. // If a thread is already doing the refresh, just use the existing token and proceed. - static synchronized boolean checkIfNeedNewAccessToken(SQLServerConnection connection, Date accessTokenExpireDate) { - Date now = new Date(); + static boolean checkIfNeedNewAccessToken(SQLServerConnection connection, Date accessTokenExpireDate) { + LOCK.lock(); + try { + Date now = new Date(); - // if the token's expiration is within the next 45 mins - // 45 mins * 60 sec/min * 1000 millisec/sec - if ((accessTokenExpireDate.getTime() - now.getTime()) < (45 * 60 * 1000)) { + // if the token's expiration is within the next 45 mins + // 45 mins * 60 sec/min * 1000 millisec/sec + if ((accessTokenExpireDate.getTime() - now.getTime()) < (45 * 60 * 1000)) { - // within the next 10 mins - if ((accessTokenExpireDate.getTime() - now.getTime()) < (10 * 60 * 1000)) { - return true; - } else { - // check if another thread is already updating the access token - if (connection.attemptRefreshTokenLocked) { - return false; - } else { - connection.attemptRefreshTokenLocked = true; + // within the next 10 mins + if ((accessTokenExpireDate.getTime() - now.getTime()) < (10 * 60 * 1000)) { return true; + } else { + // check if another thread is already updating the access token + if (connection.attemptRefreshTokenLocked) { + return false; + } else { + connection.attemptRefreshTokenLocked = true; + return true; + } } } + return false; + } finally { + LOCK.unlock(); } - return false; } @SuppressWarnings("unchecked") From b75dd6e2574553587fa8c463651a6c5306fd81fb Mon Sep 17 00:00:00 2001 From: jono-coder <114485052+jono-coder@users.noreply.github.com> Date: Wed, 5 Oct 2022 15:13:33 +0100 Subject: [PATCH 3/6] prefer double-checked locking --- .../microsoft/sqlserver/jdbc/SharedTimer.java | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SharedTimer.java b/src/main/java/com/microsoft/sqlserver/jdbc/SharedTimer.java index d761e584e..8a1f82e00 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SharedTimer.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SharedTimer.java @@ -110,17 +110,20 @@ public void removeRef() { * When the caller is finished with the SharedTimer it must be released via {@link#removeRef} */ public static SharedTimer getTimer() { - LOCK.lock(); - try { - if (instance == null) { - // No shared object exists so create a new one - instance = new SharedTimer(); + SharedTimer result = instance; + if (result == null) { + LOCK.lock(); + try { + result = instance; + if (result == null) { + instance = result = new SharedTimer(); + } + } finally { + LOCK.unlock(); } - instance.refCount.getAndIncrement(); - return instance; - } finally { - LOCK.unlock(); } + result.refCount.getAndIncrement(); + return result; } /** From e8174f40ce27d40b1da85b0b1540a95cceef9ee4 Mon Sep 17 00:00:00 2001 From: jono-coder <114485052+jono-coder@users.noreply.github.com> Date: Thu, 6 Oct 2022 16:17:37 +0100 Subject: [PATCH 4/6] use AtomicLong instead of a Lock --- .../microsoft/sqlserver/jdbc/IOBuffer.java | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java index bdc29df64..9353509b0 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java @@ -37,6 +37,7 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -2038,7 +2039,7 @@ else if (null != (trustStoreFileName = System.getProperty("javax.net.ssl.trustSt /** * Attempts to poll the input stream to see if the network socket is still connected. - * + * * @return */ final Boolean networkSocketStillConnected() { @@ -3173,8 +3174,7 @@ final class SocketConnector implements Runnable { // a counter used to give unique IDs to each connector thread. // this will have the id of the thread that was last created. - private static long lastThreadID = 0; - private static final Lock LOCK = new ReentrantLock(); + private static final AtomicLong lastThreadID = new AtomicLong(); /** * Constructs a new SocketConnector object with the associated socket and socketFinder @@ -3233,19 +3233,15 @@ public String toString() { * Generates the next unique thread id. */ private static long nextThreadID() { - LOCK.lock(); - try { - if (lastThreadID == Long.MAX_VALUE) { + return lastThreadID.updateAndGet(threadId -> { + if (threadId == Long.MAX_VALUE) { if (logger.isLoggable(Level.FINER)) logger.finer("Resetting the Id count"); - lastThreadID = 1; + return 1; } else { - lastThreadID++; + return threadId + 1; } - return lastThreadID; - } finally { - LOCK.unlock(); - } + }); } } From 1cb2c3f5fc070ccb683b2904a70f4bc97d3908ba Mon Sep 17 00:00:00 2001 From: Jono Kimber Date: Thu, 13 Oct 2022 12:36:42 +0100 Subject: [PATCH 5/6] reverted single class imports --- .../microsoft/sqlserver/jdbc/IOBuffer.java | 52 +++++++++++++++--- .../sqlserver/jdbc/SQLServerConnection.java | 53 ++++++++++++++----- .../sqlserver/jdbc/SQLServerDataTable.java | 9 +++- .../jdbc/SQLServerDatabaseMetaData.java | 15 +++++- .../sqlserver/jdbc/SQLServerStatement.java | 20 +++++-- .../sqlserver/jdbc/SQLServerXAResource.java | 15 ++++-- .../com/microsoft/sqlserver/jdbc/Util.java | 6 ++- 7 files changed, 137 insertions(+), 33 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java index 9353509b0..60663edbd 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java @@ -5,16 +5,28 @@ package com.microsoft.sqlserver.jdbc; -import com.microsoft.sqlserver.jdbc.SQLServerConnection.FedAuthTokenCommand; -import com.microsoft.sqlserver.jdbc.dataclassification.SensitivityClassification; - -import javax.net.SocketFactory; -import javax.net.ssl.*; -import java.io.*; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.Serializable; +import java.io.UnsupportedEncodingException; import java.math.BigDecimal; import java.math.BigInteger; import java.math.RoundingMode; -import java.net.*; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketException; +import java.net.SocketOption; +import java.net.SocketTimeoutException; import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -30,8 +42,19 @@ import java.time.LocalDate; import java.time.OffsetDateTime; import java.time.OffsetTime; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.Iterator; +import java.util.LinkedList; +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; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; @@ -44,9 +67,22 @@ import java.util.logging.Level; import java.util.logging.Logger; +import javax.net.SocketFactory; +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; + +import com.microsoft.sqlserver.jdbc.SQLServerConnection.FedAuthTokenCommand; +import com.microsoft.sqlserver.jdbc.dataclassification.SensitivityClassification; + /** * ExtendedSocketOptions provides methods to keep track of keep alive and socket information. + * */ final class ExtendedSocketOptions { private static class ExtSocketOption implements SocketOption { diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index 4605d2021..be09b2b46 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -5,22 +5,41 @@ package com.microsoft.sqlserver.jdbc; -import com.microsoft.sqlserver.jdbc.SQLServerError.TransientError; -import mssql.googlecode.cityhash.CityHash; -import mssql.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap; -import mssql.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap.Builder; -import mssql.googlecode.concurrentlinkedhashmap.EvictionListener; -import org.ietf.jgss.GSSCredential; +import static java.nio.charset.StandardCharsets.UTF_16LE; -import javax.sql.XAConnection; import java.io.IOException; import java.io.Serializable; -import java.net.*; -import java.sql.*; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLClientInfoException; +import java.sql.SQLException; +import java.sql.SQLPermission; +import java.sql.SQLWarning; +import java.sql.SQLXML; +import java.sql.Savepoint; +import java.sql.Statement; import java.text.MessageFormat; import java.time.Duration; import java.time.Instant; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Executor; @@ -30,7 +49,16 @@ import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; -import static java.nio.charset.StandardCharsets.UTF_16LE; +import javax.sql.XAConnection; + +import org.ietf.jgss.GSSCredential; + +import com.microsoft.sqlserver.jdbc.SQLServerError.TransientError; + +import mssql.googlecode.cityhash.CityHash; +import mssql.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap; +import mssql.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap.Builder; +import mssql.googlecode.concurrentlinkedhashmap.EvictionListener; /** @@ -1099,7 +1127,8 @@ SQLServerColumnEncryptionKeyStoreProvider getColumnEncryptionKeyStoreProviderOnC lock.lock(); try { // Check for a connection-level provider first - if (null != connectionColumnEncryptionKeyStoreProvider && connectionColumnEncryptionKeyStoreProvider.size() > 0) { + if (null != connectionColumnEncryptionKeyStoreProvider + && connectionColumnEncryptionKeyStoreProvider.size() > 0) { // If any connection-level providers are registered, we don't fall back to system/global providers if (connectionColumnEncryptionKeyStoreProvider.containsKey(providerName)) { return connectionColumnEncryptionKeyStoreProvider.get(providerName); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataTable.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataTable.java index 11beb9da9..87564204a 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataTable.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataTable.java @@ -9,8 +9,15 @@ import java.text.MessageFormat; import java.time.OffsetDateTime; import java.time.OffsetTime; -import java.util.*; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; import java.util.Map.Entry; +import java.util.Set; +import java.util.UUID; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDatabaseMetaData.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDatabaseMetaData.java index efa2f49e5..8e257ca58 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDatabaseMetaData.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDatabaseMetaData.java @@ -6,10 +6,21 @@ package com.microsoft.sqlserver.jdbc; import java.io.Serializable; -import java.sql.*; +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.DriverPropertyInfo; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.RowIdLifetime; +import java.sql.SQLException; +import java.sql.SQLTimeoutException; import java.text.MessageFormat; -import java.util.*; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; import java.util.Map.Entry; +import java.util.Properties; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java index 658832a4b..515d43bdf 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java @@ -5,11 +5,22 @@ package com.microsoft.sqlserver.jdbc; -import com.microsoft.sqlserver.jdbc.SQLServerConnection.CityHash128Key; +import static com.microsoft.sqlserver.jdbc.SQLServerConnection.getCachedParsedSQL; +import static com.microsoft.sqlserver.jdbc.SQLServerConnection.parseAndCacheSQL; -import java.sql.*; +import java.sql.BatchUpdateException; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLTimeoutException; +import java.sql.SQLWarning; +import java.sql.Statement; import java.text.MessageFormat; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.Stack; +import java.util.StringTokenizer; +import java.util.Vector; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -17,8 +28,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import static com.microsoft.sqlserver.jdbc.SQLServerConnection.getCachedParsedSQL; -import static com.microsoft.sqlserver.jdbc.SQLServerConnection.parseAndCacheSQL; +import com.microsoft.sqlserver.jdbc.SQLServerConnection.CityHash128Key; /** diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerXAResource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerXAResource.java index 3e1aae9c2..3d26b5a3f 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerXAResource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerXAResource.java @@ -5,10 +5,12 @@ package com.microsoft.sqlserver.jdbc; -import javax.transaction.xa.XAException; -import javax.transaction.xa.XAResource; -import javax.transaction.xa.Xid; -import java.sql.*; +import java.sql.CallableStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLTimeoutException; +import java.sql.Statement; +import java.sql.Types; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Properties; @@ -18,6 +20,11 @@ import java.util.logging.Level; import java.util.logging.Logger; +import javax.transaction.xa.XAException; +import javax.transaction.xa.XAResource; +import javax.transaction.xa.Xid; + + /** * Implements Transaction id used to recover transactions. */ diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Util.java b/src/main/java/com/microsoft/sqlserver/jdbc/Util.java index 74b0059f3..7ec709ea4 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Util.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Util.java @@ -13,7 +13,11 @@ import java.net.UnknownHostException; import java.text.DecimalFormat; import java.text.MessageFormat; -import java.util.*; +import java.util.Date; +import java.util.Locale; +import java.util.Properties; +import java.util.Set; +import java.util.UUID; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; From 36e9f96d344b457267e3da3a62ae299a241987d5 Mon Sep 17 00:00:00 2001 From: Jono Kimber Date: Fri, 14 Oct 2022 09:09:08 +0100 Subject: [PATCH 6/6] revert formatting --- .../java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java | 3 ++- .../com/microsoft/sqlserver/jdbc/SQLServerXAConnection.java | 4 +--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java index 515d43bdf..4975341fa 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java @@ -2487,7 +2487,8 @@ String getAllStatementColumnEncryptionKeyStoreProviders() { boolean hasColumnEncryptionKeyStoreProvidersRegistered() { lock.lock(); try { - return null != statementColumnEncryptionKeyStoreProviders && statementColumnEncryptionKeyStoreProviders.size() > 0; + return null != statementColumnEncryptionKeyStoreProviders + && statementColumnEncryptionKeyStoreProviders.size() > 0; } finally { lock.unlock(); } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerXAConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerXAConnection.java index 2e089cb91..58d156b98 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerXAConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerXAConnection.java @@ -31,9 +31,7 @@ public final class SQLServerXAConnection extends SQLServerPooledConnection imple */ private volatile SQLServerXAResource XAResource; - /** - * physical connection - */ + /** physical connection */ private SQLServerConnection physicalControlConnection; /** logger */