diff --git a/alloydb-jdbc-connector/src/main/java/com/google/cloud/alloydb/ConnectorRegistry.java b/alloydb-jdbc-connector/src/main/java/com/google/cloud/alloydb/ConnectorRegistry.java index d8db8b88..62acb890 100644 --- a/alloydb-jdbc-connector/src/main/java/com/google/cloud/alloydb/ConnectorRegistry.java +++ b/alloydb-jdbc-connector/src/main/java/com/google/cloud/alloydb/ConnectorRegistry.java @@ -45,4 +45,12 @@ public static void close(String name) { public static void reset() { InternalConnectorRegistry.INSTANCE.resetInstance(); } + + /** + * Shutdown the entire AlloyDB JDBC Connector. This will stop all background threads. All future + * attempts to connect to the database will fail. + */ + public static void shutdown() { + InternalConnectorRegistry.INSTANCE.shutdownInstance(); + } } diff --git a/alloydb-jdbc-connector/src/main/java/com/google/cloud/alloydb/InternalConnectorRegistry.java b/alloydb-jdbc-connector/src/main/java/com/google/cloud/alloydb/InternalConnectorRegistry.java index 4d920fe7..10cffb37 100644 --- a/alloydb-jdbc-connector/src/main/java/com/google/cloud/alloydb/InternalConnectorRegistry.java +++ b/alloydb-jdbc-connector/src/main/java/com/google/cloud/alloydb/InternalConnectorRegistry.java @@ -19,6 +19,7 @@ import com.google.common.base.Preconditions; import com.google.common.util.concurrent.ListeningScheduledExecutorService; import com.google.common.util.concurrent.MoreExecutors; +import com.google.errorprone.annotations.concurrent.GuardedBy; import java.io.Closeable; import java.io.IOException; import java.net.Socket; @@ -45,6 +46,13 @@ enum InternalConnectorRegistry implements Closeable { @SuppressWarnings("ImmutableEnumChecker") private ConcurrentHashMap namedConnectors; + @SuppressWarnings("ImmutableEnumChecker") + private final Object shutdownGuard = new Object(); + + @SuppressWarnings("ImmutableEnumChecker") + @GuardedBy("shutdownGuard") + private boolean shutdown = false; + InternalConnectorRegistry() { // During refresh, each instance consumes 2 threads from the thread pool. By using 8 threads, // there should be enough free threads so that there will not be a deadlock. Most users @@ -70,6 +78,12 @@ void setCredentialFactoryProvider(CredentialFactoryProvider credentialFactoryPro * @throws IOException if error occurs during socket creation. */ public Socket connect(ConnectionConfig config) throws IOException { + synchronized (shutdownGuard) { + if (shutdown) { + throw new IllegalStateException("ConnectorRegistry was shut down."); + } + } + if (config.getNamedConnector() != null) { Connector connector = getNamedConnector(config.getNamedConnector()); return connector.connect(config.withConnectorConfig(connector.getConfig())); @@ -86,6 +100,12 @@ public Socket connect(ConnectionConfig config) throws IOException { /** Register the configuration for a named connector. */ public void register(String name, ConnectorConfig config) { + synchronized (shutdownGuard) { + if (shutdown) { + throw new IllegalStateException("ConnectorRegistry was shut down."); + } + } + if (this.namedConnectors.containsKey(name)) { throw new IllegalArgumentException("Named connection " + name + " exists."); } @@ -94,6 +114,12 @@ public void register(String name, ConnectorConfig config) { /** Close a named connector, stopping the refresh process and removing it from the registry. */ public void close(String name) { + synchronized (shutdownGuard) { + if (shutdown) { + throw new IllegalStateException("ConnectorRegistry was shut down."); + } + } + Connector connector = namedConnectors.remove(name); if (connector == null) { throw new IllegalArgumentException("Named connection " + name + " does not exist."); @@ -101,8 +127,8 @@ public void close(String name) { connector.close(); } - /** Shutdown all connectors and remove the singleton instance. */ - private void shutdown() { + /** Shutdown all connectors. */ + private void shutdownConnectors() { this.unnamedConnectors.forEach((key, c) -> c.close()); this.unnamedConnectors.clear(); this.namedConnectors.forEach((key, c) -> c.close()); @@ -111,13 +137,21 @@ private void shutdown() { @Override public void close() { - shutdown(); - this.executor.shutdown(); + shutdownInstance(); } /** Calls shutdown on the singleton. */ public void resetInstance() { - shutdown(); + shutdownConnectors(); + } + + /** Calls shutdown on the singleton. */ + public void shutdownInstance() { + synchronized (shutdownGuard) { + shutdown = true; + shutdownConnectors(); + this.executor.shutdown(); + } } private Connector getConnector(ConnectionConfig config) { diff --git a/docs/configuration.md b/docs/configuration.md index 1e7a91ad..a311f170 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -182,9 +182,21 @@ ConnectorRegistry.reset(); ``` After calling `ConnectorRegistry.reset()`, the next attempt to connect to a -database using a SocketFactory or R2DBC ConnectionFactory, or -to `ConnectorRegistry.register()` will start a new connector registry, restart -the background threads, and create a new connector. +database, or to `ConnectorRegistry.register()` will start a new connector +registry, restart the background threads, and create a new connector. + +### Shutdown The Connector Registry + +The application may shut down the ConnectorRegistry. This closes all existing +named and unnamed connectors, and stops internal background threads. + +```java +ConnectorRegistry.shutdown(); +``` + +After calling `ConnectorRegistry.shutdown()`, subsequent attempts to connect to +a database, or to `ConnectorRegistry.register()` will fail, +throwing `IllegalStateException`. ## Configuring Google Credentials