diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java index 7ab09283..b6b90c07 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java @@ -125,8 +125,7 @@ public KASKeyCache getKeyCache() { @Override public synchronized void close() { - this.httpClient.dispatcher().cancelAll(); - this.httpClient.connectionPool().evictAll(); + // KASClient is no longer responsible for closing the shared http client as it is closed by SDKBuilder } static class RewrapRequestBody { diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java index 698953f0..ec2720f9 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java @@ -53,6 +53,7 @@ import java.util.Collections; import java.util.List; import java.util.UUID; +import java.util.concurrent.TimeUnit; import java.util.function.BiFunction; /** @@ -177,7 +178,7 @@ public SDKBuilder protocol(ProtocolType protocolType) { return this; } - private Interceptor getAuthInterceptor(RSAKey rsaKey) { + private Interceptor getAuthInterceptor(RSAKey rsaKey, OkHttpClient httpClient) { if (platformEndpoint == null) { throw new SDKException("cannot build an SDK without specifying the platform endpoint"); } @@ -191,7 +192,6 @@ private Interceptor getAuthInterceptor(RSAKey rsaKey) { // we don't add the auth listener to this channel since it is only used to call // the well known endpoint GetWellKnownConfigurationResponse config; - var httpClient = getHttpClient(); ProtocolClient bootstrapClient = getUnauthenticatedProtocolClient(platformEndpoint, httpClient) ; var stub = new WellKnownServiceClient(bootstrapClient); try { @@ -266,9 +266,9 @@ ServicesAndInternals buildServices() { } this.platformEndpoint = AddressNormalizer.normalizeAddress(this.platformEndpoint, this.usePlainText); - var authInterceptor = getAuthInterceptor(dpopKey); - var kasClient = getKASClient(dpopKey, authInterceptor); var httpClient = getHttpClient(); + var authInterceptor = getAuthInterceptor(dpopKey, httpClient); + var kasClient = getKASClient(dpopKey, authInterceptor, httpClient); var client = getProtocolClient(platformEndpoint, httpClient, authInterceptor); var attributeService = new AttributesServiceClient(client); var namespaceService = new NamespaceServiceClient(client); @@ -284,6 +284,14 @@ ServicesAndInternals buildServices() { public void close() { kasClient.close(); httpClient.dispatcher().executorService().shutdown(); + try { + if (!httpClient.dispatcher().executorService().awaitTermination(60, TimeUnit.SECONDS)) { // Wait for 60 seconds + httpClient.dispatcher().executorService().shutdownNow(); // Force shutdown if tasks don't complete + } + } catch (InterruptedException e) { + httpClient.dispatcher().executorService().shutdownNow(); + Thread.currentThread().interrupt(); // Preserve interrupt status + } httpClient.connectionPool().evictAll(); } @@ -341,9 +349,9 @@ public SDK.KAS kas() { } @Nonnull - private KASClient getKASClient(RSAKey dpopKey, Interceptor interceptor) { + private KASClient getKASClient(RSAKey dpopKey, Interceptor interceptor, OkHttpClient httpClient) { BiFunction protocolClientFactory = (OkHttpClient client, String address) -> getProtocolClient(address, client, interceptor); - return new KASClient(getHttpClient(), protocolClientFactory, dpopKey, usePlainText); + return new KASClient(httpClient, protocolClientFactory, dpopKey, usePlainText); } public SDK build() {