diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5cc432412..063fd44d5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
### :magic_wand: Added
- Logic and a connection property to enable driver failover when network exceptions occur in the connect pipeline (PR #1099)[https://github.com/aws/aws-advanced-jdbc-wrapper/pull/1099]
+- A new reworked and re-architected failover plugin (PR #1089)[https://github.com/aws/aws-advanced-jdbc-wrapper/pull/1089]
+- Virtual Threading support (PR #1120)[https://github.com/aws/aws-advanced-jdbc-wrapper/pull/1120]
## [2.3.9] - 2024-08-09
@@ -308,7 +310,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- Lock initialization of `AuroraHostListProvider` ([PR #347](https://github.com/awslabs/aws-advanced-jdbc-wrapper/pull/347)).
- Optimized thread locks and expiring cache for the Enhanced Monitoring Plugin ([PR #365](https://github.com/awslabs/aws-advanced-jdbc-wrapper/pull/365)).
- Updated Hibernate sample code to reflect changes in the wrapper source code ([PR #368](https://github.com/awslabs/aws-advanced-jdbc-wrapper/pull/368)).
-- Updated KnownLimitations.md to reflect that Amazon RDS Blue/Green Deployments are not supported. See [Amazon RDS Blue/Green Deployments](./docs/KnownLimitations.md#amazon-rds-blue-green-deployments).
+- Updated KnownLimitations.md to reflect that Amazon RDS Blue/Green Deployments are not supported. See [Amazon RDS Blue/Green Deployments](./docs/README.md#amazon-rds-bluegreen-deployments).
## [1.0.1] - 2023-01-30
### :magic_wand: Added
diff --git a/README.md b/README.md
index d0b5a8474..2e6842321 100644
--- a/README.md
+++ b/README.md
@@ -66,40 +66,40 @@ You can find our driver by searching in The Central Repository with GroupId and
## Properties
-| Parameter | Reference | Documentation Link |
-|----------------------------------------|:-------------------------------------------------------------------------------------------------------:|:-----------------------------------------------------------------------------------------------------------------------------:|
-| `wrapperDialect` | `DialectManager.DIALECT` | [Dialects](/docs/using-the-jdbc-driver/DatabaseDialects.md), and whether you should include it. |
-| `wrapperPlugins` | `PropertyDefinition.PLUGINS` | |
-| `secretsManagerSecretId` | `AwsSecretsManagerConnectionPlugin.SECRET_ID_PROPERTY` | [SecretsManagerPlugin](./docs/using-the-jdbc-driver/using-plugins/UsingTheAwsSecretsManagerPlugin.md) |
-| `secretsManagerRegion` | `AwsSecretsManagerConnectionPlugin.REGION_PROPERTY` | [SecretsManagerPlugin](./docs/using-the-jdbc-driver/using-plugins/UsingTheAwsSecretsManagerPlugin.md) |
-| `wrapperDriverName` | `DriverMetaDataConnectionPlugin.WRAPPER_DRIVER_NAME` | [DriverMetaDataConnectionPlugin](./docs/using-the-jdbc-driver/using-plugins/UsingTheDriverMetadataConnectionPlugin.md) |
-| `failoverMode` | `FailoverConnectionPlugin.FAILOVER_MODE` | [FailoverPlugin](./docs/using-the-jdbc-driver/using-plugins/UsingTheFailoverPlugin.md) |
-| `clusterInstanceHostPattern` | `AuroraHostListProvider.CLUSTER_INSTANCE_HOST_PATTERN` | [FailoverPlugin](./docs/using-the-jdbc-driver/using-plugins/UsingTheFailoverPlugin.md) |
-| `enableClusterAwareFailover` | `FailoverConnectionPlugin.ENABLE_CLUSTER_AWARE_FAILOVER` | [FailoverPlugin](./docs/using-the-jdbc-driver/using-plugins/UsingTheFailoverPlugin.md) |
-| `failoverClusterTopologyRefreshRateMs` | `FailoverConnectionPlugin.FAILOVER_CLUSTER_TOPOLOGY_REFRESH_RATE_MS` | [FailoverPlugin](./docs/using-the-jdbc-driver/using-plugins/UsingTheFailoverPlugin.md) |
-| `failoverReaderConnectTimeoutMs` | `FailoverConnectionPlugin.FAILOVER_READER_CONNECT_TIMEOUT_MS` | [FailoverPlugin](./docs/using-the-jdbc-driver/using-plugins/UsingTheFailoverPlugin.md) |
-| `failoverTimeoutMs` | `FailoverConnectionPlugin.FAILOVER_TIMEOUT_MS` | [FailoverPlugin](./docs/using-the-jdbc-driver/using-plugins/UsingTheFailoverPlugin.md) |
-| `failoverWriterReconnectIntervalMs` | `FailoverConnectionPlugin.FAILOVER_WRITER_RECONNECT_INTERVAL_MS` | [FailoverPlugin](./docs/using-the-jdbc-driver/using-plugins/UsingTheFailoverPlugin.md) |
-| `clusterTopologyHighRefreshRateMs` | `MonitoringRdsHostListProvider.CLUSTER_TOPOLOGY_HIGH_REFRESH_RATE_MS` | [FailoverPlugin v2](./docs/using-the-jdbc-driver/using-plugins/UsingTheFailover2Plugin.md) |
-| `failoverReaderHostSelectorStrategy` | `software.amazon.jdbc.plugin.failover2.`
`FailoverConnectionPlugin.FAILOVER_READER_HOST_SELECTOR_STRATEGY` | [FailoverPlugin v2](./docs/using-the-jdbc-driver/using-plugins/UsingTheFailover2Plugin.md) |
-| `failureDetectionCount` | `HostMonitoringConnectionPlugin.FAILURE_DETECTION_COUNT` | [HostMonitoringPlugin](./docs/using-the-jdbc-driver/using-plugins/UsingTheHostMonitoringPlugin.md) |
-| `failureDetectionEnabled` | `HostMonitoringConnectionPlugin.FAILURE_DETECTION_ENABLED` | [HostMonitoringPlugin](./docs/using-the-jdbc-driver/using-plugins/UsingTheHostMonitoringPlugin.md) |
-| `failureDetectionInterval` | `HostMonitoringConnectionPlugin.FAILURE_DETECTION_INTERVAL` | [HostMonitoringPlugin](./docs/using-the-jdbc-driver/using-plugins/UsingTheHostMonitoringPlugin.md) |
-| `failureDetectionTime` | `HostMonitoringConnectionPlugin.FAILURE_DETECTION_TIME` | [HostMonitoringPlugin](./docs/using-the-jdbc-driver/using-plugins/UsingTheHostMonitoringPlugin.md) |
-| `monitorDisposalTime` | `MonitorServiceImpl.MONITOR_DISPOSAL_TIME_MS` | [HostMonitoringPlugin](./docs/using-the-jdbc-driver/using-plugins/UsingTheHostMonitoringPlugin.md) |
-| `iamDefaultPort` | `IamAuthConnectionPlugin.IAM_DEFAULT_PORT` | [IamAuthenticationPlugin](./docs/using-the-jdbc-driver/using-plugins/UsingTheIamAuthenticationPlugin.md) |
-| `iamHost` | `IamAuthConnectionPlugin.IAM_HOST` | [IamAuthenticationPlugin](./docs/using-the-jdbc-driver/using-plugins/UsingTheIamAuthenticationPlugin.md) |
-| `iamRegion` | `IamAuthConnectionPlugin.IAM_REGION` | [IamAuthenticationPlugin](./docs/using-the-jdbc-driver/using-plugins/UsingTheIamAuthenticationPlugin.md) |
-| `iamExpiration` | `IamAuthConnectionPlugin.IAM_EXPIRATION` | [IamAuthenticationPlugin](./docs/using-the-jdbc-driver/using-plugins/UsingTheIamAuthenticationPlugin.md) |
-| `awsProfile` | `PropertyDefinition.AWS_PROFILE` | [AWS Advanced JDBC Driver Parameters](./docs/using-the-jdbc-driver/UsingTheJdbcDriver.md#aws-advanced-jdbc-driver-parameters) |
-| `wrapperLogUnclosedConnections` | `PropertyDefinition.LOG_UNCLOSED_CONNECTIONS` | [AWS Advanced JDBC Driver Parameters](./docs/using-the-jdbc-driver/UsingTheJdbcDriver.md#aws-advanced-jdbc-driver-parameters) |
-| `wrapperLoggerLevel` | `PropertyDefinition.LOGGER_LEVEL` | [Logging](./docs/using-the-jdbc-driver/UsingTheJdbcDriver.md#logging) |
-| `wrapperProfileName` | `PropertyDefinition.PROFILE_NAME` | [Configuration Profiles](./docs/using-the-jdbc-driver/UsingTheJdbcDriver.md#configuration-profiles) |
-| `autoSortWrapperPluginOrder` | `PropertyDefinition.AUTO_SORT_PLUGIN_ORDER` | [Plugins](./docs/using-the-jdbc-driver/UsingTheJdbcDriver.md#plugins) |
-| `loginTimeout` | `PropertyDefinition.LOGIN_TIMEOUT` | [AWS Advanced JDBC Driver Parameters](./docs/using-the-jdbc-driver/UsingTheJdbcDriver.md#aws-advanced-jdbc-driver-parameters) |
-| `connectTimeout` | `PropertyDefinition.CONNECT_TIMEOUT` | [AWS Advanced JDBC Driver Parameters](./docs/using-the-jdbc-driver/UsingTheJdbcDriver.md#aws-advanced-jdbc-driver-parameters) |
-| `socketTimeout` | `PropertyDefinition.SOCKET_TIMEOUT` | [AWS Advanced JDBC Driver Parameters](./docs/using-the-jdbc-driver/UsingTheJdbcDriver.md#aws-advanced-jdbc-driver-parameters) |
-| `tcpKeepAlive` | `PropertyDefinition.TCP_KEEP_ALIVE` | [AWS Advanced JDBC Driver Parameters](./docs/using-the-jdbc-driver/UsingTheJdbcDriver.md#aws-advanced-jdbc-driver-parameters) |
+| Parameter | Reference | Documentation Link |
+|----------------------------------------|:--------------------------------------------------------------------------------------------------------------:|:-----------------------------------------------------------------------------------------------------------------------------:|
+| `wrapperDialect` | `DialectManager.DIALECT` | [Dialects](/docs/using-the-jdbc-driver/DatabaseDialects.md), and whether you should include it. |
+| `wrapperPlugins` | `PropertyDefinition.PLUGINS` | |
+| `secretsManagerSecretId` | `AwsSecretsManagerConnectionPlugin.SECRET_ID_PROPERTY` | [SecretsManagerPlugin](./docs/using-the-jdbc-driver/using-plugins/UsingTheAwsSecretsManagerPlugin.md) |
+| `secretsManagerRegion` | `AwsSecretsManagerConnectionPlugin.REGION_PROPERTY` | [SecretsManagerPlugin](./docs/using-the-jdbc-driver/using-plugins/UsingTheAwsSecretsManagerPlugin.md) |
+| `wrapperDriverName` | `DriverMetaDataConnectionPlugin.WRAPPER_DRIVER_NAME` | [DriverMetaDataConnectionPlugin](./docs/using-the-jdbc-driver/using-plugins/UsingTheDriverMetadataConnectionPlugin.md) |
+| `failoverMode` | `FailoverConnectionPlugin.FAILOVER_MODE` | [FailoverPlugin](./docs/using-the-jdbc-driver/using-plugins/UsingTheFailoverPlugin.md) |
+| `clusterInstanceHostPattern` | `AuroraHostListProvider.CLUSTER_INSTANCE_HOST_PATTERN` | [FailoverPlugin](./docs/using-the-jdbc-driver/using-plugins/UsingTheFailoverPlugin.md) |
+| `enableClusterAwareFailover` | `FailoverConnectionPlugin.ENABLE_CLUSTER_AWARE_FAILOVER` | [FailoverPlugin](./docs/using-the-jdbc-driver/using-plugins/UsingTheFailoverPlugin.md) |
+| `failoverClusterTopologyRefreshRateMs` | `FailoverConnectionPlugin.FAILOVER_CLUSTER_TOPOLOGY_REFRESH_RATE_MS` | [FailoverPlugin](./docs/using-the-jdbc-driver/using-plugins/UsingTheFailoverPlugin.md) |
+| `failoverReaderConnectTimeoutMs` | `FailoverConnectionPlugin.FAILOVER_READER_CONNECT_TIMEOUT_MS` | [FailoverPlugin](./docs/using-the-jdbc-driver/using-plugins/UsingTheFailoverPlugin.md) |
+| `failoverTimeoutMs` | `FailoverConnectionPlugin.FAILOVER_TIMEOUT_MS` | [FailoverPlugin](./docs/using-the-jdbc-driver/using-plugins/UsingTheFailoverPlugin.md) |
+| `failoverWriterReconnectIntervalMs` | `FailoverConnectionPlugin.FAILOVER_WRITER_RECONNECT_INTERVAL_MS` | [FailoverPlugin](./docs/using-the-jdbc-driver/using-plugins/UsingTheFailoverPlugin.md) |
+| `clusterTopologyHighRefreshRateMs` | `MonitoringRdsHostListProvider.CLUSTER_TOPOLOGY_HIGH_REFRESH_RATE_MS` | [FailoverPlugin v2](./docs/using-the-jdbc-driver/using-plugins/UsingTheFailover2Plugin.md) |
+| `failoverReaderHostSelectorStrategy` | `software.amazon.jdbc.plugin.failover2.`
`FailoverConnectionPlugin.FAILOVER_READER_HOST_SELECTOR_STRATEGY` | [FailoverPlugin v2](./docs/using-the-jdbc-driver/using-plugins/UsingTheFailover2Plugin.md) |
+| `failureDetectionCount` | `HostMonitoringConnectionPlugin.FAILURE_DETECTION_COUNT` | [HostMonitoringPlugin](./docs/using-the-jdbc-driver/using-plugins/UsingTheHostMonitoringPlugin.md) |
+| `failureDetectionEnabled` | `HostMonitoringConnectionPlugin.FAILURE_DETECTION_ENABLED` | [HostMonitoringPlugin](./docs/using-the-jdbc-driver/using-plugins/UsingTheHostMonitoringPlugin.md) |
+| `failureDetectionInterval` | `HostMonitoringConnectionPlugin.FAILURE_DETECTION_INTERVAL` | [HostMonitoringPlugin](./docs/using-the-jdbc-driver/using-plugins/UsingTheHostMonitoringPlugin.md) |
+| `failureDetectionTime` | `HostMonitoringConnectionPlugin.FAILURE_DETECTION_TIME` | [HostMonitoringPlugin](./docs/using-the-jdbc-driver/using-plugins/UsingTheHostMonitoringPlugin.md) |
+| `monitorDisposalTime` | `MonitorServiceImpl.MONITOR_DISPOSAL_TIME_MS` | [HostMonitoringPlugin](./docs/using-the-jdbc-driver/using-plugins/UsingTheHostMonitoringPlugin.md) |
+| `iamDefaultPort` | `IamAuthConnectionPlugin.IAM_DEFAULT_PORT` | [IamAuthenticationPlugin](./docs/using-the-jdbc-driver/using-plugins/UsingTheIamAuthenticationPlugin.md) |
+| `iamHost` | `IamAuthConnectionPlugin.IAM_HOST` | [IamAuthenticationPlugin](./docs/using-the-jdbc-driver/using-plugins/UsingTheIamAuthenticationPlugin.md) |
+| `iamRegion` | `IamAuthConnectionPlugin.IAM_REGION` | [IamAuthenticationPlugin](./docs/using-the-jdbc-driver/using-plugins/UsingTheIamAuthenticationPlugin.md) |
+| `iamExpiration` | `IamAuthConnectionPlugin.IAM_EXPIRATION` | [IamAuthenticationPlugin](./docs/using-the-jdbc-driver/using-plugins/UsingTheIamAuthenticationPlugin.md) |
+| `awsProfile` | `PropertyDefinition.AWS_PROFILE` | [AWS Advanced JDBC Driver Parameters](./docs/using-the-jdbc-driver/UsingTheJdbcDriver.md#aws-advanced-jdbc-driver-parameters) |
+| `wrapperLogUnclosedConnections` | `PropertyDefinition.LOG_UNCLOSED_CONNECTIONS` | [AWS Advanced JDBC Driver Parameters](./docs/using-the-jdbc-driver/UsingTheJdbcDriver.md#aws-advanced-jdbc-driver-parameters) |
+| `wrapperLoggerLevel` | `PropertyDefinition.LOGGER_LEVEL` | [Logging](./docs/using-the-jdbc-driver/UsingTheJdbcDriver.md#logging) |
+| `wrapperProfileName` | `PropertyDefinition.PROFILE_NAME` | [Configuration Profiles](./docs/using-the-jdbc-driver/UsingTheJdbcDriver.md#configuration-profiles) |
+| `autoSortWrapperPluginOrder` | `PropertyDefinition.AUTO_SORT_PLUGIN_ORDER` | [Plugins](./docs/using-the-jdbc-driver/UsingTheJdbcDriver.md#plugins) |
+| `loginTimeout` | `PropertyDefinition.LOGIN_TIMEOUT` | [AWS Advanced JDBC Driver Parameters](./docs/using-the-jdbc-driver/UsingTheJdbcDriver.md#aws-advanced-jdbc-driver-parameters) |
+| `connectTimeout` | `PropertyDefinition.CONNECT_TIMEOUT` | [AWS Advanced JDBC Driver Parameters](./docs/using-the-jdbc-driver/UsingTheJdbcDriver.md#aws-advanced-jdbc-driver-parameters) |
+| `socketTimeout` | `PropertyDefinition.SOCKET_TIMEOUT` | [AWS Advanced JDBC Driver Parameters](./docs/using-the-jdbc-driver/UsingTheJdbcDriver.md#aws-advanced-jdbc-driver-parameters) |
+| `tcpKeepAlive` | `PropertyDefinition.TCP_KEEP_ALIVE` | [AWS Advanced JDBC Driver Parameters](./docs/using-the-jdbc-driver/UsingTheJdbcDriver.md#aws-advanced-jdbc-driver-parameters) |
**A Secret ARN** has the following format: `arn:aws:secretsmanager:::secret:SecretName-6RandomCharacters`
@@ -132,9 +132,6 @@ The development team is aware of these limitations and is working to improve the
This driver currently does not support failover with Amazon Aurora Global Databases. While it is possible to connect to global databases, failing over to a secondary cluster will result in errors and there may be additional unforeseen errors when working with global databases. Support for Amazon Aurora Global Databases is in the backlog, but we cannot comment on a timeline right now.
-#### Virtual Threading
-Due to the use of `sychronized` in the AWS JDBC Driver, pinning with virtual threads may occur. Note that this will not cause the AWS JDBC Driver to behave incorrectly, but may hinder scalability when using virtual threads with the AWS JDBC Driver.
-
## Examples
| Description | Examples |
diff --git a/wrapper/src/main/java/software/amazon/jdbc/RoundRobinHostSelector.java b/wrapper/src/main/java/software/amazon/jdbc/RoundRobinHostSelector.java
index 06d9cfac2..d2a9ff31e 100644
--- a/wrapper/src/main/java/software/amazon/jdbc/RoundRobinHostSelector.java
+++ b/wrapper/src/main/java/software/amazon/jdbc/RoundRobinHostSelector.java
@@ -23,6 +23,7 @@
import java.util.List;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -48,6 +49,8 @@ public class RoundRobinHostSelector implements HostSelector {
"((?[^:/?#]*):(?[0-9]*))");
protected static final CacheMap roundRobinCache = new CacheMap<>();
+ protected static final ReentrantLock lock = new ReentrantLock();
+
static {
PropertyDefinition.registerPluginProperties(RoundRobinHostSelector.class);
}
@@ -69,56 +72,63 @@ public static void setRoundRobinHostWeightPairsProperty(final @NonNull Propertie
}
@Override
- public synchronized HostSpec getHost(
+ public HostSpec getHost(
final @NonNull List hosts,
final @NonNull HostRole role,
final @Nullable Properties props) throws SQLException {
- final List eligibleHosts = hosts.stream()
- .filter(hostSpec ->
- role.equals(hostSpec.getRole()) && hostSpec.getAvailability().equals(HostAvailability.AVAILABLE))
- .sorted(Comparator.comparing(HostSpec::getHost))
- .collect(Collectors.toList());
- if (eligibleHosts.isEmpty()) {
- throw new SQLException(Messages.get("HostSelector.noHostsMatchingRole", new Object[]{role}));
- }
+ lock.lock();
+ try {
+ final List eligibleHosts = hosts.stream()
+ .filter(hostSpec ->
+ role.equals(hostSpec.getRole()) && hostSpec.getAvailability().equals(HostAvailability.AVAILABLE))
+ .sorted(Comparator.comparing(HostSpec::getHost))
+ .collect(Collectors.toList());
+
+ if (eligibleHosts.isEmpty()) {
+ throw new SQLException(Messages.get("HostSelector.noHostsMatchingRole", new Object[]{role}));
+ }
- // Create new cache entries for provided hosts if necessary. All hosts point to the same cluster info.
- createCacheEntryForHosts(eligibleHosts, props);
- final String currentClusterInfoKey = eligibleHosts.get(0).getHost();
- final RoundRobinClusterInfo clusterInfo = roundRobinCache.get(currentClusterInfoKey);
+ // Create new cache entries for provided hosts if necessary. All hosts point to the same cluster info.
+ createCacheEntryForHosts(eligibleHosts, props);
+ final String currentClusterInfoKey = eligibleHosts.get(0).getHost();
+ final RoundRobinClusterInfo clusterInfo = roundRobinCache.get(currentClusterInfoKey);
- final HostSpec lastHost = clusterInfo.lastHost;
- int lastHostIndex = -1;
+ final HostSpec lastHost = clusterInfo.lastHost;
+ int lastHostIndex = -1;
- // Check if lastHost is in list of eligible hosts. Update lastHostIndex.
- if (lastHost != null) {
- for (int i = 0; i < eligibleHosts.size(); i++) {
- if (eligibleHosts.get(i).getHost().equals(lastHost.getHost())) {
- lastHostIndex = i;
+ // Check if lastHost is in list of eligible hosts. Update lastHostIndex.
+ if (lastHost != null) {
+ for (int i = 0; i < eligibleHosts.size(); i++) {
+ if (eligibleHosts.get(i).getHost().equals(lastHost.getHost())) {
+ lastHostIndex = i;
+ }
}
}
- }
- final int targetHostIndex;
- // If the host is weighted and the lastHost is in the eligibleHosts list.
- if (clusterInfo.weightCounter > 0 && lastHostIndex != -1) {
- targetHostIndex = lastHostIndex;
- } else {
- if (lastHostIndex != -1 && lastHostIndex != eligibleHosts.size() - 1) {
- targetHostIndex = lastHostIndex + 1;
+ final int targetHostIndex;
+ // If the host is weighted and the lastHost is in the eligibleHosts list.
+ if (clusterInfo.weightCounter > 0 && lastHostIndex != -1) {
+ targetHostIndex = lastHostIndex;
} else {
- targetHostIndex = 0;
+ if (lastHostIndex != -1 && lastHostIndex != eligibleHosts.size() - 1) {
+ targetHostIndex = lastHostIndex + 1;
+ } else {
+ targetHostIndex = 0;
+ }
+
+ final Integer weight = clusterInfo.clusterWeightsMap.get(eligibleHosts.get(targetHostIndex).getHostId());
+ clusterInfo.weightCounter = weight == null ? clusterInfo.defaultWeight : weight;
}
- final Integer weight = clusterInfo.clusterWeightsMap.get(eligibleHosts.get(targetHostIndex).getHostId());
- clusterInfo.weightCounter = weight == null ? clusterInfo.defaultWeight : weight;
- }
+ clusterInfo.weightCounter--;
+ clusterInfo.lastHost = eligibleHosts.get(targetHostIndex);
- clusterInfo.weightCounter--;
- clusterInfo.lastHost = eligibleHosts.get(targetHostIndex);
+ return eligibleHosts.get(targetHostIndex);
- return eligibleHosts.get(targetHostIndex);
+ } finally {
+ lock.unlock();
+ }
}
private void createCacheEntryForHosts(
diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/efm/HostMonitoringConnectionPlugin.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/efm/HostMonitoringConnectionPlugin.java
index 7305250f0..55de2859c 100644
--- a/wrapper/src/main/java/software/amazon/jdbc/plugin/efm/HostMonitoringConnectionPlugin.java
+++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/efm/HostMonitoringConnectionPlugin.java
@@ -40,7 +40,6 @@
import software.amazon.jdbc.util.RdsUrlType;
import software.amazon.jdbc.util.RdsUtils;
import software.amazon.jdbc.util.SubscribedMethodHelper;
-import software.amazon.jdbc.util.telemetry.TelemetryFactory;
/**
* Monitor the server while the connection is executing methods for more sophisticated failure
@@ -181,7 +180,8 @@ public T execute(
} finally {
if (monitorContext != null) {
- synchronized (monitorContext) {
+ monitorContext.getLock().lock();
+ try {
this.monitorService.stopMonitoring(monitorContext);
if (monitorContext.isNodeUnhealthy()) {
@@ -206,6 +206,8 @@ public T execute(
new Object[] {this.pluginService.getCurrentHostSpec().asAlias()})));
}
}
+ } finally {
+ monitorContext.getLock().unlock();
}
LOGGER.finest(
diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/efm/MonitorConnectionContext.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/efm/MonitorConnectionContext.java
index 3fcf03a28..3f538f838 100644
--- a/wrapper/src/main/java/software/amazon/jdbc/plugin/efm/MonitorConnectionContext.java
+++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/efm/MonitorConnectionContext.java
@@ -21,6 +21,7 @@
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger;
import software.amazon.jdbc.util.Messages;
import software.amazon.jdbc.util.telemetry.TelemetryCounter;
@@ -49,6 +50,8 @@ public class MonitorConnectionContext {
private long invalidNodeStartTimeNano; // Only accessed by monitor thread
private long failureCount; // Only accessed by monitor thread
+ private final ReentrantLock lock = new ReentrantLock();
+
/**
* Constructor.
*
@@ -238,4 +241,8 @@ void setConnectionValid(
() -> Messages.get("MonitorConnectionContext.hostAlive",
new Object[] {hostName}));
}
+
+ public ReentrantLock getLock() {
+ return this.lock;
+ }
}
diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/efm/MonitorImpl.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/efm/MonitorImpl.java
index e678e1044..5bd55516d 100644
--- a/wrapper/src/main/java/software/amazon/jdbc/plugin/efm/MonitorImpl.java
+++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/efm/MonitorImpl.java
@@ -192,7 +192,8 @@ public void run() {
while ((monitorContext = this.activeContexts.poll()) != null) {
- synchronized (monitorContext) {
+ monitorContext.getLock().lock();
+ try {
// If context is already invalid, just skip it
if (!monitorContext.isActiveContext()) {
continue;
@@ -223,6 +224,8 @@ public void run() {
delayMillis = monitorContext.getFailureDetectionIntervalMillis();
}
}
+ } finally {
+ monitorContext.getLock().unlock();
}
}
diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java
index 15fb51ca8..672f676cc 100644
--- a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java
+++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java
@@ -558,7 +558,7 @@ protected void dealWithIllegalStateException(
* @param failedHost The host with network errors.
* @throws SQLException if an error occurs
*/
- protected synchronized void failover(final HostSpec failedHost) throws SQLException {
+ protected void failover(final HostSpec failedHost) throws SQLException {
this.pluginService.setAvailability(failedHost.asAliases(), HostAvailability.NOT_AVAILABLE);
if (this.failoverMode == FailoverMode.STRICT_WRITER) {
@@ -720,7 +720,7 @@ protected void invalidateCurrentConnection() {
}
}
- protected synchronized void pickNewConnection() throws SQLException {
+ protected void pickNewConnection() throws SQLException {
if (this.isClosed && this.closedExplicitly) {
LOGGER.fine(() -> Messages.get("Failover.transactionResolutionUnknownError"));
return;
diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/limitless/LimitlessRouterMonitor.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/limitless/LimitlessRouterMonitor.java
index bd5f2f655..8970f4f54 100644
--- a/wrapper/src/main/java/software/amazon/jdbc/plugin/limitless/LimitlessRouterMonitor.java
+++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/limitless/LimitlessRouterMonitor.java
@@ -31,6 +31,7 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.checkerframework.checker.nullness.qual.NonNull;
@@ -56,7 +57,7 @@ public class LimitlessRouterMonitor implements AutoCloseable, Runnable {
private static final String MONITORING_PROPERTY_PREFIX = "limitless-router-monitor-";
private final int intervalMs;
- private @NonNull HostSpec hostSpec;
+ private final @NonNull HostSpec hostSpec;
private final AtomicBoolean stopped = new AtomicBoolean(false);
private final AtomicReference> limitlessRouters = new AtomicReference<>(
Collections.unmodifiableList(new ArrayList<>()));
@@ -73,6 +74,8 @@ public class LimitlessRouterMonitor implements AutoCloseable, Runnable {
return monitoringThread;
});
+ private final ReentrantLock lock = new ReentrantLock();
+
public LimitlessRouterMonitor(
final @NonNull PluginService pluginService,
final @NonNull HostSpec hostSpec,
@@ -164,17 +167,24 @@ public void run() {
}
}
- public synchronized List forceGetLimitlessRouters() throws SQLException {
+ public List forceGetLimitlessRouters() throws SQLException {
LOGGER.finest(Messages.get("LimitlessRouterMonitor.forceGetLimitlessRouters"));
- this.openConnection();
- if (this.monitoringConn == null || this.monitoringConn.isClosed()) {
- throw new SQLException(Messages.get("LimitlessRouterMonitor.forceGetLimitlessRoutersFailed"));
+
+ lock.lock();
+ try {
+ this.openConnection();
+ if (this.monitoringConn == null || this.monitoringConn.isClosed()) {
+ throw new SQLException(Messages.get("LimitlessRouterMonitor.forceGetLimitlessRoutersFailed"));
+ }
+ List newLimitlessRouters = queryForLimitlessRouters(this.monitoringConn);
+ this.limitlessRouters.set(Collections.unmodifiableList(newLimitlessRouters));
+ RoundRobinHostSelector.setRoundRobinHostWeightPairsProperty(this.props, newLimitlessRouters);
+ LOGGER.finest(Utils.logTopology(limitlessRouters.get(), "[limitlessRouterMonitor]"));
+ return newLimitlessRouters;
+
+ } finally {
+ lock.unlock();
}
- List newLimitlessRouters = queryForLimitlessRouters(this.monitoringConn);
- this.limitlessRouters.set(Collections.unmodifiableList(newLimitlessRouters));
- RoundRobinHostSelector.setRoundRobinHostWeightPairsProperty(this.props, newLimitlessRouters);
- LOGGER.finest(Utils.logTopology(limitlessRouters.get(), "[limitlessRouterMonitor]"));
- return newLimitlessRouters;
}
private void openConnection() throws SQLException {
diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/limitless/LimitlessRouterServiceImpl.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/limitless/LimitlessRouterServiceImpl.java
index 1bcbbbce7..3461cabef 100644
--- a/wrapper/src/main/java/software/amazon/jdbc/plugin/limitless/LimitlessRouterServiceImpl.java
+++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/limitless/LimitlessRouterServiceImpl.java
@@ -98,7 +98,7 @@ public List forceGetLimitlessRouters(final String clusterId, final Pro
}
@Override
- public synchronized void startMonitoring(final @NonNull PluginService pluginService,
+ public void startMonitoring(final @NonNull PluginService pluginService,
final @NonNull HostSpec hostSpec,
final @NonNull Properties props,
final int intervalMs) {
diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/readwritesplitting/ReadWriteSplittingPlugin.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/readwritesplitting/ReadWriteSplittingPlugin.java
index ead24e32e..857436430 100644
--- a/wrapper/src/main/java/software/amazon/jdbc/plugin/readwritesplitting/ReadWriteSplittingPlugin.java
+++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/readwritesplitting/ReadWriteSplittingPlugin.java
@@ -377,7 +377,7 @@ private void logAndThrowException(
throw new ReadWriteSplittingSQLException(logMessage, sqlState.getState(), cause);
}
- private synchronized void switchToWriterConnection(
+ private void switchToWriterConnection(
final List hosts)
throws SQLException {
final Connection currentConnection = this.pluginService.getCurrentConnection();
@@ -418,7 +418,7 @@ private void switchCurrentConnectionTo(
newConnectionHost.getUrl()}));
}
- private synchronized void switchToReaderConnection(final List hosts)
+ private void switchToReaderConnection(final List hosts)
throws SQLException {
final Connection currentConnection = this.pluginService.getCurrentConnection();
final HostSpec currentHost = this.pluginService.getCurrentHostSpec();
diff --git a/wrapper/src/main/java/software/amazon/jdbc/wrapper/ConnectionWrapper.java b/wrapper/src/main/java/software/amazon/jdbc/wrapper/ConnectionWrapper.java
index 6062255b5..1712928e8 100644
--- a/wrapper/src/main/java/software/amazon/jdbc/wrapper/ConnectionWrapper.java
+++ b/wrapper/src/main/java/software/amazon/jdbc/wrapper/ConnectionWrapper.java
@@ -193,7 +193,7 @@ public void abort(final Executor executor) throws SQLException {
}
@Override
- public synchronized void clearWarnings() throws SQLException {
+ public void clearWarnings() throws SQLException {
WrapperUtils.runWithPlugins(
SQLException.class,
this.pluginManager,
@@ -500,7 +500,7 @@ public Map> getTypeMap() throws SQLException {
}
@Override
- public synchronized SQLWarning getWarnings() throws SQLException {
+ public SQLWarning getWarnings() throws SQLException {
return WrapperUtils.executeWithPlugins(
SQLWarning.class,
SQLException.class,
diff --git a/wrapper/src/test/java/software/amazon/jdbc/plugin/efm/HostMonitoringConnectionPluginTest.java b/wrapper/src/test/java/software/amazon/jdbc/plugin/efm/HostMonitoringConnectionPluginTest.java
index 34225a2ef..895fabfe3 100644
--- a/wrapper/src/test/java/software/amazon/jdbc/plugin/efm/HostMonitoringConnectionPluginTest.java
+++ b/wrapper/src/test/java/software/amazon/jdbc/plugin/efm/HostMonitoringConnectionPluginTest.java
@@ -41,6 +41,7 @@
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
+import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Supplier;
import java.util.stream.Stream;
import org.junit.jupiter.api.AfterEach;
@@ -85,6 +86,7 @@ class HostMonitoringConnectionPluginTest {
@Mock Supplier supplier;
@Mock RdsUtils rdsUtils;
@Mock MonitorConnectionContext context;
+ @Mock ReentrantLock mockReentrantLock;
@Mock MonitorService monitorService;
@Mock JdbcCallable sqlFunction;
private HostMonitoringConnectionPlugin plugin;
@@ -130,6 +132,7 @@ void initDefaultMockReturns() throws Exception {
anyInt(),
anyInt()))
.thenReturn(context);
+ when(context.getLock()).thenReturn(mockReentrantLock);
when(pluginService.getCurrentConnection()).thenReturn(connection);
when(pluginService.getCurrentHostSpec()).thenReturn(hostSpec);