diff --git a/google-cloud-spanner/pom.xml b/google-cloud-spanner/pom.xml index 77ab722be3..ad37f479a9 100644 --- a/google-cloud-spanner/pom.xml +++ b/google-cloud-spanner/pom.xml @@ -15,7 +15,6 @@ google-cloud-spanner - false @@ -51,7 +50,6 @@ default-test com.google.cloud.spanner.TracerTest,com.google.cloud.spanner.IntegrationTest - ${skipUTs} diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java index 62cfa336e9..88587c6428 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java @@ -104,6 +104,7 @@ public class SpannerOptions extends ServiceOptions { private final DatabaseAdminStubSettings databaseAdminStubSettings; private final Duration partitionedDmlTimeout; private final boolean autoThrottleAdministrativeRequests; + private final RetrySettings retryAdministrativeRequestsSettings; private final boolean trackTransactionStarter; /** * These are the default {@link QueryOptions} defined by the user on this {@link SpannerOptions}. @@ -554,6 +555,7 @@ private SpannerOptions(Builder builder) { } partitionedDmlTimeout = builder.partitionedDmlTimeout; autoThrottleAdministrativeRequests = builder.autoThrottleAdministrativeRequests; + retryAdministrativeRequestsSettings = builder.retryAdministrativeRequestsSettings; trackTransactionStarter = builder.trackTransactionStarter; defaultQueryOptions = builder.defaultQueryOptions; envQueryOptions = builder.getEnvironmentQueryOptions(); @@ -606,6 +608,13 @@ public static class Builder extends ServiceOptions.Builder { static final int DEFAULT_PREFETCH_CHUNKS = 4; static final QueryOptions DEFAULT_QUERY_OPTIONS = QueryOptions.getDefaultInstance(); + static final RetrySettings DEFAULT_ADMIN_REQUESTS_LIMIT_EXCEEDED_RETRY_SETTINGS = + RetrySettings.newBuilder() + .setInitialRetryDelay(Duration.ofSeconds(5L)) + .setRetryDelayMultiplier(2.0) + .setMaxRetryDelay(Duration.ofSeconds(60L)) + .setMaxAttempts(10) + .build(); private final ImmutableSet allowedClientLibTokens = ImmutableSet.of( ServiceOptions.getGoogApiClientLibName(), @@ -632,6 +641,8 @@ public static class Builder private DatabaseAdminStubSettings.Builder databaseAdminStubSettingsBuilder = DatabaseAdminStubSettings.newBuilder(); private Duration partitionedDmlTimeout = Duration.ofHours(2L); + private RetrySettings retryAdministrativeRequestsSettings = + DEFAULT_ADMIN_REQUESTS_LIMIT_EXCEEDED_RETRY_SETTINGS; private boolean autoThrottleAdministrativeRequests = false; private boolean trackTransactionStarter = false; private Map defaultQueryOptions = new HashMap<>(); @@ -680,6 +691,7 @@ private Builder() { this.databaseAdminStubSettingsBuilder = options.databaseAdminStubSettings.toBuilder(); this.partitionedDmlTimeout = options.partitionedDmlTimeout; this.autoThrottleAdministrativeRequests = options.autoThrottleAdministrativeRequests; + this.retryAdministrativeRequestsSettings = options.retryAdministrativeRequestsSettings; this.trackTransactionStarter = options.trackTransactionStarter; this.defaultQueryOptions = options.defaultQueryOptions; this.callCredentialsProvider = options.callCredentialsProvider; @@ -892,6 +904,16 @@ public Builder setAutoThrottleAdministrativeRequests() { return this; } + /** + * Sets the retry settings for retrying administrative requests when the quote of administrative + * requests per minute has been exceeded. + */ + Builder setRetryAdministrativeRequestsSettings( + RetrySettings retryAdministrativeRequestsSettings) { + this.retryAdministrativeRequestsSettings = retryAdministrativeRequestsSettings; + return this; + } + /** * Instructs the client library to track the first request of each read/write transaction. This * statement will include a BeginTransaction option and will return a transaction id as part of @@ -1092,6 +1114,10 @@ public boolean isAutoThrottleAdministrativeRequests() { return autoThrottleAdministrativeRequests; } + public RetrySettings getRetryAdministrativeRequestsSettings() { + return retryAdministrativeRequestsSettings; + } + public boolean isTrackTransactionStarter() { return trackTransactionStarter; } diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java index 29ee44bede..ef7966beed 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java @@ -273,6 +273,7 @@ private void awaitTermination() throws InterruptedException { private final ScheduledExecutorService spannerWatchdog; private final boolean throttleAdministrativeRequests; + private final RetrySettings retryAdministrativeRequestsSettings; private static final double ADMINISTRATIVE_REQUESTS_RATE_LIMIT = 1.0D; private static final ConcurrentMap ADMINISTRATIVE_REQUESTS_RATE_LIMITERS = new ConcurrentHashMap<>(); @@ -282,6 +283,10 @@ public static GapicSpannerRpc create(SpannerOptions options) { } public GapicSpannerRpc(final SpannerOptions options) { + this(options, true); + } + + GapicSpannerRpc(final SpannerOptions options, boolean initializeStubs) { this.projectId = options.getProjectId(); String projectNameStr = PROJECT_NAME_TEMPLATE.instantiate("project", this.projectId); try { @@ -296,6 +301,7 @@ public GapicSpannerRpc(final SpannerOptions options) { ADMINISTRATIVE_REQUESTS_RATE_LIMITERS.putIfAbsent( projectNameStr, RateLimiter.create(ADMINISTRATIVE_REQUESTS_RATE_LIMIT)); } + this.retryAdministrativeRequestsSettings = options.getRetryAdministrativeRequestsSettings(); // create a metadataProvider which combines both internal headers and // per-method-call extra headers for channelProvider to inject the headers @@ -322,173 +328,184 @@ public GapicSpannerRpc(final SpannerOptions options) { this.callCredentialsProvider = options.getCallCredentialsProvider(); this.compressorName = options.getCompressorName(); - // Create a managed executor provider. - this.executorProvider = - new ManagedInstantiatingExecutorProvider( - new ThreadFactoryBuilder() - .setDaemon(true) - .setNameFormat("Cloud-Spanner-TransportChannel-%d") - .build()); - // First check if SpannerOptions provides a TransportChannelProvider. Create one - // with information gathered from SpannerOptions if none is provided - InstantiatingGrpcChannelProvider.Builder defaultChannelProviderBuilder = - InstantiatingGrpcChannelProvider.newBuilder() - .setChannelConfigurator(options.getChannelConfigurator()) - .setEndpoint(options.getEndpoint()) - .setMaxInboundMessageSize(MAX_MESSAGE_SIZE) - .setMaxInboundMetadataSize(MAX_METADATA_SIZE) - .setPoolSize(options.getNumChannels()) - - // Before updating this method to setExecutor, please verify with a code owner on - // the lowest version of gax-grpc that needs to be supported. Currently v1.47.17, - // which doesn't support the setExecutor variant. - .setExecutorProvider(executorProvider) - - // Set a keepalive time of 120 seconds to help long running - // commit GRPC calls succeed - .setKeepAliveTime(Duration.ofSeconds(GRPC_KEEPALIVE_SECONDS)) - - // Then check if SpannerOptions provides an InterceptorProvider. Create a default - // SpannerInterceptorProvider if none is provided - .setInterceptorProvider( - SpannerInterceptorProvider.create( - MoreObjects.firstNonNull( - options.getInterceptorProvider(), - SpannerInterceptorProvider.createDefault())) - .withEncoding(compressorName)) - .setHeaderProvider(headerProviderWithUserAgent) - // Attempts direct access to spanner service over gRPC to improve throughput, - // whether the attempt is allowed is totally controlled by service owner. - .setAttemptDirectPath(true); - - TransportChannelProvider channelProvider = - MoreObjects.firstNonNull( - options.getChannelProvider(), defaultChannelProviderBuilder.build()); - - CredentialsProvider credentialsProvider = - GrpcTransportOptions.setUpCredentialsProvider(options); - - spannerWatchdog = - Executors.newSingleThreadScheduledExecutor( - new ThreadFactoryBuilder() - .setDaemon(true) - .setNameFormat("Cloud-Spanner-WatchdogProvider-%d") - .build()); - WatchdogProvider watchdogProvider = - InstantiatingWatchdogProvider.create() - .withExecutor(spannerWatchdog) - .withCheckInterval(checkInterval) - .withClock(NanoClock.getDefaultClock()); - - try { - this.spannerStub = - GrpcSpannerStub.create( - options - .getSpannerStubSettings() - .toBuilder() - .setTransportChannelProvider(channelProvider) - .setCredentialsProvider(credentialsProvider) - .setStreamWatchdogProvider(watchdogProvider) + if (initializeStubs) { + // Create a managed executor provider. + this.executorProvider = + new ManagedInstantiatingExecutorProvider( + new ThreadFactoryBuilder() + .setDaemon(true) + .setNameFormat("Cloud-Spanner-TransportChannel-%d") .build()); - partitionedDmlRetrySettings = - options - .getSpannerStubSettings() - .executeSqlSettings() - .getRetrySettings() - .toBuilder() - .setInitialRpcTimeout(options.getPartitionedDmlTimeout()) - .setMaxRpcTimeout(options.getPartitionedDmlTimeout()) - .setTotalTimeout(options.getPartitionedDmlTimeout()) - .setRpcTimeoutMultiplier(1.0) - .build(); - SpannerStubSettings.Builder pdmlSettings = options.getSpannerStubSettings().toBuilder(); - pdmlSettings - .setTransportChannelProvider(channelProvider) - .setCredentialsProvider(credentialsProvider) - .setStreamWatchdogProvider(watchdogProvider) - .executeSqlSettings() - .setRetrySettings(partitionedDmlRetrySettings); - pdmlSettings.executeStreamingSqlSettings().setRetrySettings(partitionedDmlRetrySettings); - // The stream watchdog will by default only check for a timeout every 10 seconds, so if the - // timeout is less than 10 seconds, it would be ignored for the first 10 seconds unless we - // also change the StreamWatchdogCheckInterval. - if (options - .getPartitionedDmlTimeout() - .dividedBy(10L) - .compareTo(pdmlSettings.getStreamWatchdogCheckInterval()) - < 0) { - pdmlSettings.setStreamWatchdogCheckInterval( - options.getPartitionedDmlTimeout().dividedBy(10)); - pdmlSettings.setStreamWatchdogProvider( - pdmlSettings - .getStreamWatchdogProvider() - .withCheckInterval(pdmlSettings.getStreamWatchdogCheckInterval())); - } - this.partitionedDmlStub = GrpcSpannerStub.create(pdmlSettings.build()); - - this.instanceAdminStub = - GrpcInstanceAdminStub.create( - options - .getInstanceAdminStubSettings() - .toBuilder() - .setTransportChannelProvider(channelProvider) - .setCredentialsProvider(credentialsProvider) - .setStreamWatchdogProvider(watchdogProvider) + // First check if SpannerOptions provides a TransportChannelProvider. Create one + // with information gathered from SpannerOptions if none is provided + InstantiatingGrpcChannelProvider.Builder defaultChannelProviderBuilder = + InstantiatingGrpcChannelProvider.newBuilder() + .setChannelConfigurator(options.getChannelConfigurator()) + .setEndpoint(options.getEndpoint()) + .setMaxInboundMessageSize(MAX_MESSAGE_SIZE) + .setMaxInboundMetadataSize(MAX_METADATA_SIZE) + .setPoolSize(options.getNumChannels()) + + // Before updating this method to setExecutor, please verify with a code owner on + // the lowest version of gax-grpc that needs to be supported. Currently v1.47.17, + // which doesn't support the setExecutor variant. + .setExecutorProvider(executorProvider) + + // Set a keepalive time of 120 seconds to help long running + // commit GRPC calls succeed + .setKeepAliveTime(Duration.ofSeconds(GRPC_KEEPALIVE_SECONDS)) + + // Then check if SpannerOptions provides an InterceptorProvider. Create a default + // SpannerInterceptorProvider if none is provided + .setInterceptorProvider( + SpannerInterceptorProvider.create( + MoreObjects.firstNonNull( + options.getInterceptorProvider(), + SpannerInterceptorProvider.createDefault())) + .withEncoding(compressorName)) + .setHeaderProvider(headerProviderWithUserAgent) + // Attempts direct access to spanner service over gRPC to improve throughput, + // whether the attempt is allowed is totally controlled by service owner. + .setAttemptDirectPath(true); + + TransportChannelProvider channelProvider = + MoreObjects.firstNonNull( + options.getChannelProvider(), defaultChannelProviderBuilder.build()); + + CredentialsProvider credentialsProvider = + GrpcTransportOptions.setUpCredentialsProvider(options); + + spannerWatchdog = + Executors.newSingleThreadScheduledExecutor( + new ThreadFactoryBuilder() + .setDaemon(true) + .setNameFormat("Cloud-Spanner-WatchdogProvider-%d") .build()); + WatchdogProvider watchdogProvider = + InstantiatingWatchdogProvider.create() + .withExecutor(spannerWatchdog) + .withCheckInterval(checkInterval) + .withClock(NanoClock.getDefaultClock()); - this.databaseAdminStubSettings = - options - .getDatabaseAdminStubSettings() - .toBuilder() - .setTransportChannelProvider(channelProvider) - .setCredentialsProvider(credentialsProvider) - .setStreamWatchdogProvider(watchdogProvider) - .build(); - - // Automatically retry RESOURCE_EXHAUSTED for GetOperation if auto-throttling of - // administrative requests has been set. The GetOperation RPC is called repeatedly by gax - // while polling long-running operations for their progress and can also cause these errors. - // The default behavior is not to retry these errors, and this option should normally only be - // enabled for (integration) testing. - if (options.isAutoThrottleAdministrativeRequests()) { - GrpcStubCallableFactory factory = - new GrpcDatabaseAdminCallableFactory() { - @Override - public UnaryCallable createUnaryCallable( - GrpcCallSettings grpcCallSettings, - UnaryCallSettings callSettings, - ClientContext clientContext) { - // Make GetOperation retry on RESOURCE_EXHAUSTED to prevent polling operations from - // failing with an Administrative requests limit exceeded error. - if (grpcCallSettings - .getMethodDescriptor() - .getFullMethodName() - .equals("google.longrunning.Operations/GetOperation")) { - Set codes = - ImmutableSet.builderWithExpectedSize( - callSettings.getRetryableCodes().size() + 1) - .addAll(callSettings.getRetryableCodes()) - .add(StatusCode.Code.RESOURCE_EXHAUSTED) - .build(); - callSettings = callSettings.toBuilder().setRetryableCodes(codes).build(); + try { + this.spannerStub = + GrpcSpannerStub.create( + options + .getSpannerStubSettings() + .toBuilder() + .setTransportChannelProvider(channelProvider) + .setCredentialsProvider(credentialsProvider) + .setStreamWatchdogProvider(watchdogProvider) + .build()); + partitionedDmlRetrySettings = + options + .getSpannerStubSettings() + .executeSqlSettings() + .getRetrySettings() + .toBuilder() + .setInitialRpcTimeout(options.getPartitionedDmlTimeout()) + .setMaxRpcTimeout(options.getPartitionedDmlTimeout()) + .setTotalTimeout(options.getPartitionedDmlTimeout()) + .setRpcTimeoutMultiplier(1.0) + .build(); + SpannerStubSettings.Builder pdmlSettings = options.getSpannerStubSettings().toBuilder(); + pdmlSettings + .setTransportChannelProvider(channelProvider) + .setCredentialsProvider(credentialsProvider) + .setStreamWatchdogProvider(watchdogProvider) + .executeSqlSettings() + .setRetrySettings(partitionedDmlRetrySettings); + pdmlSettings.executeStreamingSqlSettings().setRetrySettings(partitionedDmlRetrySettings); + // The stream watchdog will by default only check for a timeout every 10 seconds, so if the + // timeout is less than 10 seconds, it would be ignored for the first 10 seconds unless we + // also change the StreamWatchdogCheckInterval. + if (options + .getPartitionedDmlTimeout() + .dividedBy(10L) + .compareTo(pdmlSettings.getStreamWatchdogCheckInterval()) + < 0) { + pdmlSettings.setStreamWatchdogCheckInterval( + options.getPartitionedDmlTimeout().dividedBy(10)); + pdmlSettings.setStreamWatchdogProvider( + pdmlSettings + .getStreamWatchdogProvider() + .withCheckInterval(pdmlSettings.getStreamWatchdogCheckInterval())); + } + this.partitionedDmlStub = GrpcSpannerStub.create(pdmlSettings.build()); + + this.instanceAdminStub = + GrpcInstanceAdminStub.create( + options + .getInstanceAdminStubSettings() + .toBuilder() + .setTransportChannelProvider(channelProvider) + .setCredentialsProvider(credentialsProvider) + .setStreamWatchdogProvider(watchdogProvider) + .build()); + + this.databaseAdminStubSettings = + options + .getDatabaseAdminStubSettings() + .toBuilder() + .setTransportChannelProvider(channelProvider) + .setCredentialsProvider(credentialsProvider) + .setStreamWatchdogProvider(watchdogProvider) + .build(); + + // Automatically retry RESOURCE_EXHAUSTED for GetOperation if auto-throttling of + // administrative requests has been set. The GetOperation RPC is called repeatedly by gax + // while polling long-running operations for their progress and can also cause these errors. + // The default behavior is not to retry these errors, and this option should normally only + // be enabled for (integration) testing. + if (options.isAutoThrottleAdministrativeRequests()) { + GrpcStubCallableFactory factory = + new GrpcDatabaseAdminCallableFactory() { + @Override + public UnaryCallable createUnaryCallable( + GrpcCallSettings grpcCallSettings, + UnaryCallSettings callSettings, + ClientContext clientContext) { + // Make GetOperation retry on RESOURCE_EXHAUSTED to prevent polling operations + // from failing with an Administrative requests limit exceeded error. + if (grpcCallSettings + .getMethodDescriptor() + .getFullMethodName() + .equals("google.longrunning.Operations/GetOperation")) { + Set codes = + ImmutableSet.builderWithExpectedSize( + callSettings.getRetryableCodes().size() + 1) + .addAll(callSettings.getRetryableCodes()) + .add(StatusCode.Code.RESOURCE_EXHAUSTED) + .build(); + callSettings = callSettings.toBuilder().setRetryableCodes(codes).build(); + } + return super.createUnaryCallable(grpcCallSettings, callSettings, clientContext); } - return super.createUnaryCallable(grpcCallSettings, callSettings, clientContext); - } - }; - this.databaseAdminStub = - new GrpcDatabaseAdminStubWithCustomCallableFactory( - databaseAdminStubSettings, - ClientContext.create(databaseAdminStubSettings), - factory); - } else { - this.databaseAdminStub = GrpcDatabaseAdminStub.create(databaseAdminStubSettings); - } + }; + this.databaseAdminStub = + new GrpcDatabaseAdminStubWithCustomCallableFactory( + databaseAdminStubSettings, + ClientContext.create(databaseAdminStubSettings), + factory); + } else { + this.databaseAdminStub = GrpcDatabaseAdminStub.create(databaseAdminStubSettings); + } - // Check whether the SPANNER_EMULATOR_HOST env var has been set, and if so, if the emulator is - // actually running. - checkEmulatorConnection(options, channelProvider, credentialsProvider); - } catch (Exception e) { - throw newSpannerException(e); + // Check whether the SPANNER_EMULATOR_HOST env var has been set, and if so, if the emulator + // is actually running. + checkEmulatorConnection(options, channelProvider, credentialsProvider); + } catch (Exception e) { + throw newSpannerException(e); + } + } else { + this.databaseAdminStub = null; + this.instanceAdminStub = null; + this.spannerStub = null; + this.partitionedDmlStub = null; + this.databaseAdminStubSettings = null; + this.spannerWatchdog = null; + this.partitionedDmlRetrySettings = null; + this.executorProvider = null; } } @@ -578,11 +595,11 @@ public boolean shouldRetry(Throwable prevThrowable, T prevResponse) } } - private static T runWithRetryOnAdministrativeRequestsExceeded(Callable callable) { + private T runWithRetryOnAdministrativeRequestsExceeded(Callable callable) { try { return RetryHelper.runWithRetries( callable, - ADMIN_REQUESTS_LIMIT_EXCEEDED_RETRY_SETTINGS, + retryAdministrativeRequestsSettings, new AdminRequestsLimitExceededRetryAlgorithm<>(), NanoClock.getDefaultClock()); } catch (RetryHelperException e) { @@ -1630,22 +1647,24 @@ GrpcCallContext newCallContext( @Override public void shutdown() { this.rpcIsClosed = true; - this.spannerStub.close(); - this.partitionedDmlStub.close(); - this.instanceAdminStub.close(); - this.databaseAdminStub.close(); - this.spannerWatchdog.shutdown(); - this.executorProvider.shutdown(); + if (this.spannerStub != null) { + this.spannerStub.close(); + this.partitionedDmlStub.close(); + this.instanceAdminStub.close(); + this.databaseAdminStub.close(); + this.spannerWatchdog.shutdown(); + this.executorProvider.shutdown(); - try { - this.spannerStub.awaitTermination(10L, TimeUnit.SECONDS); - this.partitionedDmlStub.awaitTermination(10L, TimeUnit.SECONDS); - this.instanceAdminStub.awaitTermination(10L, TimeUnit.SECONDS); - this.databaseAdminStub.awaitTermination(10L, TimeUnit.SECONDS); - this.spannerWatchdog.awaitTermination(10L, TimeUnit.SECONDS); - this.executorProvider.awaitTermination(); - } catch (InterruptedException e) { - throw SpannerExceptionFactory.propagateInterrupt(e); + try { + this.spannerStub.awaitTermination(10L, TimeUnit.SECONDS); + this.partitionedDmlStub.awaitTermination(10L, TimeUnit.SECONDS); + this.instanceAdminStub.awaitTermination(10L, TimeUnit.SECONDS); + this.databaseAdminStub.awaitTermination(10L, TimeUnit.SECONDS); + this.spannerWatchdog.awaitTermination(10L, TimeUnit.SECONDS); + this.executorProvider.awaitTermination(); + } catch (InterruptedException e) { + throw SpannerExceptionFactory.propagateInterrupt(e); + } } } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientTest.java index 3a18f24173..9dcadc85ad 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientTest.java @@ -38,18 +38,22 @@ import com.google.cloud.spanner.MockSpannerServiceImpl.SimulatedExecutionTime; import com.google.longrunning.Operation; import com.google.protobuf.InvalidProtocolBufferException; +import com.google.rpc.ErrorInfo; import com.google.spanner.admin.database.v1.CreateBackupMetadata; import com.google.spanner.admin.database.v1.CreateBackupRequest; import com.google.spanner.admin.database.v1.CreateDatabaseMetadata; import com.google.spanner.admin.database.v1.CreateDatabaseRequest; +import com.google.spanner.admin.database.v1.GetDatabaseRequest; import com.google.spanner.admin.database.v1.OptimizeRestoredDatabaseMetadata; import com.google.spanner.admin.database.v1.RestoreDatabaseMetadata; import com.google.spanner.admin.database.v1.RestoreDatabaseRequest; import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata; import io.grpc.ManagedChannelBuilder; +import io.grpc.Metadata; import io.grpc.Server; import io.grpc.Status; import io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder; +import io.grpc.protobuf.lite.ProtoLiteUtils; import java.net.InetSocketAddress; import java.util.Arrays; import java.util.Collections; @@ -83,8 +87,8 @@ public class DatabaseAdminClientTest { private static Server server; private static InetSocketAddress address; - private Spanner spanner; - private DatabaseAdminClient client; + private static Spanner spanner; + private static DatabaseAdminClient client; private OperationFuture createDatabaseOperation; private OperationFuture createBackupOperation; private OperationFuture restoreDatabaseOperation; @@ -101,18 +105,6 @@ public static void startStaticServer() throws Exception { .addService(mockDatabaseAdmin) .build() .start(); - } - - @AfterClass - public static void stopServer() throws Exception { - server.shutdown(); - server.awaitTermination(); - } - - @Before - public void setUp() { - mockDatabaseAdmin.reset(); - mockOperations.reset(); SpannerOptions.Builder builder = SpannerOptions.newBuilder(); RetrySettings longRunningInitialRetrySettings = RetrySettings.newBuilder() @@ -193,6 +185,11 @@ public void setUp() { .setRetryDelayMultiplier(1.3) .setRpcTimeoutMultiplier(1.3) .build())); + builder.setRetryAdministrativeRequestsSettings( + SpannerOptions.Builder.DEFAULT_ADMIN_REQUESTS_LIMIT_EXCEEDED_RETRY_SETTINGS + .toBuilder() + .setInitialRetryDelay(Duration.ofNanos(1L)) + .build()); spanner = builder .setHost("http://localhost:" + server.getPort()) @@ -202,6 +199,19 @@ public void setUp() { .build() .getService(); client = spanner.getDatabaseAdminClient(); + } + + @AfterClass + public static void stopServer() throws Exception { + spanner.close(); + server.shutdown(); + server.awaitTermination(); + } + + @Before + public void setUp() { + mockDatabaseAdmin.reset(); + mockOperations.reset(); createTestDatabase(); createTestBackup(); restoreTestBackup(); @@ -212,7 +222,6 @@ public void tearDown() { mockDatabaseAdmin.reset(); mockDatabaseAdmin.removeAllExecutionTimes(); mockOperations.reset(); - spanner.close(); } @Test @@ -905,4 +914,25 @@ public void retryRestoreDatabaseSlowStartup() throws Exception { assertThat(retrieved.getCreateTime()).isEqualTo(database.getCreateTime()); assertThat(mockDatabaseAdmin.countRequestsOfType(RestoreDatabaseRequest.class)).isAtLeast(3); } + + @Test + public void testRetryOperationOnAdminMethodQuotaPerMinutePerProjectExceeded() { + ErrorInfo info = + ErrorInfo.newBuilder() + .putMetadata("quota_limit", "AdminMethodQuotaPerMinutePerProject") + .build(); + Metadata.Key key = + Metadata.Key.of( + info.getDescriptorForType().getFullName() + Metadata.BINARY_HEADER_SUFFIX, + ProtoLiteUtils.metadataMarshaller(info)); + Metadata trailers = new Metadata(); + trailers.put(key, info); + mockDatabaseAdmin.addException( + Status.RESOURCE_EXHAUSTED.withDescription("foo").asRuntimeException(trailers)); + mockDatabaseAdmin.clearRequests(); + + Database database = client.getDatabase(INSTANCE_ID, DB_ID); + assertEquals(DB_ID, database.getId().getDatabase()); + assertEquals(2, mockDatabaseAdmin.countRequestsOfType(GetDatabaseRequest.class)); + } } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java index 891f226562..42c7bb1b94 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java @@ -958,6 +958,7 @@ public void transactionManagerIsNonBlocking() throws Exception { } } + @SuppressWarnings("resource") @Test public void transactionManagerExecuteQueryAsync() throws Exception { DatabaseClient client = @@ -991,7 +992,6 @@ public void transactionManagerExecuteQueryAsync() throws Exception { txManager.commit(); break; } catch (AbortedException e) { - Thread.sleep(e.getRetryDelayInMillis()); transaction = txManager.resetForRetry(); } } @@ -1519,11 +1519,7 @@ public void testBackendQueryOptions() { .setProjectId("[PROJECT]") .setChannelProvider(channelProvider) .setCredentials(NoCredentials.getInstance()) - .setSessionPoolOption( - SessionPoolOptions.newBuilder() - .setMinSessions(0) - .setWriteSessionsFraction(0.0f) - .build()) + .setSessionPoolOption(SessionPoolOptions.newBuilder().setMinSessions(0).build()) .build() .getService()) { DatabaseClient client = @@ -1557,11 +1553,7 @@ public void testBackendQueryOptionsWithAnalyzeQuery() { .setProjectId("[PROJECT]") .setChannelProvider(channelProvider) .setCredentials(NoCredentials.getInstance()) - .setSessionPoolOption( - SessionPoolOptions.newBuilder() - .setMinSessions(0) - .setWriteSessionsFraction(0.0f) - .build()) + .setSessionPoolOption(SessionPoolOptions.newBuilder().setMinSessions(0).build()) .build() .getService()) { DatabaseClient client = @@ -1597,11 +1589,7 @@ public void testBackendPartitionQueryOptions() { .setProjectId("[PROJECT]") .setChannelProvider(channelProvider) .setCredentials(NoCredentials.getInstance()) - .setSessionPoolOption( - SessionPoolOptions.newBuilder() - .setMinSessions(0) - .setWriteSessionsFraction(0.0f) - .build()) + .setSessionPoolOption(SessionPoolOptions.newBuilder().setMinSessions(0).build()) .build() .getService()) { BatchClient client = diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MockDatabaseAdminServiceImpl.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MockDatabaseAdminServiceImpl.java index 20a39f318f..6b248f7902 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MockDatabaseAdminServiceImpl.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MockDatabaseAdminServiceImpl.java @@ -447,6 +447,7 @@ private com.google.rpc.Status fromException(Exception e) { private SimulatedExecutionTime createDatabaseStartupExecutionTime = SimulatedExecutionTime.none(); private SimulatedExecutionTime createDatabaseResponseExecutionTime = SimulatedExecutionTime.none(); + private SimulatedExecutionTime getDatabaseExecutionTime = SimulatedExecutionTime.none(); private SimulatedExecutionTime restoreDatabaseStartupExecutionTime = SimulatedExecutionTime.none(); private SimulatedExecutionTime restoreDatabaseResponseExecutionTime = @@ -509,17 +510,22 @@ public void dropDatabase(DropDatabaseRequest request, StreamObserver resp @Override public void getDatabase(GetDatabaseRequest request, StreamObserver responseObserver) { requests.add(request); - MockDatabase db = databases.get(request.getName()); - if (db != null) { - responseObserver.onNext( - Database.newBuilder() - .setName(request.getName()) - .setCreateTime(db.createTime) - .setState(State.READY) - .build()); - responseObserver.onCompleted(); - } else { - responseObserver.onError(Status.NOT_FOUND.asRuntimeException()); + try { + getDatabaseExecutionTime.simulateExecutionTime(exceptions, false, freezeLock); + MockDatabase db = databases.get(request.getName()); + if (db != null) { + responseObserver.onNext( + Database.newBuilder() + .setName(request.getName()) + .setCreateTime(db.createTime) + .setState(State.READY) + .build()); + responseObserver.onCompleted(); + } else { + responseObserver.onError(Status.NOT_FOUND.asRuntimeException()); + } + } catch (Throwable t) { + responseObserver.onError(t); } } @@ -912,6 +918,10 @@ public List getRequests() { return new ArrayList<>(requests); } + public void clearRequests() { + requests.clear(); + } + public int countRequestsOfType(final Class type) { return Collections2.filter(getRequests(), input -> input.getClass().equals(type)).size(); } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/RetryOnInvalidatedSessionTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/RetryOnInvalidatedSessionTest.java index d8eec88d88..a8fbbeb40b 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/RetryOnInvalidatedSessionTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/RetryOnInvalidatedSessionTest.java @@ -18,10 +18,7 @@ import static com.google.cloud.spanner.SpannerApiFutures.get; import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.fail; import com.google.api.core.ApiFuture; @@ -29,13 +26,11 @@ import com.google.api.gax.core.NoCredentialsProvider; import com.google.api.gax.grpc.testing.LocalChannelProvider; import com.google.cloud.NoCredentials; -import com.google.cloud.Timestamp; import com.google.cloud.spanner.AsyncResultSet.CallbackResponse; import com.google.cloud.spanner.AsyncTransactionManager.AsyncTransactionStep; import com.google.cloud.spanner.AsyncTransactionManager.CommitTimestampFuture; import com.google.cloud.spanner.AsyncTransactionManager.TransactionContextFuture; import com.google.cloud.spanner.MockSpannerServiceImpl.StatementResult; -import com.google.cloud.spanner.TransactionRunner.TransactionCallable; import com.google.cloud.spanner.v1.SpannerClient; import com.google.cloud.spanner.v1.SpannerClient.ListSessionsPagedResponse; import com.google.cloud.spanner.v1.SpannerSettings; @@ -55,12 +50,12 @@ import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; -import org.junit.After; +import java.util.function.Supplier; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; @@ -208,30 +203,31 @@ public static void stopServer() throws InterruptedException { } @Before - public void setUp() { + public void setUp() throws InterruptedException { mockSpanner.reset(); - SessionPoolOptions.Builder builder = SessionPoolOptions.newBuilder().setFailOnSessionLeak(); - if (failOnInvalidatedSession) { - builder.setFailIfSessionNotFound(); + if (spanner == null + || spanner.getOptions().getSessionPoolOptions().isFailIfPoolExhausted() + != failOnInvalidatedSession) { + if (spanner != null) { + spanner.close(); + } + SessionPoolOptions.Builder builder = SessionPoolOptions.newBuilder().setFailOnSessionLeak(); + if (failOnInvalidatedSession) { + builder.setFailIfSessionNotFound(); + } + // This prevents repeated retries for a large number of sessions in the pool. + builder.setMinSessions(1); + spanner = + SpannerOptions.newBuilder() + .setProjectId("[PROJECT]") + .setChannelProvider(channelProvider) + .setSessionPoolOption(builder.build()) + .setCredentials(NoCredentials.getInstance()) + .build() + .getService(); + client = spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + invalidateSessionPool(client, spanner.getOptions().getSessionPoolOptions().getMinSessions()); } - spanner = - SpannerOptions.newBuilder() - .setProjectId("[PROJECT]") - .setChannelProvider(channelProvider) - .setSessionPoolOption(builder.build()) - .setCredentials(NoCredentials.getInstance()) - .build() - .getService(); - client = spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); - } - - @After - public void tearDown() { - spanner.close(); - } - - private static void invalidateSessionPool() throws InterruptedException { - invalidateSessionPool(client, spanner.getOptions().getSessionPoolOptions().getMinSessions()); } private static void invalidateSessionPool(DatabaseClient client, int minSessions) @@ -242,7 +238,7 @@ private static void invalidateSessionPool(DatabaseClient client, int minSessions if (watch.elapsed(TimeUnit.SECONDS) > 5L) { fail(String.format("Failed to create MinSessions=%d", minSessions)); } - Thread.sleep(5L); + Thread.sleep(1L); } ListSessionsPagedResponse response = @@ -252,333 +248,226 @@ private static void invalidateSessionPool(DatabaseClient client, int minSessions } } + private T assertThrowsSessionNotFoundIfShouldFail(Supplier supplier) { + if (failOnInvalidatedSession) { + assertThrows(SessionNotFoundException.class, () -> supplier.get()); + return null; + } else { + return supplier.get(); + } + } + @Test public void singleUseSelect() throws InterruptedException { - invalidateSessionPool(); - try { - // This call will receive an invalidated session that will be replaced on the first call to - // rs.next(). - int count = 0; - try (ReadContext context = client.singleUse()) { - try (ResultSet rs = context.executeQuery(SELECT1AND2)) { - while (rs.next()) { - count++; - } - } + // This call will receive an invalidated session that will be replaced on the first call to + // rs.next(). + try (ReadContext context = client.singleUse()) { + try (ResultSet rs = context.executeQuery(SELECT1AND2)) { + assertThrowsSessionNotFoundIfShouldFail(() -> rs.next()); } - assertThat(count).isEqualTo(2); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); } } @Test public void singleUseSelectAsync() throws Exception { - invalidateSessionPool(); ApiFuture> list; try (AsyncResultSet rs = client.singleUse().executeQueryAsync(SELECT1AND2)) { list = rs.toListAsync(TO_LONG, executor); - assertThat(list.get()).containsExactly(1L, 2L); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (ExecutionException e) { - assertThat(e.getCause()).isInstanceOf(SessionNotFoundException.class); - assertThat(failOnInvalidatedSession).isTrue(); + assertThrowsSessionNotFoundIfShouldFail(() -> get(list)); } } @Test public void singleUseRead() throws InterruptedException { - invalidateSessionPool(); - int count = 0; try (ReadContext context = client.singleUse()) { try (ResultSet rs = context.read("FOO", KeySet.all(), Collections.singletonList("BAR"))) { - while (rs.next()) { - count++; - } + assertThrowsSessionNotFoundIfShouldFail(() -> rs.next()); } - assertThat(count).isEqualTo(2); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); } } @Test public void singleUseReadUsingIndex() throws InterruptedException { - invalidateSessionPool(); - int count = 0; try (ReadContext context = client.singleUse()) { try (ResultSet rs = context.readUsingIndex("FOO", "IDX", KeySet.all(), Collections.singletonList("BAR"))) { - while (rs.next()) { - count++; - } + assertThrowsSessionNotFoundIfShouldFail(() -> rs.next()); } - assertThat(count).isEqualTo(2); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); } } @Test public void singleUseReadRow() throws InterruptedException { - invalidateSessionPool(); try (ReadContext context = client.singleUse()) { - Struct row = context.readRow("FOO", Key.of(), Collections.singletonList("BAR")); - assertThat(row.getLong(0)).isEqualTo(1L); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); + assertThrowsSessionNotFoundIfShouldFail( + () -> context.readRow("FOO", Key.of(), Collections.singletonList("BAR"))); } } @Test public void singleUseReadRowUsingIndex() throws InterruptedException { - invalidateSessionPool(); try (ReadContext context = client.singleUse()) { - Struct row = - context.readRowUsingIndex("FOO", "IDX", Key.of(), Collections.singletonList("BAR")); - assertThat(row.getLong(0)).isEqualTo(1L); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); + assertThrowsSessionNotFoundIfShouldFail( + () -> + context.readRowUsingIndex("FOO", "IDX", Key.of(), Collections.singletonList("BAR"))); } } @Test public void singleUseReadOnlyTransactionSelect() throws InterruptedException { - invalidateSessionPool(); - int count = 0; try (ReadContext context = client.singleUseReadOnlyTransaction()) { try (ResultSet rs = context.executeQuery(SELECT1AND2)) { - while (rs.next()) { - count++; - } + assertThrowsSessionNotFoundIfShouldFail(() -> rs.next()); } - assertThat(count).isEqualTo(2); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); } } @Test public void singleUseReadOnlyTransactionRead() throws InterruptedException { - invalidateSessionPool(); - int count = 0; try (ReadContext context = client.singleUseReadOnlyTransaction()) { try (ResultSet rs = context.read("FOO", KeySet.all(), Collections.singletonList("BAR"))) { - while (rs.next()) { - count++; - } + assertThrowsSessionNotFoundIfShouldFail(() -> rs.next()); } - assertThat(count).isEqualTo(2); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); } } @Test public void singlUseReadOnlyTransactionReadUsingIndex() throws InterruptedException { - invalidateSessionPool(); - int count = 0; try (ReadContext context = client.singleUseReadOnlyTransaction()) { try (ResultSet rs = context.readUsingIndex("FOO", "IDX", KeySet.all(), Collections.singletonList("BAR"))) { - while (rs.next()) { - count++; - } + assertThrowsSessionNotFoundIfShouldFail(() -> rs.next()); } - assertThat(count).isEqualTo(2); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); } } @Test public void singleUseReadOnlyTransactionReadRow() throws InterruptedException { - invalidateSessionPool(); try (ReadContext context = client.singleUseReadOnlyTransaction()) { - Struct row = context.readRow("FOO", Key.of(), Collections.singletonList("BAR")); - assertThat(row.getLong(0)).isEqualTo(1L); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); + assertThrowsSessionNotFoundIfShouldFail( + () -> context.readRow("FOO", Key.of(), Collections.singletonList("BAR"))); } } @Test public void singleUseReadOnlyTransactionReadRowUsingIndex() throws InterruptedException { - invalidateSessionPool(); try (ReadContext context = client.singleUseReadOnlyTransaction()) { - Struct row = - context.readRowUsingIndex("FOO", "IDX", Key.of(), Collections.singletonList("BAR")); - assertThat(row.getLong(0)).isEqualTo(1L); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); + assertThrowsSessionNotFoundIfShouldFail( + () -> + context.readRowUsingIndex("FOO", "IDX", Key.of(), Collections.singletonList("BAR"))); } } @Test public void readOnlyTransactionSelect() throws InterruptedException { - invalidateSessionPool(); - int count = 0; try (ReadContext context = client.readOnlyTransaction()) { try (ResultSet rs = context.executeQuery(SELECT1AND2)) { - while (rs.next()) { - count++; - } + assertThrowsSessionNotFoundIfShouldFail(() -> rs.next()); } - assertThat(count).isEqualTo(2); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); } } @Test public void readOnlyTransactionRead() throws InterruptedException { - invalidateSessionPool(); - int count = 0; try (ReadContext context = client.readOnlyTransaction()) { try (ResultSet rs = context.read("FOO", KeySet.all(), Collections.singletonList("BAR"))) { - while (rs.next()) { - count++; - } + assertThrowsSessionNotFoundIfShouldFail(() -> rs.next()); } - assertThat(count).isEqualTo(2); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); } } @Test public void readOnlyTransactionReadUsingIndex() throws InterruptedException { - invalidateSessionPool(); - int count = 0; try (ReadContext context = client.readOnlyTransaction()) { try (ResultSet rs = context.readUsingIndex("FOO", "IDX", KeySet.all(), Collections.singletonList("BAR"))) { - while (rs.next()) { - count++; - } + assertThrowsSessionNotFoundIfShouldFail(() -> rs.next()); } - assertThat(count).isEqualTo(2); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); } } @Test public void readOnlyTransactionReadRow() throws InterruptedException { - invalidateSessionPool(); try (ReadContext context = client.readOnlyTransaction()) { - Struct row = context.readRow("FOO", Key.of(), Collections.singletonList("BAR")); - assertThat(row.getLong(0)).isEqualTo(1L); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); + assertThrowsSessionNotFoundIfShouldFail( + () -> context.readRow("FOO", Key.of(), Collections.singletonList("BAR"))); } } @Test public void readOnlyTransactionReadRowUsingIndex() throws InterruptedException { - invalidateSessionPool(); try (ReadContext context = client.readOnlyTransaction()) { - Struct row = - context.readRowUsingIndex("FOO", "IDX", Key.of(), Collections.singletonList("BAR")); - assertThat(row.getLong(0)).isEqualTo(1L); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); + assertThrowsSessionNotFoundIfShouldFail( + () -> + context.readRowUsingIndex("FOO", "IDX", Key.of(), Collections.singletonList("BAR"))); } } - @Test(expected = SessionNotFoundException.class) + @Test public void readOnlyTransactionSelectNonRecoverable() throws InterruptedException { - int count = 0; try (ReadContext context = client.readOnlyTransaction()) { try (ResultSet rs = context.executeQuery(SELECT1AND2)) { - while (rs.next()) { - count++; - } + assertThrowsSessionNotFoundIfShouldFail(() -> rs.next()); } - assertThat(count).isEqualTo(2); // Invalidate the session pool while in a transaction. This is not recoverable. - invalidateSessionPool(); + invalidateSessionPool(client, spanner.getOptions().getSessionPoolOptions().getMinSessions()); try (ResultSet rs = context.executeQuery(SELECT1AND2)) { - while (rs.next()) { - count++; - } + assertThrows(SessionNotFoundException.class, () -> rs.next()); } } } - @Test(expected = SessionNotFoundException.class) + @Test public void readOnlyTransactionReadNonRecoverable() throws InterruptedException { - int count = 0; try (ReadContext context = client.readOnlyTransaction()) { try (ResultSet rs = context.read("FOO", KeySet.all(), Collections.singletonList("BAR"))) { - while (rs.next()) { - count++; - } + assertThrowsSessionNotFoundIfShouldFail(() -> rs.next()); } - assertThat(count).isEqualTo(2); - invalidateSessionPool(); + invalidateSessionPool(client, spanner.getOptions().getSessionPoolOptions().getMinSessions()); try (ResultSet rs = context.read("FOO", KeySet.all(), Collections.singletonList("BAR"))) { - while (rs.next()) { - count++; - } + assertThrows(SessionNotFoundException.class, () -> rs.next()); } } } - @Test(expected = SessionNotFoundException.class) + @Test public void readOnlyTransactionReadUsingIndexNonRecoverable() throws InterruptedException { - int count = 0; try (ReadContext context = client.readOnlyTransaction()) { try (ResultSet rs = context.readUsingIndex("FOO", "IDX", KeySet.all(), Collections.singletonList("BAR"))) { - while (rs.next()) { - count++; - } + assertThrowsSessionNotFoundIfShouldFail(() -> rs.next()); } - assertThat(count).isEqualTo(2); - invalidateSessionPool(); + invalidateSessionPool(client, spanner.getOptions().getSessionPoolOptions().getMinSessions()); try (ResultSet rs = context.readUsingIndex("FOO", "IDX", KeySet.all(), Collections.singletonList("BAR"))) { - while (rs.next()) { - count++; - } + assertThrows(SessionNotFoundException.class, () -> rs.next()); } } } - @Test(expected = SessionNotFoundException.class) + @Test public void readOnlyTransactionReadRowNonRecoverable() throws InterruptedException { try (ReadContext context = client.readOnlyTransaction()) { - Struct row = context.readRow("FOO", Key.of(), Collections.singletonList("BAR")); - assertThat(row.getLong(0)).isEqualTo(1L); - invalidateSessionPool(); - context.readRow("FOO", Key.of(), Collections.singletonList("BAR")); + assertThrowsSessionNotFoundIfShouldFail( + () -> context.readRow("FOO", Key.of(), Collections.singletonList("BAR"))); + invalidateSessionPool(client, spanner.getOptions().getSessionPoolOptions().getMinSessions()); + assertThrows( + SessionNotFoundException.class, + () -> context.readRow("FOO", Key.of(), Collections.singletonList("BAR"))); } } - @Test(expected = SessionNotFoundException.class) + @Test public void readOnlyTransactionReadRowUsingIndexNonRecoverable() throws InterruptedException { try (ReadContext context = client.readOnlyTransaction()) { - Struct row = - context.readRowUsingIndex("FOO", "IDX", Key.of(), Collections.singletonList("BAR")); - assertThat(row.getLong(0)).isEqualTo(1L); - invalidateSessionPool(); - context.readRowUsingIndex("FOO", "IDX", Key.of(), Collections.singletonList("BAR")); + assertThrowsSessionNotFoundIfShouldFail( + () -> + context.readRowUsingIndex("FOO", "IDX", Key.of(), Collections.singletonList("BAR"))); + invalidateSessionPool(client, spanner.getOptions().getSessionPoolOptions().getMinSessions()); + assertThrows( + SessionNotFoundException.class, + () -> + context.readRowUsingIndex("FOO", "IDX", Key.of(), Collections.singletonList("BAR"))); } } @@ -588,581 +477,409 @@ public void readWriteTransactionReadOnlySessionInPool() throws InterruptedExcept if (failOnInvalidatedSession) { builder.setFailIfSessionNotFound(); } - Spanner spanner = + try (Spanner spanner = SpannerOptions.newBuilder() .setProjectId("[PROJECT]") .setChannelProvider(channelProvider) .setSessionPoolOption(builder.build()) .setCredentials(NoCredentials.getInstance()) .build() - .getService(); - DatabaseClient client = - spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); - invalidateSessionPool(client, spanner.getOptions().getSessionPoolOptions().getMinSessions()); - try { + .getService()) { + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + invalidateSessionPool(client, spanner.getOptions().getSessionPoolOptions().getMinSessions()); TransactionRunner runner = client.readWriteTransaction(); - int count = - runner.run( - transaction -> { - int count1 = 0; - try (ResultSet rs = transaction.executeQuery(SELECT1AND2)) { - while (rs.next()) { - count1++; - } - } - return count1; - }); - assertThat(count).isEqualTo(2); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); + assertThrowsSessionNotFoundIfShouldFail( + () -> + runner.run( + transaction -> { + try (ResultSet rs = transaction.executeQuery(SELECT1AND2)) { + while (rs.next()) {} + } + return null; + })); } } @Test public void readWriteTransactionSelect() throws InterruptedException { - invalidateSessionPool(); - try { - TransactionRunner runner = client.readWriteTransaction(); - int count = - runner.run( - transaction -> { - int count1 = 0; - try (ResultSet rs = transaction.executeQuery(SELECT1AND2)) { - while (rs.next()) { - count1++; + TransactionRunner runner = client.readWriteTransaction(); + assertThrowsSessionNotFoundIfShouldFail( + () -> + runner.run( + transaction -> { + try (ResultSet rs = transaction.executeQuery(SELECT1AND2)) { + while (rs.next()) {} } - } - return count1; - }); - assertThat(count).isEqualTo(2); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); - } + return null; + })); } @Test public void readWriteTransactionRead() throws InterruptedException { - invalidateSessionPool(); - try { - TransactionRunner runner = client.readWriteTransaction(); - int count = - runner.run( - transaction -> { - int count1 = 0; - try (ResultSet rs = - transaction.read("FOO", KeySet.all(), Collections.singletonList("BAR"))) { - while (rs.next()) { - count1++; + TransactionRunner runner = client.readWriteTransaction(); + assertThrowsSessionNotFoundIfShouldFail( + () -> + runner.run( + transaction -> { + try (ResultSet rs = + transaction.read("FOO", KeySet.all(), Collections.singletonList("BAR"))) { + while (rs.next()) {} } - } - return count1; - }); - assertThat(count).isEqualTo(2); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); - } + return null; + })); } @Test public void readWriteTransactionReadUsingIndex() throws InterruptedException { - invalidateSessionPool(); - try { - TransactionRunner runner = client.readWriteTransaction(); - int count = - runner.run( - transaction -> { - int count1 = 0; - try (ResultSet rs = - transaction.readUsingIndex( - "FOO", "IDX", KeySet.all(), Collections.singletonList("BAR"))) { - while (rs.next()) { - count1++; + TransactionRunner runner = client.readWriteTransaction(); + assertThrowsSessionNotFoundIfShouldFail( + () -> + runner.run( + transaction -> { + try (ResultSet rs = + transaction.readUsingIndex( + "FOO", "IDX", KeySet.all(), Collections.singletonList("BAR"))) { + while (rs.next()) {} } - } - return count1; - }); - assertThat(count).isEqualTo(2); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); - } + return null; + })); } @Test public void readWriteTransactionReadRow() throws InterruptedException { - invalidateSessionPool(); - try { - TransactionRunner runner = client.readWriteTransaction(); - Struct row = - runner.run( - transaction -> - transaction.readRow("FOO", Key.of(), Collections.singletonList("BAR"))); - assertThat(row.getLong(0)).isEqualTo(1L); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); - } + TransactionRunner runner = client.readWriteTransaction(); + assertThrowsSessionNotFoundIfShouldFail( + () -> + runner.run( + transaction -> + transaction.readRow("FOO", Key.of(), Collections.singletonList("BAR")))); } @Test public void readWriteTransactionReadRowUsingIndex() throws InterruptedException { - invalidateSessionPool(); - try { - TransactionRunner runner = client.readWriteTransaction(); - Struct row = - runner.run( - transaction -> - transaction.readRowUsingIndex( - "FOO", "IDX", Key.of(), Collections.singletonList("BAR"))); - assertThat(row.getLong(0)).isEqualTo(1L); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); - } + TransactionRunner runner = client.readWriteTransaction(); + assertThrowsSessionNotFoundIfShouldFail( + () -> + runner.run( + transaction -> + transaction.readRowUsingIndex( + "FOO", "IDX", Key.of(), Collections.singletonList("BAR")))); } @Test public void readWriteTransactionUpdate() throws InterruptedException { - invalidateSessionPool(); - try { - TransactionRunner runner = client.readWriteTransaction(); - long count = runner.run(transaction -> transaction.executeUpdate(UPDATE_STATEMENT)); - assertThat(count).isEqualTo(UPDATE_COUNT); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); - } + TransactionRunner runner = client.readWriteTransaction(); + assertThrowsSessionNotFoundIfShouldFail( + () -> runner.run(transaction -> transaction.executeUpdate(UPDATE_STATEMENT))); } @Test public void readWriteTransactionBatchUpdate() throws InterruptedException { - invalidateSessionPool(); - try { - TransactionRunner runner = client.readWriteTransaction(); - long[] count = - runner.run( - transaction -> transaction.batchUpdate(Collections.singletonList(UPDATE_STATEMENT))); - assertThat(count.length).isEqualTo(1); - assertThat(count[0]).isEqualTo(UPDATE_COUNT); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); - } + TransactionRunner runner = client.readWriteTransaction(); + assertThrowsSessionNotFoundIfShouldFail( + () -> + runner.run( + transaction -> + transaction.batchUpdate(Collections.singletonList(UPDATE_STATEMENT)))); } @Test public void readWriteTransactionBuffer() throws InterruptedException { - invalidateSessionPool(); - try { - TransactionRunner runner = client.readWriteTransaction(); - runner.run( - transaction -> { - transaction.buffer(Mutation.newInsertBuilder("FOO").set("BAR").to(1L).build()); - return null; - }); - assertThat(runner.getCommitTimestamp()).isNotNull(); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); - } + TransactionRunner runner = client.readWriteTransaction(); + assertThrowsSessionNotFoundIfShouldFail( + () -> + runner.run( + transaction -> { + transaction.buffer(Mutation.newInsertBuilder("FOO").set("BAR").to(1L).build()); + return null; + })); } @Test public void readWriteTransactionSelectInvalidatedDuringTransaction() { - try { - TransactionRunner runner = client.readWriteTransaction(); - int attempts = - runner.run( - new TransactionCallable() { - private int attempt = 0; - - @Override - public Integer run(TransactionContext transaction) throws Exception { - attempt++; - int count = 0; + TransactionRunner runner = client.readWriteTransaction(); + final AtomicInteger attempt = new AtomicInteger(); + assertThrowsSessionNotFoundIfShouldFail( + () -> + runner.run( + transaction -> { + attempt.incrementAndGet(); try (ResultSet rs = transaction.executeQuery(SELECT1AND2)) { - while (rs.next()) { - count++; - } + while (rs.next()) {} } - assertThat(count).isEqualTo(2); - if (attempt == 1) { - invalidateSessionPool(); + if (attempt.get() == 1) { + invalidateSessionPool( + client, spanner.getOptions().getSessionPoolOptions().getMinSessions()); } try (ResultSet rs = transaction.executeQuery(SELECT1AND2)) { - while (rs.next()) { - count++; - } + while (rs.next()) {} } - return attempt; - } - }); - assertThat(attempts).isGreaterThan(1); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); - } + assertThat(attempt.get()).isGreaterThan(1); + return null; + })); } @Test public void readWriteTransactionReadInvalidatedDuringTransaction() { - try { - TransactionRunner runner = client.readWriteTransaction(); - int attempts = - runner.run( - new TransactionCallable() { - private int attempt = 0; - - @Override - public Integer run(TransactionContext transaction) throws Exception { - attempt++; - int count = 0; + TransactionRunner runner = client.readWriteTransaction(); + final AtomicInteger attempt = new AtomicInteger(); + assertThrowsSessionNotFoundIfShouldFail( + () -> + runner.run( + transaction -> { + attempt.incrementAndGet(); try (ResultSet rs = transaction.read("FOO", KeySet.all(), Collections.singletonList("BAR"))) { - while (rs.next()) { - count++; - } + while (rs.next()) {} } - assertThat(count).isEqualTo(2); - if (attempt == 1) { - invalidateSessionPool(); + if (attempt.get() == 1) { + invalidateSessionPool( + client, spanner.getOptions().getSessionPoolOptions().getMinSessions()); } try (ResultSet rs = transaction.read("FOO", KeySet.all(), Collections.singletonList("BAR"))) { - while (rs.next()) { - count++; - } + while (rs.next()) {} } - return attempt; - } - }); - assertThat(attempts).isGreaterThan(1); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); - } + assertThat(attempt.get()).isGreaterThan(1); + return null; + })); } @Test public void readWriteTransactionReadUsingIndexInvalidatedDuringTransaction() { - try { - TransactionRunner runner = client.readWriteTransaction(); - int attempts = - runner.run( - new TransactionCallable() { - private int attempt = 0; - - @Override - public Integer run(TransactionContext transaction) throws Exception { - attempt++; - int count = 0; + TransactionRunner runner = client.readWriteTransaction(); + final AtomicInteger attempt = new AtomicInteger(); + assertThrowsSessionNotFoundIfShouldFail( + () -> + runner.run( + transaction -> { + attempt.incrementAndGet(); try (ResultSet rs = transaction.readUsingIndex( "FOO", "IDX", KeySet.all(), Collections.singletonList("BAR"))) { - while (rs.next()) { - count++; - } + while (rs.next()) {} } - assertThat(count).isEqualTo(2); - if (attempt == 1) { - invalidateSessionPool(); + if (attempt.get() == 1) { + invalidateSessionPool( + client, spanner.getOptions().getSessionPoolOptions().getMinSessions()); } try (ResultSet rs = transaction.readUsingIndex( "FOO", "IDX", KeySet.all(), Collections.singletonList("BAR"))) { - while (rs.next()) { - count++; - } + while (rs.next()) {} } - return attempt; - } - }); - assertThat(attempts).isGreaterThan(1); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); - } + assertThat(attempt.get()).isGreaterThan(1); + return null; + })); } @Test public void readWriteTransactionReadRowInvalidatedDuringTransaction() { - try { - TransactionRunner runner = client.readWriteTransaction(); - int attempts = - runner.run( - new TransactionCallable() { - private int attempt = 0; - - @Override - public Integer run(TransactionContext transaction) throws Exception { - attempt++; + TransactionRunner runner = client.readWriteTransaction(); + final AtomicInteger attempt = new AtomicInteger(); + assertThrowsSessionNotFoundIfShouldFail( + () -> + runner.run( + transaction -> { + attempt.incrementAndGet(); Struct row = transaction.readRow("FOO", Key.of(), Collections.singletonList("BAR")); assertThat(row.getLong(0)).isEqualTo(1L); - if (attempt == 1) { - invalidateSessionPool(); + if (attempt.get() == 1) { + invalidateSessionPool( + client, spanner.getOptions().getSessionPoolOptions().getMinSessions()); } transaction.readRow("FOO", Key.of(), Collections.singletonList("BAR")); - return attempt; - } - }); - assertThat(attempts).isGreaterThan(1); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); - } + assertThat(attempt.get()).isGreaterThan(1); + return null; + })); } @Test public void readWriteTransactionReadRowUsingIndexInvalidatedDuringTransaction() { - try { - TransactionRunner runner = client.readWriteTransaction(); - int attempts = - runner.run( - new TransactionCallable() { - private int attempt = 0; - - @Override - public Integer run(TransactionContext transaction) throws Exception { - attempt++; + TransactionRunner runner = client.readWriteTransaction(); + final AtomicInteger attempt = new AtomicInteger(); + assertThrowsSessionNotFoundIfShouldFail( + () -> + runner.run( + transaction -> { + attempt.incrementAndGet(); Struct row = transaction.readRowUsingIndex( "FOO", "IDX", Key.of(), Collections.singletonList("BAR")); assertThat(row.getLong(0)).isEqualTo(1L); - if (attempt == 1) { - invalidateSessionPool(); + if (attempt.get() == 1) { + invalidateSessionPool( + client, spanner.getOptions().getSessionPoolOptions().getMinSessions()); } transaction.readRowUsingIndex( "FOO", "IDX", Key.of(), Collections.singletonList("BAR")); - return attempt; - } - }); - assertThat(attempts).isGreaterThan(1); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); - } + assertThat(attempt.get()).isGreaterThan(1); + return null; + })); } - /** - * Test with one read-only session in the pool that is invalidated. The session pool will try to - * prepare this session for read/write, which will fail with a {@link SessionNotFoundException}. - * That again will trigger the creation of a new session. This will always succeed. - */ @SuppressWarnings("resource") @Test public void transactionManagerReadOnlySessionInPool() throws InterruptedException { - SessionPoolOptions.Builder builder = SessionPoolOptions.newBuilder(); - if (failOnInvalidatedSession) { - builder.setFailIfSessionNotFound(); - } - Spanner spanner = - SpannerOptions.newBuilder() - .setProjectId("[PROJECT]") - .setChannelProvider(channelProvider) - .setSessionPoolOption(builder.build()) - .setCredentials(NoCredentials.getInstance()) - .build() - .getService(); - DatabaseClient client = - spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); - invalidateSessionPool(client, spanner.getOptions().getSessionPoolOptions().getMinSessions()); - int count = 0; try (TransactionManager manager = client.transactionManager()) { TransactionContext transaction = manager.begin(); while (true) { try { try (ResultSet rs = transaction.executeQuery(SELECT1AND2)) { - while (rs.next()) { - count++; - } + assertThrowsSessionNotFoundIfShouldFail(() -> rs.next()); } manager.commit(); break; } catch (AbortedException e) { - Thread.sleep(e.getRetryDelayInMillis()); - transaction = manager.resetForRetry(); + transaction = assertThrowsSessionNotFoundIfShouldFail(() -> manager.resetForRetry()); + if (transaction == null) { + break; + } } } - assertThat(count).isEqualTo(2); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); } } @SuppressWarnings("resource") @Test public void transactionManagerSelect() throws InterruptedException { - invalidateSessionPool(); try (TransactionManager manager = client.transactionManager()) { - int count = 0; TransactionContext transaction = manager.begin(); while (true) { try { try (ResultSet rs = transaction.executeQuery(SELECT1AND2)) { - while (rs.next()) { - count++; - } + assertThrowsSessionNotFoundIfShouldFail(() -> rs.next()); } manager.commit(); break; } catch (AbortedException e) { - Thread.sleep(e.getRetryDelayInMillis()); - transaction = manager.resetForRetry(); + transaction = assertThrowsSessionNotFoundIfShouldFail(() -> manager.resetForRetry()); + if (transaction == null) { + break; + } } } - assertThat(count).isEqualTo(2); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); } } @SuppressWarnings("resource") @Test public void transactionManagerRead() throws InterruptedException { - invalidateSessionPool(); try (TransactionManager manager = client.transactionManager()) { - int count = 0; TransactionContext transaction = manager.begin(); while (true) { try { try (ResultSet rs = transaction.read("FOO", KeySet.all(), Collections.singletonList("BAR"))) { - while (rs.next()) { - count++; - } + assertThrowsSessionNotFoundIfShouldFail(() -> rs.next()); } manager.commit(); break; } catch (AbortedException e) { - Thread.sleep(e.getRetryDelayInMillis()); - transaction = manager.resetForRetry(); + transaction = assertThrowsSessionNotFoundIfShouldFail(() -> manager.resetForRetry()); + if (transaction == null) { + break; + } } } - assertThat(count).isEqualTo(2); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); } } @SuppressWarnings("resource") @Test public void transactionManagerReadUsingIndex() throws InterruptedException { - invalidateSessionPool(); try (TransactionManager manager = client.transactionManager()) { - int count = 0; TransactionContext transaction = manager.begin(); while (true) { try { try (ResultSet rs = transaction.readUsingIndex( "FOO", "IDX", KeySet.all(), Collections.singletonList("BAR"))) { - while (rs.next()) { - count++; - } + assertThrowsSessionNotFoundIfShouldFail(() -> rs.next()); } manager.commit(); break; } catch (AbortedException e) { - Thread.sleep(e.getRetryDelayInMillis()); - transaction = manager.resetForRetry(); + transaction = assertThrowsSessionNotFoundIfShouldFail(() -> manager.resetForRetry()); + if (transaction == null) { + break; + } } } - assertThat(count).isEqualTo(2); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); } } - @SuppressWarnings("resource") @Test public void transactionManagerReadRow() throws InterruptedException { - invalidateSessionPool(); try (TransactionManager manager = client.transactionManager()) { - Struct row; TransactionContext transaction = manager.begin(); while (true) { try { - row = transaction.readRow("FOO", Key.of(), Collections.singletonList("BAR")); + TransactionContext context = transaction; + assertThrowsSessionNotFoundIfShouldFail( + () -> context.readRow("FOO", Key.of(), Collections.singletonList("BAR"))); manager.commit(); break; } catch (AbortedException e) { - Thread.sleep(e.getRetryDelayInMillis()); - transaction = manager.resetForRetry(); + transaction = assertThrowsSessionNotFoundIfShouldFail(() -> manager.resetForRetry()); + if (transaction == null) { + break; + } } } - assertThat(row.getLong(0)).isEqualTo(1L); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); } } - @SuppressWarnings("resource") @Test public void transactionManagerReadRowUsingIndex() throws InterruptedException { - invalidateSessionPool(); try (TransactionManager manager = client.transactionManager()) { - Struct row; TransactionContext transaction = manager.begin(); while (true) { try { - row = - transaction.readRowUsingIndex( - "FOO", "IDX", Key.of(), Collections.singletonList("BAR")); + TransactionContext context = transaction; + assertThrowsSessionNotFoundIfShouldFail( + () -> + context.readRowUsingIndex( + "FOO", "IDX", Key.of(), Collections.singletonList("BAR"))); manager.commit(); break; } catch (AbortedException e) { - Thread.sleep(e.getRetryDelayInMillis()); - transaction = manager.resetForRetry(); + transaction = assertThrowsSessionNotFoundIfShouldFail(() -> manager.resetForRetry()); + if (transaction == null) { + break; + } } } - assertThat(row.getLong(0)).isEqualTo(1L); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); } } - @SuppressWarnings("resource") @Test public void transactionManagerUpdate() throws InterruptedException { - invalidateSessionPool(); try (TransactionManager manager = client.transactionManager(Options.commitStats())) { - long count; TransactionContext transaction = manager.begin(); while (true) { try { - count = transaction.executeUpdate(UPDATE_STATEMENT); + TransactionContext context = transaction; + assertThrowsSessionNotFoundIfShouldFail(() -> context.executeUpdate(UPDATE_STATEMENT)); manager.commit(); break; } catch (AbortedException e) { - Thread.sleep(e.getRetryDelayInMillis()); - transaction = manager.resetForRetry(); + transaction = assertThrowsSessionNotFoundIfShouldFail(() -> manager.resetForRetry()); + if (transaction == null) { + break; + } } } - assertEquals(UPDATE_COUNT, count); - assertNotNull(manager.getCommitResponse().getCommitStats()); - assertFalse(failOnInvalidatedSession); - } catch (SessionNotFoundException e) { - assertTrue(failOnInvalidatedSession); } } - @SuppressWarnings("resource") @Test public void transactionManagerAborted_thenSessionNotFoundOnBeginTransaction() throws InterruptedException { int attempt = 0; try (TransactionManager manager = client.transactionManager()) { - long count; TransactionContext transaction = manager.begin(); while (true) { try { @@ -1171,55 +888,50 @@ public void transactionManagerAborted_thenSessionNotFoundOnBeginTransaction() mockSpanner.abortNextStatement(); } if (attempt == 2) { - invalidateSessionPool(); + invalidateSessionPool( + client, spanner.getOptions().getSessionPoolOptions().getMinSessions()); } - count = transaction.executeUpdate(UPDATE_STATEMENT); + TransactionContext context = transaction; + assertThrowsSessionNotFoundIfShouldFail(() -> context.executeUpdate(UPDATE_STATEMENT)); manager.commit(); + // The actual number of attempts depends on when the transaction manager will actually get + // a valid session, as we invalidate the entire session pool. + assertThat(attempt).isAtLeast(3); break; } catch (AbortedException e) { - Thread.sleep(e.getRetryDelayInMillis()); - transaction = manager.resetForRetry(); + transaction = assertThrowsSessionNotFoundIfShouldFail(() -> manager.resetForRetry()); + if (transaction == null) { + break; + } } } - assertThat(count).isEqualTo(UPDATE_COUNT); - assertThat(failOnInvalidatedSession).isFalse(); - // The actual number of attempts depends on when the transaction manager will actually get a - // valid session, as we invalidate the entire session pool. - assertThat(attempt).isAtLeast(3); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); } } - @SuppressWarnings("resource") @Test public void transactionManagerBatchUpdate() throws InterruptedException { - invalidateSessionPool(); try (TransactionManager manager = client.transactionManager()) { - long[] count; TransactionContext transaction = manager.begin(); while (true) { try { - count = transaction.batchUpdate(Collections.singletonList(UPDATE_STATEMENT)); + TransactionContext context = transaction; + assertThrowsSessionNotFoundIfShouldFail( + () -> context.batchUpdate(Collections.singletonList(UPDATE_STATEMENT))); manager.commit(); break; } catch (AbortedException e) { - Thread.sleep(e.getRetryDelayInMillis()); - transaction = manager.resetForRetry(); + transaction = assertThrowsSessionNotFoundIfShouldFail(() -> manager.resetForRetry()); + if (transaction == null) { + break; + } } } - assertThat(count.length).isEqualTo(1); - assertThat(count[0]).isEqualTo(UPDATE_COUNT); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); } } @SuppressWarnings("resource") @Test public void transactionManagerBuffer() throws InterruptedException { - invalidateSessionPool(); try (TransactionManager manager = client.transactionManager()) { TransactionContext transaction = manager.begin(); while (true) { @@ -1228,8 +940,10 @@ public void transactionManagerBuffer() throws InterruptedException { manager.commit(); break; } catch (AbortedException e) { - Thread.sleep(e.getRetryDelayInMillis()); - transaction = manager.resetForRetry(); + transaction = assertThrowsSessionNotFoundIfShouldFail(() -> manager.resetForRetry()); + if (transaction == null) { + break; + } } } assertThat(manager.getCommitTimestamp()).isNotNull(); @@ -1242,78 +956,93 @@ public void transactionManagerBuffer() throws InterruptedException { @SuppressWarnings("resource") @Test public void transactionManagerSelectInvalidatedDuringTransaction() throws InterruptedException { - try (TransactionManager manager = client.transactionManager()) { - int attempts = 0; - TransactionContext transaction = manager.begin(); - while (true) { - attempts++; - int count = 0; - try { - try (ResultSet rs = transaction.executeQuery(SELECT1AND2)) { - while (rs.next()) { - count++; + SessionPoolOptions.Builder builder = SessionPoolOptions.newBuilder(); + if (failOnInvalidatedSession) { + builder.setFailIfSessionNotFound(); + } + try (Spanner spanner = + SpannerOptions.newBuilder() + .setProjectId("[PROJECT]") + .setChannelProvider(channelProvider) + .setSessionPoolOption(builder.build()) + .setCredentials(NoCredentials.getInstance()) + .build() + .getService()) { + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + try (TransactionManager manager = client.transactionManager()) { + int attempts = 0; + TransactionContext transaction = manager.begin(); + while (true) { + attempts++; + try { + try (ResultSet rs = transaction.executeQuery(SELECT1AND2)) { + while (rs.next()) {} } - } - assertThat(count).isEqualTo(2); - if (attempts == 1) { - invalidateSessionPool(); - } - try (ResultSet rs = transaction.executeQuery(SELECT1AND2)) { - while (rs.next()) { - count++; + if (attempts == 1) { + invalidateSessionPool( + client, spanner.getOptions().getSessionPoolOptions().getMinSessions()); + } + try (ResultSet rs = transaction.executeQuery(SELECT1AND2)) { + if (assertThrowsSessionNotFoundIfShouldFail(() -> rs.next()) == null) { + break; + } } + manager.commit(); + assertThat(attempts).isGreaterThan(1); + break; + } catch (AbortedException e) { + transaction = assertThrowsSessionNotFoundIfShouldFail(() -> manager.resetForRetry()); } - manager.commit(); - break; - } catch (AbortedException e) { - Thread.sleep(e.getRetryDelayInMillis()); - transaction = manager.resetForRetry(); } } - assertThat(attempts).isGreaterThan(1); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); } } @SuppressWarnings("resource") @Test public void transactionManagerReadInvalidatedDuringTransaction() throws InterruptedException { - try (TransactionManager manager = client.transactionManager()) { - int attempts = 0; - TransactionContext transaction = manager.begin(); - while (true) { - attempts++; - int count = 0; - try { - try (ResultSet rs = - transaction.read("FOO", KeySet.all(), Collections.singletonList("BAR"))) { - while (rs.next()) { - count++; + SessionPoolOptions.Builder builder = SessionPoolOptions.newBuilder(); + if (failOnInvalidatedSession) { + builder.setFailIfSessionNotFound(); + } + try (Spanner spanner = + SpannerOptions.newBuilder() + .setProjectId("[PROJECT]") + .setChannelProvider(channelProvider) + .setSessionPoolOption(builder.build()) + .setCredentials(NoCredentials.getInstance()) + .build() + .getService()) { + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + try (TransactionManager manager = client.transactionManager()) { + int attempts = 0; + TransactionContext transaction = manager.begin(); + while (true) { + attempts++; + try { + try (ResultSet rs = + transaction.read("FOO", KeySet.all(), Collections.singletonList("BAR"))) { + while (rs.next()) {} } - } - assertThat(count).isEqualTo(2); - if (attempts == 1) { - invalidateSessionPool(); - } - try (ResultSet rs = - transaction.read("FOO", KeySet.all(), Collections.singletonList("BAR"))) { - while (rs.next()) { - count++; + if (attempts == 1) { + invalidateSessionPool( + client, spanner.getOptions().getSessionPoolOptions().getMinSessions()); + } + try (ResultSet rs = + transaction.read("FOO", KeySet.all(), Collections.singletonList("BAR"))) { + if (assertThrowsSessionNotFoundIfShouldFail(() -> rs.next()) == null) { + break; + } } + manager.commit(); + break; + } catch (AbortedException e) { + transaction = manager.resetForRetry(); } - manager.commit(); - break; - } catch (AbortedException e) { - Thread.sleep(e.getRetryDelayInMillis()); - transaction = manager.resetForRetry(); } } - assertThat(attempts).isGreaterThan(1); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); } } @@ -1321,71 +1050,94 @@ public void transactionManagerReadInvalidatedDuringTransaction() throws Interrup @Test public void transactionManagerReadUsingIndexInvalidatedDuringTransaction() throws InterruptedException { - try (TransactionManager manager = client.transactionManager()) { - int attempts = 0; - TransactionContext transaction = manager.begin(); - while (true) { - attempts++; - int count = 0; - try { - try (ResultSet rs = - transaction.readUsingIndex( - "FOO", "IDX", KeySet.all(), Collections.singletonList("BAR"))) { - while (rs.next()) { - count++; + SessionPoolOptions.Builder builder = SessionPoolOptions.newBuilder(); + if (failOnInvalidatedSession) { + builder.setFailIfSessionNotFound(); + } + try (Spanner spanner = + SpannerOptions.newBuilder() + .setProjectId("[PROJECT]") + .setChannelProvider(channelProvider) + .setSessionPoolOption(builder.build()) + .setCredentials(NoCredentials.getInstance()) + .build() + .getService()) { + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + try (TransactionManager manager = client.transactionManager()) { + int attempts = 0; + TransactionContext transaction = manager.begin(); + while (true) { + attempts++; + try { + try (ResultSet rs = + transaction.readUsingIndex( + "FOO", "IDX", KeySet.all(), Collections.singletonList("BAR"))) { + while (rs.next()) {} } - } - assertThat(count).isEqualTo(2); - if (attempts == 1) { - invalidateSessionPool(); - } - try (ResultSet rs = - transaction.readUsingIndex( - "FOO", "IDX", KeySet.all(), Collections.singletonList("BAR"))) { - while (rs.next()) { - count++; + if (attempts == 1) { + invalidateSessionPool( + client, spanner.getOptions().getSessionPoolOptions().getMinSessions()); + } + try (ResultSet rs = + transaction.readUsingIndex( + "FOO", "IDX", KeySet.all(), Collections.singletonList("BAR"))) { + if (assertThrowsSessionNotFoundIfShouldFail(() -> rs.next()) == null) { + break; + } } + manager.commit(); + break; + } catch (AbortedException e) { + transaction = manager.resetForRetry(); } - manager.commit(); - break; - } catch (AbortedException e) { - Thread.sleep(e.getRetryDelayInMillis()); - transaction = manager.resetForRetry(); } } - assertThat(attempts).isGreaterThan(1); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); } } @SuppressWarnings("resource") @Test public void transactionManagerReadRowInvalidatedDuringTransaction() throws InterruptedException { - try (TransactionManager manager = client.transactionManager()) { - int attempts = 0; - TransactionContext transaction = manager.begin(); - while (true) { - attempts++; - try { - Struct row = transaction.readRow("FOO", Key.of(), Collections.singletonList("BAR")); - assertThat(row.getLong(0)).isEqualTo(1L); - if (attempts == 1) { - invalidateSessionPool(); + SessionPoolOptions.Builder builder = SessionPoolOptions.newBuilder(); + if (failOnInvalidatedSession) { + builder.setFailIfSessionNotFound(); + } + try (Spanner spanner = + SpannerOptions.newBuilder() + .setProjectId("[PROJECT]") + .setChannelProvider(channelProvider) + .setSessionPoolOption(builder.build()) + .setCredentials(NoCredentials.getInstance()) + .build() + .getService()) { + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + try (TransactionManager manager = client.transactionManager()) { + int attempts = 0; + TransactionContext transaction = manager.begin(); + while (true) { + attempts++; + try { + Struct row = transaction.readRow("FOO", Key.of(), Collections.singletonList("BAR")); + assertThat(row.getLong(0)).isEqualTo(1L); + if (attempts == 1) { + invalidateSessionPool( + client, spanner.getOptions().getSessionPoolOptions().getMinSessions()); + } + TransactionContext context = transaction; + if (assertThrowsSessionNotFoundIfShouldFail( + () -> context.readRow("FOO", Key.of(), Collections.singletonList("BAR"))) + == null) { + break; + } + manager.commit(); + break; + } catch (AbortedException e) { + transaction = manager.resetForRetry(); } - transaction.readRow("FOO", Key.of(), Collections.singletonList("BAR")); - manager.commit(); - break; - } catch (AbortedException e) { - Thread.sleep(e.getRetryDelayInMillis()); - transaction = manager.resetForRetry(); } } - assertThat(attempts).isGreaterThan(1); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); } } @@ -1393,69 +1145,70 @@ public void transactionManagerReadRowInvalidatedDuringTransaction() throws Inter @Test public void transactionManagerReadRowUsingIndexInvalidatedDuringTransaction() throws InterruptedException { - try (TransactionManager manager = client.transactionManager()) { - int attempts = 0; - TransactionContext transaction = manager.begin(); - while (true) { - attempts++; - try { - Struct row = - transaction.readRowUsingIndex( - "FOO", "IDX", Key.of(), Collections.singletonList("BAR")); - assertThat(row.getLong(0)).isEqualTo(1L); - if (attempts == 1) { - invalidateSessionPool(); + SessionPoolOptions.Builder builder = SessionPoolOptions.newBuilder(); + if (failOnInvalidatedSession) { + builder.setFailIfSessionNotFound(); + } + try (Spanner spanner = + SpannerOptions.newBuilder() + .setProjectId("[PROJECT]") + .setChannelProvider(channelProvider) + .setSessionPoolOption(builder.build()) + .setCredentials(NoCredentials.getInstance()) + .build() + .getService()) { + DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + try (TransactionManager manager = client.transactionManager()) { + int attempts = 0; + TransactionContext transaction = manager.begin(); + while (true) { + attempts++; + try { + Struct row = + transaction.readRowUsingIndex( + "FOO", "IDX", Key.of(), Collections.singletonList("BAR")); + assertThat(row.getLong(0)).isEqualTo(1L); + if (attempts == 1) { + invalidateSessionPool( + client, spanner.getOptions().getSessionPoolOptions().getMinSessions()); + } + TransactionContext context = transaction; + if (assertThrowsSessionNotFoundIfShouldFail( + () -> + context.readRowUsingIndex( + "FOO", "IDX", Key.of(), Collections.singletonList("BAR"))) + == null) { + break; + } + manager.commit(); + break; + } catch (AbortedException e) { + transaction = manager.resetForRetry(); } - transaction.readRowUsingIndex("FOO", "IDX", Key.of(), Collections.singletonList("BAR")); - manager.commit(); - break; - } catch (AbortedException e) { - Thread.sleep(e.getRetryDelayInMillis()); - transaction = manager.resetForRetry(); } } - assertThat(attempts).isGreaterThan(1); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); } } @Test public void partitionedDml() throws InterruptedException { - invalidateSessionPool(); - try { - assertThat(client.executePartitionedUpdate(UPDATE_STATEMENT)).isEqualTo(UPDATE_COUNT); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); - } + assertThrowsSessionNotFoundIfShouldFail( + () -> client.executePartitionedUpdate(UPDATE_STATEMENT)); } @Test public void write() throws InterruptedException { - invalidateSessionPool(); - try { - Timestamp timestamp = - client.write(Collections.singletonList(Mutation.delete("FOO", KeySet.all()))); - assertThat(timestamp).isNotNull(); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); - } + assertThrowsSessionNotFoundIfShouldFail( + () -> client.write(Collections.singletonList(Mutation.delete("FOO", KeySet.all())))); } @Test public void writeAtLeastOnce() throws InterruptedException { - invalidateSessionPool(); - try { - Timestamp timestamp = - client.writeAtLeastOnce(Collections.singletonList(Mutation.delete("FOO", KeySet.all()))); - assertThat(timestamp).isNotNull(); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); - } + assertThrowsSessionNotFoundIfShouldFail( + () -> + client.writeAtLeastOnce( + Collections.singletonList(Mutation.delete("FOO", KeySet.all())))); } @Test @@ -1479,39 +1232,36 @@ public void asyncRunnerReadUsingIndex() throws InterruptedException { private void asyncRunner_withReadFunction( final Function readFunction) throws InterruptedException { - invalidateSessionPool(); final ExecutorService queryExecutor = Executors.newSingleThreadExecutor(); try { AsyncRunner runner = client.runAsync(); final AtomicLong counter = new AtomicLong(); - ApiFuture count = - runner.runAsync( - txn -> { - AsyncResultSet rs = readFunction.apply(txn); - ApiFuture fut = - rs.setCallback( - queryExecutor, - resultSet -> { - while (true) { - switch (resultSet.tryNext()) { - case OK: - counter.incrementAndGet(); - break; - case DONE: - return CallbackResponse.DONE; - case NOT_READY: - return CallbackResponse.CONTINUE; - } - } - }); - return ApiFutures.transform( - fut, input -> counter.get(), MoreExecutors.directExecutor()); - }, - executor); - assertThat(get(count)).isEqualTo(2); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); + assertThrowsSessionNotFoundIfShouldFail( + () -> + get( + runner.runAsync( + txn -> { + AsyncResultSet rs = readFunction.apply(txn); + ApiFuture fut = + rs.setCallback( + queryExecutor, + resultSet -> { + while (true) { + switch (resultSet.tryNext()) { + case OK: + counter.incrementAndGet(); + break; + case DONE: + return CallbackResponse.DONE; + case NOT_READY: + return CallbackResponse.CONTINUE; + } + } + }); + return ApiFutures.transform( + fut, input -> counter.get(), MoreExecutors.directExecutor()); + }, + executor))); } finally { queryExecutor.shutdown(); } @@ -1519,86 +1269,58 @@ private void asyncRunner_withReadFunction( @Test public void asyncRunnerReadRow() throws InterruptedException { - invalidateSessionPool(); - try { - AsyncRunner runner = client.runAsync(); - ApiFuture row = - runner.runAsync( - txn -> txn.readRowAsync("FOO", Key.of(), Collections.singletonList("BAR")), executor); - assertThat(get(row).getLong(0)).isEqualTo(1L); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); - } + AsyncRunner runner = client.runAsync(); + assertThrowsSessionNotFoundIfShouldFail( + () -> + get( + runner.runAsync( + txn -> txn.readRowAsync("FOO", Key.of(), Collections.singletonList("BAR")), + executor))); } @Test public void asyncRunnerReadRowUsingIndex() throws InterruptedException { - invalidateSessionPool(); - try { - AsyncRunner runner = client.runAsync(); - ApiFuture row = - runner.runAsync( - txn -> - txn.readRowUsingIndexAsync( - "FOO", "IDX", Key.of(), Collections.singletonList("BAR")), - executor); - assertThat(get(row).getLong(0)).isEqualTo(1L); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); - } + AsyncRunner runner = client.runAsync(); + assertThrowsSessionNotFoundIfShouldFail( + () -> + get( + runner.runAsync( + txn -> + txn.readRowUsingIndexAsync( + "FOO", "IDX", Key.of(), Collections.singletonList("BAR")), + executor))); } @Test public void asyncRunnerUpdate() throws InterruptedException { - invalidateSessionPool(); - try { - AsyncRunner runner = client.runAsync(); - ApiFuture count = - runner.runAsync(txn -> txn.executeUpdateAsync(UPDATE_STATEMENT), executor); - assertThat(get(count)).isEqualTo(UPDATE_COUNT); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); - } + AsyncRunner runner = client.runAsync(); + assertThrowsSessionNotFoundIfShouldFail( + () -> get(runner.runAsync(txn -> txn.executeUpdateAsync(UPDATE_STATEMENT), executor))); } @Test public void asyncRunnerBatchUpdate() throws InterruptedException { - invalidateSessionPool(); - try { - AsyncRunner runner = client.runAsync(); - ApiFuture count = - runner.runAsync( - txn -> txn.batchUpdateAsync(Arrays.asList(UPDATE_STATEMENT, UPDATE_STATEMENT)), - executor); - assertThat(get(count)).hasLength(2); - assertThat(get(count)).asList().containsExactly(UPDATE_COUNT, UPDATE_COUNT); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); - } + AsyncRunner runner = client.runAsync(); + assertThrowsSessionNotFoundIfShouldFail( + () -> + get( + runner.runAsync( + txn -> txn.batchUpdateAsync(Arrays.asList(UPDATE_STATEMENT, UPDATE_STATEMENT)), + executor))); } @Test public void asyncRunnerBuffer() throws InterruptedException { - invalidateSessionPool(); - try { - AsyncRunner runner = client.runAsync(); - ApiFuture res = - runner.runAsync( - txn -> { - txn.buffer(Mutation.newInsertBuilder("FOO").set("BAR").to(1L).build()); - return ApiFutures.immediateFuture(null); - }, - executor); - assertThat(get(res)).isNull(); - assertThat(get(runner.getCommitTimestamp())).isNotNull(); - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); - } + AsyncRunner runner = client.runAsync(); + assertThrowsSessionNotFoundIfShouldFail( + () -> + get( + runner.runAsync( + txn -> { + txn.buffer(Mutation.newInsertBuilder("FOO").set("BAR").to(1L).build()); + return ApiFutures.immediateFuture(null); + }, + executor))); } @Test @@ -1622,7 +1344,6 @@ public void asyncTransactionManagerAsyncReadUsingIndex() throws InterruptedExcep private void asyncTransactionManager_readAsync( final Function fn) throws InterruptedException { - invalidateSessionPool(); final ExecutorService queryExecutor = Executors.newSingleThreadExecutor(); try (AsyncTransactionManager manager = client.transactionManagerAsync()) { TransactionContextFuture context = manager.beginAsync(); @@ -1654,16 +1375,12 @@ private void asyncTransactionManager_readAsync( }, executor); CommitTimestampFuture ts = count.commitAsync(); - assertThat(get(ts)).isNotNull(); - assertThat(get(count)).isEqualTo(2); - assertThat(failOnInvalidatedSession).isFalse(); + assertThrowsSessionNotFoundIfShouldFail(() -> get(ts)); break; } catch (AbortedException e) { context = manager.resetForRetryAsync(); } } - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); } finally { queryExecutor.shutdown(); } @@ -1689,7 +1406,6 @@ public void asyncTransactionManagerReadUsingIndex() throws InterruptedException private void asyncTransactionManager_readSync(final Function fn) throws InterruptedException { - invalidateSessionPool(); final ExecutorService queryExecutor = Executors.newSingleThreadExecutor(); try (AsyncTransactionManager manager = client.transactionManagerAsync()) { TransactionContextFuture context = manager.beginAsync(); @@ -1708,16 +1424,12 @@ private void asyncTransactionManager_readSync(final Function get(ts)); break; } catch (AbortedException e) { context = manager.resetForRetryAsync(); } } - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); } finally { queryExecutor.shutdown(); } @@ -1756,7 +1468,6 @@ public void asyncTransactionManagerReadRowUsingIndexAsync() throws InterruptedEx private void asyncTransactionManager_readRowFunction( final Function> fn) throws InterruptedException { - invalidateSessionPool(); final ExecutorService queryExecutor = Executors.newSingleThreadExecutor(); try (AsyncTransactionManager manager = client.transactionManagerAsync()) { TransactionContextFuture context = manager.beginAsync(); @@ -1765,16 +1476,12 @@ private void asyncTransactionManager_readRowFunction( AsyncTransactionStep row = context.then((transaction, ignored) -> fn.apply(transaction), executor); CommitTimestampFuture ts = row.commitAsync(); - assertThat(get(ts)).isNotNull(); - assertThat(get(row)).isEqualTo(Struct.newBuilder().set("BAR").to(1L).build()); - assertThat(failOnInvalidatedSession).isFalse(); + assertThrowsSessionNotFoundIfShouldFail(() -> get(ts)); break; } catch (AbortedException e) { context = manager.resetForRetryAsync(); } } - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); } finally { queryExecutor.shutdown(); } @@ -1810,7 +1517,6 @@ public void asyncTransactionManagerBatchUpdate() throws InterruptedException { private void asyncTransactionManager_updateFunction( final Function> fn, T expected) throws InterruptedException { - invalidateSessionPool(); try (AsyncTransactionManager manager = client.transactionManagerAsync()) { TransactionContextFuture transaction = manager.beginAsync(); while (true) { @@ -1818,16 +1524,12 @@ private void asyncTransactionManager_updateFunction( AsyncTransactionStep res = transaction.then((txn, input) -> fn.apply(txn), executor); CommitTimestampFuture ts = res.commitAsync(); - assertThat(get(res)).isEqualTo(expected); - assertThat(get(ts)).isNotNull(); + assertThrowsSessionNotFoundIfShouldFail(() -> get(ts)); break; } catch (AbortedException e) { transaction = manager.resetForRetryAsync(); } } - assertThat(failOnInvalidatedSession).isFalse(); - } catch (SessionNotFoundException e) { - assertThat(failOnInvalidatedSession).isTrue(); } } } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerGaxRetryTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerGaxRetryTest.java index 5dfde3503a..ad45f9039a 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerGaxRetryTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerGaxRetryTest.java @@ -130,8 +130,7 @@ public void setUp() throws Exception { .setChannelProvider(channelProvider) .setCredentials(NoCredentials.getInstance()); // Make sure the session pool is empty by default. - builder.setSessionPoolOption( - SessionPoolOptions.newBuilder().setMinSessions(0).setWriteSessionsFraction(0.0f).build()); + builder.setSessionPoolOption(SessionPoolOptions.newBuilder().setMinSessions(0).build()); // Create one client with default timeout values and one with short timeout values specifically // for the test cases that expect a DEADLINE_EXCEEDED. spanner = builder.build().getService(); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/AbstractMockServerTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/AbstractMockServerTest.java index 154384e2e5..6cf838845b 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/AbstractMockServerTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/AbstractMockServerTest.java @@ -53,7 +53,6 @@ import java.util.List; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; -import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; @@ -113,10 +112,10 @@ public abstract class AbstractMockServerTest { private static Server server; private static InetSocketAddress address; - private boolean futureParentHandlers; - private boolean exceptionRunnableParentHandlers; - private boolean nettyServerParentHandlers; - private boolean clientStreamParentHandlers; + private static boolean futureParentHandlers; + private static boolean exceptionRunnableParentHandlers; + private static boolean nettyServerParentHandlers; + private static boolean clientStreamParentHandlers; @BeforeClass public static void startStaticServer() throws IOException { @@ -152,18 +151,6 @@ public void getOperation( mockSpanner.putStatementResult(StatementResult.update(INSERT_STATEMENT, UPDATE_COUNT)); mockSpanner.putStatementResult( StatementResult.query(SELECT_RANDOM_STATEMENT, RANDOM_RESULT_SET)); - } - - @AfterClass - public static void stopServer() { - server.shutdown(); - } - - @Before - public void setupResults() { - mockSpanner.reset(); - mockDatabaseAdmin.reset(); - mockInstanceAdmin.reset(); futureParentHandlers = Logger.getLogger(AbstractFuture.class.getName()).getUseParentHandlers(); exceptionRunnableParentHandlers = @@ -181,8 +168,8 @@ public void setupResults() { Logger.getLogger("io.grpc.internal.AbstractClientStream").setUseParentHandlers(false); } - @After - public void closeSpannerPool() { + @AfterClass + public static void stopServer() { try { SpannerPool.INSTANCE.checkAndCloseSpanners( CheckAndCloseSpannersMode.ERROR, @@ -196,6 +183,14 @@ public void closeSpannerPool() { Logger.getLogger("io.grpc.internal.AbstractClientStream") .setUseParentHandlers(clientStreamParentHandlers); } + server.shutdown(); + } + + @Before + public void setupResults() { + mockSpanner.clearRequests(); + mockDatabaseAdmin.getRequests().clear(); + mockInstanceAdmin.getRequests().clear(); } protected java.sql.Connection createJdbcConnection() throws SQLException { diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/AbstractSqlScriptVerifier.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/AbstractSqlScriptVerifier.java index 2eb3087089..f247b2d630 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/AbstractSqlScriptVerifier.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/AbstractSqlScriptVerifier.java @@ -28,6 +28,7 @@ import com.google.cloud.Timestamp; import com.google.cloud.spanner.ResultSet; import com.google.cloud.spanner.SpannerException; +import com.google.cloud.spanner.SpannerExceptionFactory; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; @@ -38,6 +39,7 @@ import java.util.Scanner; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Stream; /** * Base class for SQL Script verifiers for both the generic Connection API and JDBC connections @@ -180,8 +182,6 @@ public static List readStatementsFromFile(String filename, Class reso private final GenericConnectionProvider connectionProvider; - private final Map variables = new HashMap<>(); - private final boolean logStatements; /** @@ -212,9 +212,12 @@ public AbstractSqlScriptVerifier(GenericConnectionProvider provider) { * semicolon (;) * @param resourceClass The class that should be used to locate the resource specified by the file * name + * @param allowParallel indicates whether the batches in the given script may be executed in + * parallel */ - public void verifyStatementsInFile(String filename, Class resourceClass) throws Exception { - verifyStatementsInFile(connectionProvider.getConnection(), filename, resourceClass); + public void verifyStatementsInFile(String filename, Class resourceClass, boolean allowParallel) + throws Exception { + verifyStatementsInFile(null, filename, resourceClass, allowParallel); } /** @@ -223,42 +226,80 @@ public void verifyStatementsInFile(String filename, Class resourceClass) thro * Statements without an @EXPECT statement will be executed and its result will be ignored, unless * the statement throws an exception, which will fail the test case. * - * @param connection The {@link com.google.cloud.spanner.jdbc.Connection} to execute the + * @param providedConnection The {@link com.google.cloud.spanner.jdbc.Connection} to execute the * statements against * @param filename The file name containing the statements. Statements must be separated by a * semicolon (;) * @param resourceClass The class that defines the package where to find the input file + * @param allowParallel indicates whether the batches in the given script may be executed in + * parallel */ public void verifyStatementsInFile( - GenericConnection connection, String filename, Class resourceClass) throws Exception { - try { - List statements = readStatementsFromFile(filename, resourceClass); - for (String statement : statements) { - String sql = statement.trim(); - if (logStatements) { - System.out.println( - "\n------------------------------------------------------\n" - + new Date() - + " ---- verifying statement:"); - System.out.println(sql); - } - if (sql.equalsIgnoreCase("NEW_CONNECTION")) { - connection.close(); - connection = connectionProvider.getConnection(); - variables.clear(); - } else { - verifyStatement(connection, sql); + GenericConnection providedConnection, + String filename, + Class resourceClass, + boolean allowParallel) + throws Exception { + List statements = readStatementsFromFile(filename, resourceClass); + List> batches = toBatches(statements); + + Stream> stream; + if (!allowParallel || logStatements) { + stream = batches.stream(); + } else { + stream = batches.parallelStream(); + } + stream.forEach( + batch -> { + try { + Map variables = new HashMap<>(); + GenericConnection connection; + if (providedConnection == null) { + connection = connectionProvider.getConnection(); + } else { + connection = providedConnection; + } + for (String sql : batch) { + if (logStatements) { + System.out.println( + "\n------------------------------------------------------\n" + + new Date() + + " ---- verifying statement:"); + System.out.println(sql); + } + verifyStatement(variables, connection, sql); + } + connection.close(); + } catch (Exception e) { + throw SpannerExceptionFactory.asSpannerException(e); + } + }); + } + + private List> toBatches(List statements) { + List> batches = new ArrayList<>(); + List currentBatch = new ArrayList<>(); + for (String statement : statements) { + String sql = statement.trim(); + if (sql.equalsIgnoreCase("NEW_CONNECTION")) { + if (!currentBatch.isEmpty()) { + batches.add(currentBatch); } - } - } finally { - if (connection != null) { - connection.close(); + currentBatch = new ArrayList<>(); + } else { + currentBatch.add(sql); } } + if (!currentBatch.isEmpty()) { + batches.add(currentBatch); + } + return batches; } - private void verifyStatement(GenericConnection connection, String statement) throws Exception { - statement = replaceVariables(statement); + private void verifyStatement( + Map variables, GenericConnection connection, String statement) + throws Exception { + statement = replaceVariables(variables, statement); String statementWithoutComments = StatementParser.removeCommentsAndTrim(statement); Matcher verifyMatcher = VERIFY_PATTERN.matcher(statementWithoutComments); Matcher putMatcher = PUT_PATTERN.matcher(statementWithoutComments); @@ -350,7 +391,7 @@ private void verifyStatement(GenericConnection connection, String statement) thr } } - private String replaceVariables(String sql) { + private String replaceVariables(Map variables, String sql) { for (String key : variables.keySet()) { sql = sql.replaceAll("%%" + key + "%%", variables.get(key).toString()); } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ClientSideStatementsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ClientSideStatementsTest.java index b36da9c7ac..eab1424814 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ClientSideStatementsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ClientSideStatementsTest.java @@ -49,7 +49,7 @@ public class ClientSideStatementsTest { @Test public void testExecuteClientSideStatementsScript() throws Exception { SqlScriptVerifier verifier = new SqlScriptVerifier(new TestConnectionProvider()); - verifier.verifyStatementsInFile("ClientSideStatementsTest.sql", getClass()); + verifier.verifyStatementsInFile("ClientSideStatementsTest.sql", getClass(), true); } private static final String SCRIPT_FILE = diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionAsyncApiTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionAsyncApiTest.java index ca16259f68..ff409e88c6 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionAsyncApiTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionAsyncApiTest.java @@ -18,6 +18,9 @@ import static com.google.cloud.spanner.SpannerApiFutures.get; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.fail; import com.google.api.core.ApiFuture; @@ -26,18 +29,21 @@ import com.google.cloud.spanner.AsyncResultSet.CallbackResponse; import com.google.cloud.spanner.AsyncResultSet.ReadyCallback; import com.google.cloud.spanner.ErrorCode; +import com.google.cloud.spanner.ForceCloseSpannerFunction; import com.google.cloud.spanner.MockSpannerServiceImpl.SimulatedExecutionTime; import com.google.cloud.spanner.Mutation; import com.google.cloud.spanner.ResultSet; import com.google.cloud.spanner.SpannerApiFutures; import com.google.cloud.spanner.SpannerException; import com.google.cloud.spanner.Statement; +import com.google.cloud.spanner.connection.SpannerPool.CheckAndCloseSpannersMode; import com.google.cloud.spanner.connection.StatementResult.ResultType; import com.google.common.base.Function; import com.google.common.collect.Collections2; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.protobuf.AbstractMessage; +import com.google.spanner.v1.CommitRequest; import com.google.spanner.v1.ExecuteBatchDmlRequest; import com.google.spanner.v1.ExecuteSqlRequest; import java.util.List; @@ -54,7 +60,7 @@ @RunWith(JUnit4.class) public class ConnectionAsyncApiTest extends AbstractMockServerTest { - private static final ExecutorService executor = Executors.newSingleThreadExecutor(); + private static ExecutorService executor = Executors.newSingleThreadExecutor(); private static final Function AUTOCOMMIT = input -> { input.setAutocommit(true); @@ -75,6 +81,8 @@ public static void stopExecutor() { @After public void reset() { mockSpanner.removeAllExecutionTimes(); + executor.shutdownNow(); + executor = Executors.newSingleThreadExecutor(); } @Test @@ -258,20 +266,32 @@ public CallbackResponse cursorReady(AsyncResultSet resultSet) { } }); } - connection.commitAsync(); + ApiFuture commit = connection.commitAsync(); assertThat(get(update1)).isEqualTo(UPDATE_COUNT); assertThat(get(update2)).isEqualTo(UPDATE_COUNT); assertThat(get(batch)).asList().containsExactly(1L, 1L); assertThat(get(rowCount)).isEqualTo(RANDOM_RESULT_SET_ROW_COUNT); + assertNull(get(commit)); + // Get the last commit request. + CommitRequest commitRequest = + mockSpanner.getRequestsOfType(CommitRequest.class).stream() + .reduce((first, second) -> second) + .get(); // Verify the order of the statements on the server. List requests = Lists.newArrayList( Collections2.filter( mockSpanner.getRequests(), input -> - input instanceof ExecuteSqlRequest - || input instanceof ExecuteBatchDmlRequest)); + (input instanceof ExecuteSqlRequest + && ((ExecuteSqlRequest) input) + .getSession() + .equals(commitRequest.getSession())) + || (input instanceof ExecuteBatchDmlRequest + && ((ExecuteBatchDmlRequest) input) + .getSession() + .equals(commitRequest.getSession())))); assertThat(requests).hasSize(4); assertThat(requests.get(0)).isInstanceOf(ExecuteSqlRequest.class); assertThat(((ExecuteSqlRequest) requests.get(0)).getSeqno()).isEqualTo(1L); @@ -326,12 +346,13 @@ public void testExecuteDdlAsync() { @Test public void testExecuteInvalidStatementAsync() { try (Connection connection = createConnection()) { - try { - connection.executeAsync(Statement.of("UPSERT INTO FOO (ID, VAL) VALUES (1, 'foo')")); - fail("Missing expected exception"); - } catch (SpannerException e) { - assertThat(e.getErrorCode()).isEqualTo(ErrorCode.INVALID_ARGUMENT); - } + SpannerException e = + assertThrows( + SpannerException.class, + () -> + connection.executeAsync( + Statement.of("UPSERT INTO FOO (ID, VAL) VALUES (1, 'foo')"))); + assertEquals(ErrorCode.INVALID_ARGUMENT, e.getErrorCode()); } } @@ -657,6 +678,10 @@ private void testExecuteBatchUpdate(Function connectionConfigu } } } + // Close the Spanner pool to prevent requests from this test from interfering with other tests. + SpannerPool.INSTANCE.checkAndCloseSpanners( + CheckAndCloseSpannersMode.ERROR, + new ForceCloseSpannerFunction(100L, TimeUnit.MILLISECONDS)); } private void testWriteAsync(Function connectionConfigurator) { diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionImplGeneratedSqlScriptTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionImplGeneratedSqlScriptTest.java index 90dc3ad9bb..bfa023c1dd 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionImplGeneratedSqlScriptTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionImplGeneratedSqlScriptTest.java @@ -63,7 +63,7 @@ public GenericConnection getConnection() { @Test public void testGeneratedScript() throws Exception { SqlScriptVerifier verifier = new SqlScriptVerifier(new TestConnectionProvider()); - verifier.verifyStatementsInFile("ConnectionImplGeneratedSqlScriptTest.sql", getClass()); + verifier.verifyStatementsInFile("ConnectionImplGeneratedSqlScriptTest.sql", getClass(), true); } /** diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/SetReadOnlyStalenessSqlScriptTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/SetReadOnlyStalenessSqlScriptTest.java index a317630ebd..25aad948fe 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/SetReadOnlyStalenessSqlScriptTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/SetReadOnlyStalenessSqlScriptTest.java @@ -42,6 +42,6 @@ public GenericConnection getConnection() { @Test public void testSetReadOnlyStalenessScript() throws Exception { SqlScriptVerifier verifier = new SqlScriptVerifier(new TestConnectionProvider()); - verifier.verifyStatementsInFile("SetReadOnlyStalenessTest.sql", getClass()); + verifier.verifyStatementsInFile("SetReadOnlyStalenessTest.sql", getClass(), true); } } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/SetStatementTimeoutSqlScriptTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/SetStatementTimeoutSqlScriptTest.java index ea70384d96..c47fde41c1 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/SetStatementTimeoutSqlScriptTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/SetStatementTimeoutSqlScriptTest.java @@ -42,6 +42,6 @@ public GenericConnection getConnection() { @Test public void testSetStatementTimeoutScript() throws Exception { SqlScriptVerifier verifier = new SqlScriptVerifier(new TestConnectionProvider()); - verifier.verifyStatementsInFile("SetStatementTimeoutTest.sql", getClass()); + verifier.verifyStatementsInFile("SetStatementTimeoutTest.sql", getClass(), true); } } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/StatementTimeoutTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/StatementTimeoutTest.java index 87fde11f9b..08f47c0c5d 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/StatementTimeoutTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/StatementTimeoutTest.java @@ -18,11 +18,13 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import com.google.api.core.SettableApiFuture; import com.google.api.gax.longrunning.OperationTimedPollAlgorithm; import com.google.api.gax.retrying.RetrySettings; import com.google.cloud.spanner.ErrorCode; @@ -33,12 +35,14 @@ import com.google.cloud.spanner.Statement; import com.google.cloud.spanner.connection.AbstractConnectionImplTest.ConnectionConsumer; import com.google.cloud.spanner.connection.ITAbstractSpannerTest.ITConnection; -import com.google.common.util.concurrent.Uninterruptibles; +import com.google.common.base.Stopwatch; +import com.google.common.collect.Collections2; import com.google.longrunning.Operation; import com.google.protobuf.AbstractMessage; import com.google.protobuf.Any; import com.google.protobuf.Empty; import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata; +import com.google.spanner.admin.database.v1.UpdateDatabaseDdlRequest; import com.google.spanner.v1.CommitRequest; import com.google.spanner.v1.ExecuteSqlRequest; import io.grpc.Status; @@ -115,12 +119,10 @@ public void testTimeoutExceptionReadOnlyAutocommit() { connection.setAutocommit(true); connection.setReadOnly(true); connection.setStatementTimeout(TIMEOUT_FOR_SLOW_STATEMENTS, TimeUnit.MILLISECONDS); - try { - connection.executeQuery(SELECT_RANDOM_STATEMENT); - fail("missing expected exception"); - } catch (SpannerException ex) { - assertEquals(ErrorCode.DEADLINE_EXCEEDED, ex.getErrorCode()); - } + SpannerException e = + assertThrows( + SpannerException.class, () -> connection.executeQuery(SELECT_RANDOM_STATEMENT)); + assertEquals(ErrorCode.DEADLINE_EXCEEDED, e.getErrorCode()); } } @@ -135,18 +137,16 @@ public void testTimeoutExceptionReadOnlyAutocommitMultipleStatements() { connection.setStatementTimeout(TIMEOUT_FOR_SLOW_STATEMENTS, TimeUnit.MILLISECONDS); // assert that multiple statements after each other also time out for (int i = 0; i < 2; i++) { - try { - connection.executeQuery(SELECT_RANDOM_STATEMENT); - fail("missing expected exception"); - } catch (SpannerException e) { - assertEquals(ErrorCode.DEADLINE_EXCEEDED, e.getErrorCode()); - } + SpannerException e = + assertThrows( + SpannerException.class, () -> connection.executeQuery(SELECT_RANDOM_STATEMENT)); + assertEquals(ErrorCode.DEADLINE_EXCEEDED, e.getErrorCode()); } // try to do a new query that is fast. mockSpanner.removeAllExecutionTimes(); connection.setStatementTimeout(TIMEOUT_FOR_FAST_STATEMENTS, TimeUnit.MILLISECONDS); try (ResultSet rs = connection.executeQuery(SELECT_RANDOM_STATEMENT)) { - assertThat(rs, is(notNullValue())); + assertNotNull(rs); } } } @@ -160,12 +160,10 @@ public void testTimeoutExceptionReadOnlyTransactional() { connection.setReadOnly(true); connection.setAutocommit(false); connection.setStatementTimeout(TIMEOUT_FOR_SLOW_STATEMENTS, TimeUnit.MILLISECONDS); - try { - connection.executeQuery(SELECT_RANDOM_STATEMENT); - fail("missing expected exception"); - } catch (SpannerException ex) { - assertEquals(ErrorCode.DEADLINE_EXCEEDED, ex.getErrorCode()); - } + SpannerException e = + assertThrows( + SpannerException.class, () -> connection.executeQuery(SELECT_RANDOM_STATEMENT)); + assertEquals(ErrorCode.DEADLINE_EXCEEDED, e.getErrorCode()); } } @@ -180,12 +178,10 @@ public void testTimeoutExceptionReadOnlyTransactionMultipleStatements() { connection.setStatementTimeout(TIMEOUT_FOR_SLOW_STATEMENTS, TimeUnit.MILLISECONDS); // assert that multiple statements after each other also time out for (int i = 0; i < 2; i++) { - try { - connection.executeQuery(SELECT_RANDOM_STATEMENT); - fail("missing expected exception"); - } catch (SpannerException e) { - assertEquals(ErrorCode.DEADLINE_EXCEEDED, e.getErrorCode()); - } + SpannerException e = + assertThrows( + SpannerException.class, () -> connection.executeQuery(SELECT_RANDOM_STATEMENT)); + assertEquals(ErrorCode.DEADLINE_EXCEEDED, e.getErrorCode()); } // do a rollback without any chance of a timeout connection.clearStatementTimeout(); @@ -194,7 +190,7 @@ public void testTimeoutExceptionReadOnlyTransactionMultipleStatements() { mockSpanner.removeAllExecutionTimes(); connection.setStatementTimeout(TIMEOUT_FOR_FAST_STATEMENTS, TimeUnit.MILLISECONDS); try (ResultSet rs = connection.executeQuery(SELECT_RANDOM_STATEMENT)) { - assertThat(rs, is(notNullValue())); + assertNotNull(rs); } } } @@ -207,12 +203,10 @@ public void testTimeoutExceptionReadWriteAutocommit() { try (Connection connection = createConnection()) { connection.setAutocommit(true); connection.setStatementTimeout(TIMEOUT_FOR_SLOW_STATEMENTS, TimeUnit.MILLISECONDS); - try { - connection.executeQuery(SELECT_RANDOM_STATEMENT); - fail("missing expected exception"); - } catch (SpannerException ex) { - assertEquals(ErrorCode.DEADLINE_EXCEEDED, ex.getErrorCode()); - } + SpannerException e = + assertThrows( + SpannerException.class, () -> connection.executeQuery(SELECT_RANDOM_STATEMENT)); + assertEquals(ErrorCode.DEADLINE_EXCEEDED, e.getErrorCode()); } } @@ -226,18 +220,16 @@ public void testTimeoutExceptionReadWriteAutocommitMultipleStatements() { connection.setStatementTimeout(TIMEOUT_FOR_SLOW_STATEMENTS, TimeUnit.MILLISECONDS); // assert that multiple statements after each other also time out for (int i = 0; i < 2; i++) { - try { - connection.executeQuery(SELECT_RANDOM_STATEMENT); - fail("missing expected exception"); - } catch (SpannerException e) { - assertEquals(ErrorCode.DEADLINE_EXCEEDED, e.getErrorCode()); - } + SpannerException e = + assertThrows( + SpannerException.class, () -> connection.executeQuery(SELECT_RANDOM_STATEMENT)); + assertEquals(ErrorCode.DEADLINE_EXCEEDED, e.getErrorCode()); } // try to do a new query that is fast. mockSpanner.removeAllExecutionTimes(); connection.setStatementTimeout(TIMEOUT_FOR_FAST_STATEMENTS, TimeUnit.MILLISECONDS); try (ResultSet rs = connection.executeQuery(SELECT_RANDOM_STATEMENT)) { - assertThat(rs, is(notNullValue())); + assertNotNull(rs); } } } @@ -250,12 +242,9 @@ public void testTimeoutExceptionReadWriteAutocommitSlowUpdate() { try (Connection connection = createConnection()) { connection.setAutocommit(true); connection.setStatementTimeout(TIMEOUT_FOR_SLOW_STATEMENTS, TimeUnit.MILLISECONDS); - try { - connection.execute(INSERT_STATEMENT); - fail("Missing expected exception"); - } catch (SpannerException ex) { - assertEquals(ErrorCode.DEADLINE_EXCEEDED, ex.getErrorCode()); - } + SpannerException e = + assertThrows(SpannerException.class, () -> connection.execute(INSERT_STATEMENT)); + assertEquals(ErrorCode.DEADLINE_EXCEEDED, e.getErrorCode()); } } @@ -270,17 +259,15 @@ public void testTimeoutExceptionReadWriteAutocommitSlowUpdateMultipleStatements( // assert that multiple statements after each other also time out for (int i = 0; i < 2; i++) { - try { - connection.execute(Statement.of(SLOW_UPDATE)); - fail("missing expected exception"); - } catch (SpannerException e) { - assertEquals(ErrorCode.DEADLINE_EXCEEDED, e.getErrorCode()); - } + SpannerException e = + assertThrows( + SpannerException.class, () -> connection.execute(Statement.of(SLOW_UPDATE))); + assertEquals(ErrorCode.DEADLINE_EXCEEDED, e.getErrorCode()); } // try to do a new update that is fast. mockSpanner.removeAllExecutionTimes(); connection.setStatementTimeout(TIMEOUT_FOR_FAST_STATEMENTS, TimeUnit.MILLISECONDS); - assertThat(connection.execute(INSERT_STATEMENT).getUpdateCount(), is(equalTo(UPDATE_COUNT))); + assertEquals(UPDATE_COUNT, connection.execute(INSERT_STATEMENT).getUpdateCount().longValue()); } } @@ -301,12 +288,9 @@ public void testTimeoutExceptionReadWriteAutocommitSlowCommit() { // gRPC call will be slow. connection.setStatementTimeout(TIMEOUT_FOR_SLOW_STATEMENTS, TimeUnit.MILLISECONDS); connection.setAutocommit(true); - try { - connection.execute(INSERT_STATEMENT); - fail("missing expected exception"); - } catch (SpannerException ex) { - assertEquals(ErrorCode.DEADLINE_EXCEEDED, ex.getErrorCode()); - } + SpannerException e = + assertThrows(SpannerException.class, () -> connection.execute(INSERT_STATEMENT)); + assertEquals(ErrorCode.DEADLINE_EXCEEDED, e.getErrorCode()); } } @@ -320,18 +304,15 @@ public void testTimeoutExceptionReadWriteAutocommitSlowCommitMultipleStatements( connection.setStatementTimeout(TIMEOUT_FOR_SLOW_STATEMENTS, TimeUnit.MILLISECONDS); // assert that multiple statements after each other also time out for (int i = 0; i < 2; i++) { - try { - connection.execute(INSERT_STATEMENT); - fail("Missing expected exception"); - } catch (SpannerException e) { - assertThat(e.getErrorCode(), is(equalTo(ErrorCode.DEADLINE_EXCEEDED))); - } + SpannerException e = + assertThrows(SpannerException.class, () -> connection.execute(INSERT_STATEMENT)); + assertEquals(ErrorCode.DEADLINE_EXCEEDED, e.getErrorCode()); } // try to do a query in autocommit mode. This will use a single-use read-only transaction that // does not need to commit, i.e. it should succeed. connection.setStatementTimeout(TIMEOUT_FOR_FAST_STATEMENTS, TimeUnit.MILLISECONDS); try (ResultSet rs = connection.executeQuery(SELECT_RANDOM_STATEMENT)) { - assertThat(rs, is(notNullValue())); + assertNotNull(rs); } } } @@ -350,12 +331,9 @@ public void testTimeoutExceptionReadWriteAutocommitPartitioned() { mockSpanner.setExecuteStreamingSqlExecutionTime( SimulatedExecutionTime.ofMinimumAndRandomTime(EXECUTION_TIME_SLOW_STATEMENT, 0)); connection.setStatementTimeout(TIMEOUT_FOR_SLOW_STATEMENTS, TimeUnit.MILLISECONDS); - try { - connection.execute(INSERT_STATEMENT); - fail("Missing expected exception"); - } catch (SpannerException ex) { - assertEquals(ErrorCode.DEADLINE_EXCEEDED, ex.getErrorCode()); - } + SpannerException e = + assertThrows(SpannerException.class, () -> connection.execute(INSERT_STATEMENT)); + assertEquals(ErrorCode.DEADLINE_EXCEEDED, e.getErrorCode()); } } @@ -367,12 +345,10 @@ public void testTimeoutExceptionReadWriteTransactional() { try (Connection connection = createConnection()) { connection.setAutocommit(false); connection.setStatementTimeout(TIMEOUT_FOR_SLOW_STATEMENTS, TimeUnit.MILLISECONDS); - try { - connection.executeQuery(SELECT_RANDOM_STATEMENT); - fail("Missing expected exception"); - } catch (SpannerException ex) { - assertEquals(ErrorCode.DEADLINE_EXCEEDED, ex.getErrorCode()); - } + SpannerException e = + assertThrows( + SpannerException.class, () -> connection.executeQuery(SELECT_RANDOM_STATEMENT)); + assertEquals(ErrorCode.DEADLINE_EXCEEDED, e.getErrorCode()); } } @@ -387,15 +363,13 @@ public void testTimeoutExceptionReadWriteTransactionMultipleStatements() { // Assert that multiple statements after each other will timeout the first time, and then // throw a SpannerException with code FAILED_PRECONDITION. for (int i = 0; i < 2; i++) { - try { - connection.executeQuery(SELECT_RANDOM_STATEMENT); - fail("Missing expected exception"); - } catch (SpannerException e) { - if (i == 0) { - assertThat(e.getErrorCode(), is(equalTo(ErrorCode.DEADLINE_EXCEEDED))); - } else { - assertThat(e.getErrorCode(), is(equalTo(ErrorCode.FAILED_PRECONDITION))); - } + SpannerException e = + assertThrows( + SpannerException.class, () -> connection.executeQuery(SELECT_RANDOM_STATEMENT)); + if (i == 0) { + assertEquals(ErrorCode.DEADLINE_EXCEEDED, e.getErrorCode()); + } else { + assertEquals(ErrorCode.FAILED_PRECONDITION, e.getErrorCode()); } } // do a rollback without any chance of a timeout @@ -405,7 +379,7 @@ public void testTimeoutExceptionReadWriteTransactionMultipleStatements() { mockSpanner.removeAllExecutionTimes(); connection.setStatementTimeout(TIMEOUT_FOR_FAST_STATEMENTS, TimeUnit.MILLISECONDS); try (ResultSet rs = connection.executeQuery(SELECT_RANDOM_STATEMENT)) { - assertThat(rs, is(notNullValue())); + assertNotNull(rs); } } } @@ -420,16 +394,12 @@ public void testTimeoutExceptionReadWriteTransactionalSlowCommit() { connection.setStatementTimeout(TIMEOUT_FOR_FAST_STATEMENTS, TimeUnit.MILLISECONDS); try (ResultSet rs = connection.executeQuery(SELECT_RANDOM_STATEMENT)) { - assertThat(rs, is(notNullValue())); + assertNotNull(rs); } connection.setStatementTimeout(TIMEOUT_FOR_SLOW_STATEMENTS, TimeUnit.MILLISECONDS); - try { - connection.commit(); - fail("Missing expected exception"); - } catch (SpannerException ex) { - assertEquals(ErrorCode.DEADLINE_EXCEEDED, ex.getErrorCode()); - } + SpannerException e = assertThrows(SpannerException.class, () -> connection.commit()); + assertEquals(ErrorCode.DEADLINE_EXCEEDED, e.getErrorCode()); } } @@ -443,7 +413,7 @@ public void testTimeoutExceptionReadWriteTransactionalSlowRollback() { connection.setStatementTimeout(TIMEOUT_FOR_FAST_STATEMENTS, TimeUnit.MILLISECONDS); try (ResultSet rs = connection.executeQuery(SELECT_RANDOM_STATEMENT)) { - assertThat(rs, is(notNullValue())); + assertNotNull(rs); } connection.setStatementTimeout(TIMEOUT_FOR_SLOW_STATEMENTS, TimeUnit.MILLISECONDS); // Rollback timeouts are not propagated as exceptions, as all errors during a Rollback RPC are @@ -513,25 +483,32 @@ private void testInterruptedException(final ConnectionConsumer consumer) mockSpanner.setExecuteStreamingSqlExecutionTime( SimulatedExecutionTime.ofMinimumAndRandomTime(EXECUTION_TIME_SLOW_STATEMENT, 0)); - final CountDownLatch latch = new CountDownLatch(1); + CountDownLatch latch = new CountDownLatch(1); + SettableApiFuture thread = SettableApiFuture.create(); ExecutorService executor = Executors.newSingleThreadExecutor(); - Future future = - executor.submit( - () -> { - try (Connection connection = createConnection()) { - consumer.accept(connection); - connection.setStatementTimeout(10000L, TimeUnit.MILLISECONDS); - - latch.countDown(); - try (ResultSet rs = connection.executeQuery(SELECT_RANDOM_STATEMENT)) {} - return false; - } catch (SpannerException e) { - return e.getErrorCode() == ErrorCode.CANCELLED; - } - }); - latch.await(10L, TimeUnit.SECONDS); - executor.shutdownNow(); - assertThat(future.get(), is(true)); + try { + Future future = + executor.submit( + () -> { + try (Connection connection = createConnection()) { + consumer.accept(connection); + connection.setStatementTimeout(10000L, TimeUnit.MILLISECONDS); + + thread.set(Thread.currentThread()); + latch.countDown(); + try (ResultSet rs = connection.executeQuery(SELECT_RANDOM_STATEMENT)) {} + return false; + } catch (SpannerException e) { + return e.getErrorCode() == ErrorCode.CANCELLED; + } + }); + latch.await(10L, TimeUnit.SECONDS); + waitForRequestsToContain(ExecuteSqlRequest.class); + thread.get().interrupt(); + assertTrue(future.get()); + } finally { + executor.shutdownNow(); + } } @Test @@ -543,12 +520,10 @@ public void testInvalidQueryReadOnlyAutocommit() { connection.setAutocommit(true); connection.setReadOnly(true); connection.setStatementTimeout(TIMEOUT_FOR_FAST_STATEMENTS, TimeUnit.MILLISECONDS); - try { - connection.executeQuery(Statement.of(INVALID_SELECT)); - fail("Missing expected exception"); - } catch (SpannerException ex) { - assertEquals(ErrorCode.INVALID_ARGUMENT, ex.getErrorCode()); - } + SpannerException e = + assertThrows( + SpannerException.class, () -> connection.executeQuery(Statement.of(INVALID_SELECT))); + assertEquals(ErrorCode.INVALID_ARGUMENT, e.getErrorCode()); } } @@ -561,12 +536,10 @@ public void testInvalidQueryReadOnlyTransactional() { connection.setReadOnly(true); connection.setAutocommit(false); connection.setStatementTimeout(TIMEOUT_FOR_FAST_STATEMENTS, TimeUnit.MILLISECONDS); - try { - connection.executeQuery(Statement.of(INVALID_SELECT)); - fail("Missing expected exception"); - } catch (SpannerException ex) { - assertEquals(ErrorCode.INVALID_ARGUMENT, ex.getErrorCode()); - } + SpannerException e = + assertThrows( + SpannerException.class, () -> connection.executeQuery(Statement.of(INVALID_SELECT))); + assertEquals(ErrorCode.INVALID_ARGUMENT, e.getErrorCode()); } } @@ -578,12 +551,10 @@ public void testInvalidQueryReadWriteAutocommit() { try (Connection connection = createConnection()) { connection.setAutocommit(true); connection.setStatementTimeout(TIMEOUT_FOR_FAST_STATEMENTS, TimeUnit.MILLISECONDS); - try { - connection.executeQuery(Statement.of(INVALID_SELECT)); - fail("Missing expected exception"); - } catch (SpannerException ex) { - assertEquals(ErrorCode.INVALID_ARGUMENT, ex.getErrorCode()); - } + SpannerException e = + assertThrows( + SpannerException.class, () -> connection.executeQuery(Statement.of(INVALID_SELECT))); + assertEquals(ErrorCode.INVALID_ARGUMENT, e.getErrorCode()); } } @@ -595,12 +566,10 @@ public void testInvalidQueryReadWriteTransactional() { try (Connection connection = createConnection()) { connection.setAutocommit(false); connection.setStatementTimeout(TIMEOUT_FOR_FAST_STATEMENTS, TimeUnit.MILLISECONDS); - try { - connection.executeQuery(Statement.of(INVALID_SELECT)); - fail("Missing expected exception"); - } catch (SpannerException ex) { - assertEquals(ErrorCode.INVALID_ARGUMENT, ex.getErrorCode()); - } + SpannerException e = + assertThrows( + SpannerException.class, () -> connection.executeQuery(Statement.of(INVALID_SELECT))); + assertEquals(ErrorCode.INVALID_ARGUMENT, e.getErrorCode()); } } @@ -614,28 +583,48 @@ static void waitForRequestsToContain(Class request) { } } + private void waitForDdlRequestOnServer() { + try { + Stopwatch watch = Stopwatch.createStarted(); + while (Collections2.filter( + mockDatabaseAdmin.getRequests(), + input -> input.getClass().equals(UpdateDatabaseDdlRequest.class)) + .size() + == 0) { + Thread.sleep(1L); + if (watch.elapsed(TimeUnit.MILLISECONDS) > EXECUTION_TIME_SLOW_STATEMENT) { + throw new TimeoutException("Timeout while waiting for DDL request"); + } + } + } catch (InterruptedException e) { + throw SpannerExceptionFactory.propagateInterrupt(e); + } catch (TimeoutException e) { + throw SpannerExceptionFactory.propagateTimeout(e); + } + } + @Test public void testCancelReadOnlyAutocommit() { mockSpanner.setExecuteStreamingSqlExecutionTime( SimulatedExecutionTime.ofMinimumAndRandomTime(EXECUTION_TIME_SLOW_STATEMENT, 0)); - ExecutorService executor = Executors.newSingleThreadExecutor(); try (Connection connection = createConnection()) { connection.setAutocommit(true); connection.setReadOnly(true); - executor.execute( - () -> { - waitForRequestsToContain(ExecuteSqlRequest.class); - connection.cancel(); - }); + ExecutorService executor = Executors.newSingleThreadExecutor(); try { - connection.executeQuery(SELECT_RANDOM_STATEMENT); - fail("Missing expected exception"); - } catch (SpannerException ex) { - assertEquals(ErrorCode.CANCELLED, ex.getErrorCode()); + executor.execute( + () -> { + waitForRequestsToContain(ExecuteSqlRequest.class); + connection.cancel(); + }); + SpannerException e = + assertThrows( + SpannerException.class, () -> connection.executeQuery(SELECT_RANDOM_STATEMENT)); + assertEquals(ErrorCode.CANCELLED, e.getErrorCode()); + } finally { + executor.shutdownNow(); } - } finally { - executor.shutdown(); } } @@ -644,29 +633,30 @@ public void testCancelReadOnlyAutocommitMultipleStatements() { mockSpanner.setExecuteStreamingSqlExecutionTime( SimulatedExecutionTime.ofMinimumAndRandomTime(EXECUTION_TIME_SLOW_STATEMENT, 0)); - ExecutorService executor = Executors.newSingleThreadExecutor(); try (Connection connection = createConnection()) { connection.setAutocommit(true); connection.setReadOnly(true); - executor.execute( - () -> { - waitForRequestsToContain(ExecuteSqlRequest.class); - connection.cancel(); - }); + ExecutorService executor = Executors.newSingleThreadExecutor(); + try { + executor.execute( + () -> { + waitForRequestsToContain(ExecuteSqlRequest.class); + connection.cancel(); + }); - try (ResultSet rs = connection.executeQuery(SELECT_RANDOM_STATEMENT)) { - fail("Missing expected exception"); - } catch (SpannerException e) { + SpannerException e = + assertThrows( + SpannerException.class, () -> connection.executeQuery(SELECT_RANDOM_STATEMENT)); assertThat(e.getErrorCode(), is(equalTo(ErrorCode.CANCELLED))); - } - mockSpanner.removeAllExecutionTimes(); - connection.setStatementTimeout(TIMEOUT_FOR_FAST_STATEMENTS, TimeUnit.MILLISECONDS); - try (ResultSet rs = connection.executeQuery(SELECT_RANDOM_STATEMENT)) { - assertThat(rs, is(notNullValue())); + mockSpanner.removeAllExecutionTimes(); + connection.setStatementTimeout(TIMEOUT_FOR_FAST_STATEMENTS, TimeUnit.MILLISECONDS); + try (ResultSet rs = connection.executeQuery(SELECT_RANDOM_STATEMENT)) { + assertNotNull(rs); + } + } finally { + executor.shutdownNow(); } - } finally { - executor.shutdown(); } } @@ -675,23 +665,23 @@ public void testCancelReadOnlyTransactional() { mockSpanner.setExecuteStreamingSqlExecutionTime( SimulatedExecutionTime.ofMinimumAndRandomTime(EXECUTION_TIME_SLOW_STATEMENT, 0)); - ExecutorService executor = Executors.newSingleThreadExecutor(); try (Connection connection = createConnection()) { connection.setReadOnly(true); connection.setAutocommit(false); - executor.execute( - () -> { - waitForRequestsToContain(ExecuteSqlRequest.class); - connection.cancel(); - }); + ExecutorService executor = Executors.newSingleThreadExecutor(); try { - connection.executeQuery(SELECT_RANDOM_STATEMENT); - fail("Missing expected exception"); - } catch (SpannerException ex) { - assertEquals(ErrorCode.CANCELLED, ex.getErrorCode()); + executor.execute( + () -> { + waitForRequestsToContain(ExecuteSqlRequest.class); + connection.cancel(); + }); + SpannerException e = + assertThrows( + SpannerException.class, () -> connection.executeQuery(SELECT_RANDOM_STATEMENT)); + assertEquals(ErrorCode.CANCELLED, e.getErrorCode()); + } finally { + executor.shutdownNow(); } - } finally { - executor.shutdown(); } } @@ -700,35 +690,35 @@ public void testCancelReadOnlyTransactionalMultipleStatements() { mockSpanner.setExecuteStreamingSqlExecutionTime( SimulatedExecutionTime.ofMinimumAndRandomTime(EXECUTION_TIME_SLOW_STATEMENT, 0)); - ExecutorService executor = Executors.newSingleThreadExecutor(); try (Connection connection = createConnection()) { connection.setReadOnly(true); connection.setAutocommit(false); - executor.execute( - () -> { - waitForRequestsToContain(ExecuteSqlRequest.class); - connection.cancel(); - }); + ExecutorService executor = Executors.newSingleThreadExecutor(); try { - connection.executeQuery(Statement.of(SLOW_SELECT)); - fail("Missing expected exception"); - } catch (SpannerException e) { + executor.execute( + () -> { + waitForRequestsToContain(ExecuteSqlRequest.class); + connection.cancel(); + }); + SpannerException e = + assertThrows( + SpannerException.class, () -> connection.executeQuery(Statement.of(SLOW_SELECT))); assertEquals(ErrorCode.CANCELLED, e.getErrorCode()); - } - // try to do a new query that is fast. - mockSpanner.removeAllExecutionTimes(); - connection.setStatementTimeout(TIMEOUT_FOR_FAST_STATEMENTS, TimeUnit.MILLISECONDS); - try (ResultSet rs = connection.executeQuery(SELECT_RANDOM_STATEMENT)) { - assertThat(rs, is(notNullValue())); - } - // rollback and do another fast query - connection.rollback(); - try (ResultSet rs = connection.executeQuery(SELECT_RANDOM_STATEMENT)) { - assertThat(rs, is(notNullValue())); + // try to do a new query that is fast. + mockSpanner.removeAllExecutionTimes(); + connection.setStatementTimeout(TIMEOUT_FOR_FAST_STATEMENTS, TimeUnit.MILLISECONDS); + try (ResultSet rs = connection.executeQuery(SELECT_RANDOM_STATEMENT)) { + assertNotNull(rs); + } + // rollback and do another fast query + connection.rollback(); + try (ResultSet rs = connection.executeQuery(SELECT_RANDOM_STATEMENT)) { + assertNotNull(rs); + } + } finally { + executor.shutdownNow(); } - } finally { - executor.shutdown(); } } @@ -737,22 +727,22 @@ public void testCancelReadWriteAutocommit() { mockSpanner.setExecuteStreamingSqlExecutionTime( SimulatedExecutionTime.ofMinimumAndRandomTime(EXECUTION_TIME_SLOW_STATEMENT, 0)); - ExecutorService executor = Executors.newSingleThreadExecutor(); try (Connection connection = createConnection()) { connection.setAutocommit(true); - executor.execute( - () -> { - waitForRequestsToContain(ExecuteSqlRequest.class); - connection.cancel(); - }); + ExecutorService executor = Executors.newSingleThreadExecutor(); try { - connection.executeQuery(SELECT_RANDOM_STATEMENT); - fail("Missing expected exception"); - } catch (SpannerException ex) { - assertEquals(ErrorCode.CANCELLED, ex.getErrorCode()); + executor.execute( + () -> { + waitForRequestsToContain(ExecuteSqlRequest.class); + connection.cancel(); + }); + SpannerException e = + assertThrows( + SpannerException.class, () -> connection.executeQuery(SELECT_RANDOM_STATEMENT)); + assertEquals(ErrorCode.CANCELLED, e.getErrorCode()); + } finally { + executor.shutdownNow(); } - } finally { - executor.shutdown(); } } @@ -761,29 +751,29 @@ public void testCancelReadWriteAutocommitMultipleStatements() { mockSpanner.setExecuteStreamingSqlExecutionTime( SimulatedExecutionTime.ofMinimumAndRandomTime(EXECUTION_TIME_SLOW_STATEMENT, 0)); - ExecutorService executor = Executors.newSingleThreadExecutor(); try (Connection connection = createConnection()) { connection.setAutocommit(true); - executor.execute( - () -> { - waitForRequestsToContain(ExecuteSqlRequest.class); - connection.cancel(); - }); + ExecutorService executor = Executors.newSingleThreadExecutor(); try { - connection.executeQuery(SELECT_RANDOM_STATEMENT); - fail("Missing expected exception"); - } catch (SpannerException ex) { - assertEquals(ErrorCode.CANCELLED, ex.getErrorCode()); - } + executor.execute( + () -> { + waitForRequestsToContain(ExecuteSqlRequest.class); + connection.cancel(); + }); + SpannerException e = + assertThrows( + SpannerException.class, () -> connection.executeQuery(SELECT_RANDOM_STATEMENT)); + assertEquals(ErrorCode.CANCELLED, e.getErrorCode()); - // try to do a new query that is fast. - mockSpanner.removeAllExecutionTimes(); - connection.setStatementTimeout(TIMEOUT_FOR_FAST_STATEMENTS, TimeUnit.MILLISECONDS); - try (ResultSet rs = connection.executeQuery(SELECT_RANDOM_STATEMENT)) { - assertThat(rs, is(notNullValue())); + // try to do a new query that is fast. + mockSpanner.removeAllExecutionTimes(); + connection.setStatementTimeout(TIMEOUT_FOR_FAST_STATEMENTS, TimeUnit.MILLISECONDS); + try (ResultSet rs = connection.executeQuery(SELECT_RANDOM_STATEMENT)) { + assertNotNull(rs); + } + } finally { + executor.shutdownNow(); } - } finally { - executor.shutdown(); } } @@ -792,22 +782,21 @@ public void testCancelReadWriteAutocommitSlowUpdate() { mockSpanner.setExecuteSqlExecutionTime( SimulatedExecutionTime.ofMinimumAndRandomTime(EXECUTION_TIME_SLOW_STATEMENT, 0)); - ExecutorService executor = Executors.newSingleThreadExecutor(); try (Connection connection = createConnection()) { connection.setAutocommit(true); - executor.execute( - () -> { - waitForRequestsToContain(ExecuteSqlRequest.class); - connection.cancel(); - }); + ExecutorService executor = Executors.newSingleThreadExecutor(); try { - connection.execute(INSERT_STATEMENT); - fail("Missing expected exception"); - } catch (SpannerException ex) { - assertEquals(ErrorCode.CANCELLED, ex.getErrorCode()); + executor.execute( + () -> { + waitForRequestsToContain(ExecuteSqlRequest.class); + connection.cancel(); + }); + SpannerException e = + assertThrows(SpannerException.class, () -> connection.execute(INSERT_STATEMENT)); + assertEquals(ErrorCode.CANCELLED, e.getErrorCode()); + } finally { + executor.shutdownNow(); } - } finally { - executor.shutdown(); } } @@ -816,20 +805,21 @@ public void testCancelReadWriteAutocommitSlowCommit() { mockSpanner.setCommitExecutionTime( SimulatedExecutionTime.ofMinimumAndRandomTime(EXECUTION_TIME_SLOW_STATEMENT, 0)); - ExecutorService executor = Executors.newSingleThreadExecutor(); try (Connection connection = createConnection()) { connection.setAutocommit(true); - executor.execute( - () -> { - waitForRequestsToContain(CommitRequest.class); - connection.cancel(); - }); - connection.execute(INSERT_STATEMENT); - fail("Missing expected exception"); - } catch (SpannerException ex) { - assertEquals(ErrorCode.CANCELLED, ex.getErrorCode()); - } finally { - executor.shutdown(); + ExecutorService executor = Executors.newSingleThreadExecutor(); + try { + executor.execute( + () -> { + waitForRequestsToContain(CommitRequest.class); + connection.cancel(); + }); + SpannerException e = + assertThrows(SpannerException.class, () -> connection.execute(INSERT_STATEMENT)); + assertEquals(ErrorCode.CANCELLED, e.getErrorCode()); + } finally { + executor.shutdownNow(); + } } } @@ -838,20 +828,22 @@ public void testCancelReadWriteTransactional() { mockSpanner.setExecuteStreamingSqlExecutionTime( SimulatedExecutionTime.ofMinimumAndRandomTime(EXECUTION_TIME_SLOW_STATEMENT, 0)); - ExecutorService executor = Executors.newSingleThreadExecutor(); try (Connection connection = createConnection()) { connection.setAutocommit(false); - executor.execute( - () -> { - waitForRequestsToContain(ExecuteSqlRequest.class); - connection.cancel(); - }); - connection.executeQuery(SELECT_RANDOM_STATEMENT); - fail("Missing expected exception"); - } catch (SpannerException ex) { - assertEquals(ErrorCode.CANCELLED, ex.getErrorCode()); - } finally { - executor.shutdown(); + ExecutorService executor = Executors.newSingleThreadExecutor(); + try { + executor.execute( + () -> { + waitForRequestsToContain(ExecuteSqlRequest.class); + connection.cancel(); + }); + SpannerException e = + assertThrows( + SpannerException.class, () -> connection.executeQuery(SELECT_RANDOM_STATEMENT)); + assertEquals(ErrorCode.CANCELLED, e.getErrorCode()); + } finally { + executor.shutdownNow(); + } } } @@ -860,31 +852,31 @@ public void testCancelReadWriteTransactionalMultipleStatements() { mockSpanner.setExecuteStreamingSqlExecutionTime( SimulatedExecutionTime.ofMinimumAndRandomTime(EXECUTION_TIME_SLOW_STATEMENT, 0)); - ExecutorService executor = Executors.newSingleThreadExecutor(); try (Connection connection = createConnection()) { connection.setAutocommit(false); - executor.execute( - () -> { - waitForRequestsToContain(ExecuteSqlRequest.class); - connection.cancel(); - }); + ExecutorService executor = Executors.newSingleThreadExecutor(); try { - connection.executeQuery(SELECT_RANDOM_STATEMENT); - fail("Missing expected exception"); - } catch (SpannerException e) { + executor.execute( + () -> { + waitForRequestsToContain(ExecuteSqlRequest.class); + connection.cancel(); + }); + SpannerException e = + assertThrows( + SpannerException.class, () -> connection.executeQuery(SELECT_RANDOM_STATEMENT)); assertEquals(ErrorCode.CANCELLED, e.getErrorCode()); + // Rollback the transaction as it is no longer usable. + connection.rollback(); + + // Try to do a new query that is fast. + mockSpanner.removeAllExecutionTimes(); + connection.setStatementTimeout(TIMEOUT_FOR_FAST_STATEMENTS, TimeUnit.MILLISECONDS); + try (ResultSet rs = connection.executeQuery(SELECT_RANDOM_STATEMENT)) { + assertNotNull(rs); + } + } finally { + executor.shutdownNow(); } - // Rollback the transaction as it is no longer usable. - connection.rollback(); - - // Try to do a new query that is fast. - mockSpanner.removeAllExecutionTimes(); - connection.setStatementTimeout(TIMEOUT_FOR_FAST_STATEMENTS, TimeUnit.MILLISECONDS); - try (ResultSet rs = connection.executeQuery(SELECT_RANDOM_STATEMENT)) { - assertThat(rs, is(notNullValue())); - } - } finally { - executor.shutdown(); } } @@ -925,22 +917,22 @@ static void addMockDdlOperations(int count, boolean done) { public void testCancelDdlBatch() { addSlowMockDdlOperation(); - ExecutorService executor = Executors.newSingleThreadExecutor(); try (Connection connection = createConnection()) { connection.setAutocommit(false); connection.startBatchDdl(); connection.execute(Statement.of(SLOW_DDL)); - executor.execute( - () -> { - Uninterruptibles.sleepUninterruptibly(100L, TimeUnit.MILLISECONDS); - connection.cancel(); - }); - connection.runBatch(); - fail("Missing expected exception"); - } catch (SpannerException ex) { - assertEquals(ErrorCode.CANCELLED, ex.getErrorCode()); - } finally { - executor.shutdown(); + ExecutorService executor = Executors.newSingleThreadExecutor(); + try { + executor.execute( + () -> { + waitForDdlRequestOnServer(); + connection.cancel(); + }); + SpannerException e = assertThrows(SpannerException.class, () -> connection.runBatch()); + assertEquals(ErrorCode.CANCELLED, e.getErrorCode()); + } finally { + executor.shutdownNow(); + } } } @@ -948,20 +940,21 @@ public void testCancelDdlBatch() { public void testCancelDdlAutocommit() { addSlowMockDdlOperation(); - ExecutorService executor = Executors.newSingleThreadExecutor(); try (Connection connection = createConnection()) { connection.setAutocommit(true); - executor.execute( - () -> { - Uninterruptibles.sleepUninterruptibly(100L, TimeUnit.MILLISECONDS); - connection.cancel(); - }); - connection.execute(Statement.of(SLOW_DDL)); - fail("Missing expected exception"); - } catch (SpannerException ex) { - assertEquals(ErrorCode.CANCELLED, ex.getErrorCode()); - } finally { - executor.shutdown(); + ExecutorService executor = Executors.newSingleThreadExecutor(); + try { + executor.execute( + () -> { + waitForDdlRequestOnServer(); + connection.cancel(); + }); + SpannerException e = + assertThrows(SpannerException.class, () -> connection.execute(Statement.of(SLOW_DDL))); + assertEquals(ErrorCode.CANCELLED, e.getErrorCode()); + } finally { + executor.shutdownNow(); + } } } @@ -972,10 +965,9 @@ public void testTimeoutExceptionDdlAutocommit() { try (Connection connection = createConnection()) { connection.setAutocommit(true); connection.setStatementTimeout(TIMEOUT_FOR_SLOW_STATEMENTS, TimeUnit.MILLISECONDS); - connection.execute(Statement.of(SLOW_DDL)); - fail("Missing expected exception"); - } catch (SpannerException ex) { - assertEquals(ErrorCode.DEADLINE_EXCEEDED, ex.getErrorCode()); + SpannerException e = + assertThrows(SpannerException.class, () -> connection.execute(Statement.of(SLOW_DDL))); + assertEquals(ErrorCode.DEADLINE_EXCEEDED, e.getErrorCode()); } } @@ -989,18 +981,15 @@ public void testTimeoutExceptionDdlAutocommitMultipleStatements() { // assert that multiple statements after each other also time out for (int i = 0; i < 2; i++) { - try { - connection.execute(Statement.of(SLOW_DDL)); - fail("Missing expected exception"); - } catch (SpannerException e) { - assertEquals(ErrorCode.DEADLINE_EXCEEDED, e.getErrorCode()); - } + SpannerException e = + assertThrows(SpannerException.class, () -> connection.execute(Statement.of(SLOW_DDL))); + assertEquals(ErrorCode.DEADLINE_EXCEEDED, e.getErrorCode()); } // try to do a new DDL statement that is fast. mockDatabaseAdmin.reset(); addFastMockDdlOperation(); connection.setStatementTimeout(TIMEOUT_FOR_FAST_STATEMENTS, TimeUnit.MILLISECONDS); - assertThat(connection.execute(Statement.of(FAST_DDL)), is(notNullValue())); + assertNotNull(connection.execute(Statement.of(FAST_DDL))); } } @@ -1016,10 +1005,8 @@ public void testTimeoutExceptionDdlBatch() { // the following statement will NOT timeout as the statement is only buffered locally connection.execute(Statement.of(SLOW_DDL)); // the runBatch() statement sends the statement to the server and should timeout - connection.runBatch(); - fail("Missing expected exception"); - } catch (SpannerException ex) { - assertEquals(ErrorCode.DEADLINE_EXCEEDED, ex.getErrorCode()); + SpannerException e = assertThrows(SpannerException.class, () -> connection.runBatch()); + assertEquals(ErrorCode.DEADLINE_EXCEEDED, e.getErrorCode()); } } @@ -1033,22 +1020,17 @@ public void testTimeoutExceptionDdlBatchMultipleStatements() { // assert that multiple statements after each other also time out for (int i = 0; i < 2; i++) { - connection.startBatchDdl(); connection.execute(Statement.of(SLOW_DDL)); - try { - connection.runBatch(); - fail("Missing expected exception"); - } catch (SpannerException e) { - assertEquals(ErrorCode.DEADLINE_EXCEEDED, e.getErrorCode()); - } + SpannerException e = assertThrows(SpannerException.class, () -> connection.runBatch()); + assertEquals(ErrorCode.DEADLINE_EXCEEDED, e.getErrorCode()); } // try to do a new DDL statement that is fast. mockDatabaseAdmin.reset(); addFastMockDdlOperation(); connection.setStatementTimeout(TIMEOUT_FOR_FAST_STATEMENTS, TimeUnit.MILLISECONDS); connection.startBatchDdl(); - assertThat(connection.execute(Statement.of(FAST_DDL)), is(notNullValue())); + assertNotNull(connection.execute(Statement.of(FAST_DDL))); connection.runBatch(); } } @@ -1061,13 +1043,9 @@ public void testTimeoutDifferentTimeUnits() { try (Connection connection = createConnection()) { connection.setAutocommit(true); for (TimeUnit unit : ReadOnlyStalenessUtil.SUPPORTED_UNITS) { + // Only set the timeout, don't execute a statement with the timeout to prevent unnecessarily + // slowing down the build time. connection.setStatementTimeout(1L, unit); - try { - connection.execute(SELECT_RANDOM_STATEMENT); - fail("Missing expected exception"); - } catch (SpannerException e) { - assertEquals(ErrorCode.DEADLINE_EXCEEDED, e.getErrorCode()); - } } } } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITDdlTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITDdlTest.java index 8448c601b1..74c072cd76 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITDdlTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITDdlTest.java @@ -32,6 +32,6 @@ public class ITDdlTest extends ITAbstractSpannerTest { @Test public void testSqlScript() throws Exception { SqlScriptVerifier verifier = new SqlScriptVerifier(new ITConnectionProvider()); - verifier.verifyStatementsInFile("ITDdlTest.sql", SqlScriptVerifier.class); + verifier.verifyStatementsInFile("ITDdlTest.sql", SqlScriptVerifier.class, false); } } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITReadOnlySpannerTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITReadOnlySpannerTest.java index 55a4926f64..fd5df07954 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITReadOnlySpannerTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITReadOnlySpannerTest.java @@ -66,7 +66,7 @@ public void createTestTables() throws Exception { // create tables SqlScriptVerifier verifier = new SqlScriptVerifier(new ITConnectionProvider()); verifier.verifyStatementsInFile( - "ITReadOnlySpannerTest_CreateTables.sql", SqlScriptVerifier.class); + "ITReadOnlySpannerTest_CreateTables.sql", SqlScriptVerifier.class, false); // fill tables with data connection.setAutocommit(false); @@ -101,7 +101,7 @@ public void testSqlScript() throws Exception { // Wait 100ms to ensure that staleness tests in the script succeed. Thread.sleep(100L); SqlScriptVerifier verifier = new SqlScriptVerifier(new ITConnectionProvider()); - verifier.verifyStatementsInFile("ITReadOnlySpannerTest.sql", SqlScriptVerifier.class); + verifier.verifyStatementsInFile("ITReadOnlySpannerTest.sql", SqlScriptVerifier.class, false); } @Test diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITReadWriteAutocommitSpannerTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITReadWriteAutocommitSpannerTest.java index ab1da50992..d394013fbe 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITReadWriteAutocommitSpannerTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITReadWriteAutocommitSpannerTest.java @@ -59,7 +59,7 @@ public boolean doCreateDefaultTestTable() { public void test01_SqlScript() throws Exception { SqlScriptVerifier verifier = new SqlScriptVerifier(new ITConnectionProvider()); verifier.verifyStatementsInFile( - "ITReadWriteAutocommitSpannerTest.sql", SqlScriptVerifier.class); + "ITReadWriteAutocommitSpannerTest.sql", SqlScriptVerifier.class, false); } @Test diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITSqlMusicScriptTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITSqlMusicScriptTest.java index e8a479c6d6..e7afe95770 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITSqlMusicScriptTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITSqlMusicScriptTest.java @@ -54,7 +54,7 @@ public class ITSqlMusicScriptTest extends ITAbstractSpannerTest { public void test01_RunScript() throws Exception { SqlScriptVerifier verifier = new SqlScriptVerifier(); try (GenericConnection connection = SpannerGenericConnection.of(createConnection())) { - verifier.verifyStatementsInFile(connection, SCRIPT_FILE, SqlScriptVerifier.class); + verifier.verifyStatementsInFile(connection, SCRIPT_FILE, SqlScriptVerifier.class, false); } } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITSqlScriptTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITSqlScriptTest.java index 5a1e9bb628..026495605e 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITSqlScriptTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITSqlScriptTest.java @@ -66,7 +66,10 @@ public class ITSqlScriptTest extends ITAbstractSpannerTest { public void test01_CreateTables() throws Exception { try (ITConnection connection = createConnection()) { verifier.verifyStatementsInFile( - SpannerGenericConnection.of(connection), CREATE_TABLES_FILE, SqlScriptVerifier.class); + SpannerGenericConnection.of(connection), + CREATE_TABLES_FILE, + SqlScriptVerifier.class, + false); } } @@ -76,7 +79,8 @@ public void test02_InsertTestData() throws Exception { verifier.verifyStatementsInFile( SpannerGenericConnection.of(connection), INSERT_AND_VERIFY_TEST_DATA, - SqlScriptVerifier.class); + SqlScriptVerifier.class, + false); } catch (SpannerException e) { if (isUsingEmulator() && e.getErrorCode() == ErrorCode.ALREADY_EXISTS) { // Errors in a transaction are 'sticky' on the emulator, so any query in the same @@ -92,7 +96,8 @@ public void test03_TestGetReadTimestamp() throws Exception { verifier.verifyStatementsInFile( SpannerGenericConnection.of(connection), TEST_GET_READ_TIMESTAMP, - SqlScriptVerifier.class); + SqlScriptVerifier.class, + false); } } @@ -102,7 +107,8 @@ public void test04_TestGetCommitTimestamp() throws Exception { verifier.verifyStatementsInFile( SpannerGenericConnection.of(connection), TEST_GET_COMMIT_TIMESTAMP, - SqlScriptVerifier.class); + SqlScriptVerifier.class, + false); } catch (SpannerException e) { if (isUsingEmulator() && e.getErrorCode() == ErrorCode.INVALID_ARGUMENT) { // Errors in a transaction are 'sticky' on the emulator, so any query in the same @@ -117,7 +123,8 @@ public void test05_TestTemporaryTransactions() throws Exception { verifier.verifyStatementsInFile( SpannerGenericConnection.of(connection), TEST_TEMPORARY_TRANSACTIONS, - SqlScriptVerifier.class); + SqlScriptVerifier.class, + false); } } @@ -125,7 +132,10 @@ public void test05_TestTemporaryTransactions() throws Exception { public void test06_TestTransactionMode() throws Exception { try (ITConnection connection = createConnection()) { verifier.verifyStatementsInFile( - SpannerGenericConnection.of(connection), TEST_TRANSACTION_MODE, SqlScriptVerifier.class); + SpannerGenericConnection.of(connection), + TEST_TRANSACTION_MODE, + SqlScriptVerifier.class, + false); } } @@ -135,7 +145,8 @@ public void test07_TestTransactionModeReadOnly() throws Exception { verifier.verifyStatementsInFile( SpannerGenericConnection.of(connection), TEST_TRANSACTION_MODE_READ_ONLY, - SqlScriptVerifier.class); + SqlScriptVerifier.class, + false); } } @@ -145,7 +156,8 @@ public void test08_TestReadOnlyStaleness() throws Exception { verifier.verifyStatementsInFile( SpannerGenericConnection.of(connection), TEST_READ_ONLY_STALENESS, - SqlScriptVerifier.class); + SqlScriptVerifier.class, + false); } } @@ -155,7 +167,8 @@ public void test09_TestAutocommitDmlMode() throws Exception { verifier.verifyStatementsInFile( SpannerGenericConnection.of(connection), TEST_AUTOCOMMIT_DML_MODE, - SqlScriptVerifier.class); + SqlScriptVerifier.class, + false); } } @@ -165,7 +178,8 @@ public void test10_TestAutocommitReadOnly() throws Exception { verifier.verifyStatementsInFile( SpannerGenericConnection.of(connection), TEST_AUTOCOMMIT_READ_ONLY, - SqlScriptVerifier.class); + SqlScriptVerifier.class, + false); } } @@ -173,7 +187,10 @@ public void test10_TestAutocommitReadOnly() throws Exception { public void test11_TestStatementTimeout() throws Exception { try (ITConnection connection = createConnection()) { verifier.verifyStatementsInFile( - SpannerGenericConnection.of(connection), TEST_STATEMENT_TIMEOUT, SqlScriptVerifier.class); + SpannerGenericConnection.of(connection), + TEST_STATEMENT_TIMEOUT, + SqlScriptVerifier.class, + false); } } @@ -181,7 +198,10 @@ public void test11_TestStatementTimeout() throws Exception { public void test12_TestSetStatements() throws Exception { try (ITConnection connection = createConnection()) { verifier.verifyStatementsInFile( - SpannerGenericConnection.of(connection), TEST_SET_STATEMENTS, SqlScriptVerifier.class); + SpannerGenericConnection.of(connection), + TEST_SET_STATEMENTS, + SqlScriptVerifier.class, + false); } } @@ -191,7 +211,8 @@ public void test13_TestInvalidStatements() throws Exception { verifier.verifyStatementsInFile( SpannerGenericConnection.of(connection), TEST_INVALID_STATEMENTS, - SqlScriptVerifier.class); + SqlScriptVerifier.class, + false); } } } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITTransactionModeTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITTransactionModeTest.java index 3874f595ba..33b059c8bf 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITTransactionModeTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITTransactionModeTest.java @@ -53,7 +53,7 @@ public boolean doCreateDefaultTestTable() { @Test public void testSqlScript() throws Exception { SqlScriptVerifier verifier = new SqlScriptVerifier(new ITConnectionProvider()); - verifier.verifyStatementsInFile("ITTransactionModeTest.sql", SqlScriptVerifier.class); + verifier.verifyStatementsInFile("ITTransactionModeTest.sql", SqlScriptVerifier.class, false); } @Test diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpcTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpcTest.java index 39e16fcca4..a004dd3465 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpcTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpcTest.java @@ -20,7 +20,8 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.fail; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; import static org.junit.Assume.assumeTrue; import com.google.api.gax.core.GaxProperties; @@ -84,7 +85,7 @@ import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; import org.junit.After; -import org.junit.Before; +import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; @@ -141,24 +142,22 @@ public class GapicSpannerRpcTest { new java.util.Date( System.currentTimeMillis() + TimeUnit.MILLISECONDS.convert(1L, TimeUnit.DAYS)))); - private MockSpannerServiceImpl mockSpanner; - private MockInstanceAdminImpl mockInstanceAdmin; - private MockDatabaseAdminImpl mockDatabaseAdmin; - private Server server; - private InetSocketAddress address; - private final Map optionsMap = new HashMap<>(); - private Metadata seenHeaders; - private String defaultUserAgent; + private static MockSpannerServiceImpl mockSpanner; + private static MockInstanceAdminImpl mockInstanceAdmin; + private static MockDatabaseAdminImpl mockDatabaseAdmin; + private static Server server; + private static InetSocketAddress address; + private static final Map optionsMap = new HashMap<>(); + private static Metadata lastSeenHeaders; + private static String defaultUserAgent; + private static Spanner spanner; @BeforeClass - public static void checkNotEmulator() { + public static void startServer() throws IOException { assumeTrue( "Skip tests when emulator is enabled as this test interferes with the check whether the emulator is running", System.getenv("SPANNER_EMULATOR_HOST") == null); - } - @Before - public void startServer() throws IOException { defaultUserAgent = "spanner-java/" + GaxProperties.getLibraryVersion(GapicSpannerRpc.class); mockSpanner = new MockSpannerServiceImpl(); mockSpanner.setAbortProbability(0.0D); // We don't want any unpredictable aborted transactions. @@ -182,7 +181,7 @@ public ServerCall.Listener interceptCall( ServerCall call, Metadata headers, ServerCallHandler next) { - seenHeaders = headers; + lastSeenHeaders = headers; String auth = headers.get(Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER)); assertThat(auth).isEqualTo("Bearer " + VARIABLE_OAUTH_TOKEN); @@ -192,12 +191,21 @@ public ServerCall.Listener interceptCall( .build() .start(); optionsMap.put(Option.CHANNEL_HINT, 1L); + spanner = createSpannerOptions().getService(); + } + + @AfterClass + public static void stopServer() throws InterruptedException { + if (spanner != null) { + spanner.close(); + server.shutdown(); + server.awaitTermination(); + } } @After - public void stopServer() throws InterruptedException { - server.shutdown(); - server.awaitTermination(); + public void reset() { + mockSpanner.reset(); } private static final int NUMBER_OF_TEST_RUNS = 2; @@ -207,8 +215,8 @@ public void stopServer() throws InterruptedException { @Test public void testCloseAllThreadsWhenClosingSpanner() throws InterruptedException { + int initialNumberOfThreads = getNumberOfThreadsWithName(SPANNER_THREAD_NAME, false, 0); for (int i = 0; i < NUMBER_OF_TEST_RUNS; i++) { - assertThat(getNumberOfThreadsWithName(SPANNER_THREAD_NAME, true), is(equalTo(0))); // Create Spanner instance. SpannerOptions options = createSpannerOptions(); Spanner spanner = options.getService(); @@ -227,8 +235,8 @@ public void testCloseAllThreadsWhenClosingSpanner() throws InterruptedException // sessions should initialize multiple transport channels. resultSets.add(rs); // Check whether the number of expected threads has been reached. - if (getNumberOfThreadsWithName(SPANNER_THREAD_NAME, false) - == options.getNumChannels() * NUM_THREADS_PER_CHANNEL) { + if (getNumberOfThreadsWithName(SPANNER_THREAD_NAME, false, initialNumberOfThreads) + == options.getNumChannels() * NUM_THREADS_PER_CHANNEL + initialNumberOfThreads) { break; } } @@ -253,11 +261,14 @@ public void testCloseAllThreadsWhenClosingSpanner() throws InterruptedException spanner.close(); // Wait for up to two seconds to allow the threads to actually shutdown. Stopwatch watch = Stopwatch.createStarted(); - while (getNumberOfThreadsWithName(SPANNER_THREAD_NAME, false) > 0 + while (getNumberOfThreadsWithName(SPANNER_THREAD_NAME, false, initialNumberOfThreads) + > initialNumberOfThreads && watch.elapsed(TimeUnit.SECONDS) < 2) { Thread.sleep(10L); } - assertThat(getNumberOfThreadsWithName(SPANNER_THREAD_NAME, true), is(equalTo(0))); + assertThat( + getNumberOfThreadsWithName(SPANNER_THREAD_NAME, true, initialNumberOfThreads), + is(equalTo(initialNumberOfThreads))); } } @@ -268,7 +279,7 @@ public void testCloseAllThreadsWhenClosingSpanner() throws InterruptedException @Test public void testMultipleOpenSpanners() throws InterruptedException { List spanners = new ArrayList<>(); - assertThat(getNumberOfThreadsWithName(SPANNER_THREAD_NAME, true), is(equalTo(0))); + int initialNumberOfThreads = getNumberOfThreadsWithName(SPANNER_THREAD_NAME, false, 0); for (int openSpanners = 1; openSpanners <= 3; openSpanners++) { // Create Spanner instance. SpannerOptions options = createSpannerOptions(); @@ -282,8 +293,9 @@ public void testMultipleOpenSpanners() throws InterruptedException { // to ensure we also hit multiple channels. for (int sessionCount = 0; sessionCount < options.getSessionPoolOptions().getMaxSessions() - && getNumberOfThreadsWithName(SPANNER_THREAD_NAME, false) - < options.getNumChannels() * NUM_THREADS_PER_CHANNEL * openSpanners; + && getNumberOfThreadsWithName(SPANNER_THREAD_NAME, false, initialNumberOfThreads) + < options.getNumChannels() * NUM_THREADS_PER_CHANNEL * openSpanners + + initialNumberOfThreads; sessionCount++) { ResultSet rs = client.singleUse().executeQuery(SELECT1AND2); // Execute ResultSet#next() to send the query to Spanner. @@ -302,11 +314,14 @@ && getNumberOfThreadsWithName(SPANNER_THREAD_NAME, false) } // Wait a little to allow the threads to actually shutdown. Stopwatch watch = Stopwatch.createStarted(); - while (getNumberOfThreadsWithName(SPANNER_THREAD_NAME, false) > 0 + while (getNumberOfThreadsWithName(SPANNER_THREAD_NAME, false, initialNumberOfThreads) + > initialNumberOfThreads && watch.elapsed(TimeUnit.SECONDS) < 2) { Thread.sleep(10L); } - assertThat(getNumberOfThreadsWithName(SPANNER_THREAD_NAME, true), is(equalTo(0))); + assertThat( + getNumberOfThreadsWithName(SPANNER_THREAD_NAME, true, initialNumberOfThreads), + is(equalTo(initialNumberOfThreads))); } @Test @@ -317,7 +332,7 @@ public void testCallCredentialsProviderPreferenceAboveCredentials() { .setCredentials(STATIC_CREDENTIALS) .setCallCredentialsProvider(() -> MoreCallCredentials.from(VARIABLE_CREDENTIALS)) .build(); - GapicSpannerRpc rpc = new GapicSpannerRpc(options); + GapicSpannerRpc rpc = new GapicSpannerRpc(options, false); // GoogleAuthLibraryCallCredentials doesn't implement equals, so we can only check for the // existence. assertThat( @@ -340,7 +355,7 @@ public void testCallCredentialsProviderReturnsNull() { .setCredentials(STATIC_CREDENTIALS) .setCallCredentialsProvider(() -> null) .build(); - GapicSpannerRpc rpc = new GapicSpannerRpc(options); + GapicSpannerRpc rpc = new GapicSpannerRpc(options, false); assertThat( rpc.newCallContext( optionsMap, @@ -360,7 +375,7 @@ public void testNoCallCredentials() { .setProjectId("some-project") .setCredentials(STATIC_CREDENTIALS) .build(); - GapicSpannerRpc rpc = new GapicSpannerRpc(options); + GapicSpannerRpc rpc = new GapicSpannerRpc(options, false); assertThat( rpc.newCallContext( optionsMap, @@ -403,41 +418,38 @@ public ApiCallContext configure( }; mockSpanner.setExecuteSqlExecutionTime(SimulatedExecutionTime.ofMinimumAndRandomTime(10, 0)); - SpannerOptions options = createSpannerOptions(); - try (Spanner spanner = options.getService()) { - final DatabaseClient client = - spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); - Context context = - Context.current().withValue(SpannerOptions.CALL_CONTEXT_CONFIGURATOR_KEY, configurator); - context.run( - () -> { - try { - // First try with a 1ns timeout. This should always cause a DEADLINE_EXCEEDED - // exception. - timeoutHolder.timeout = Duration.ofNanos(1L); + final DatabaseClient client = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); + Context context = + Context.current().withValue(SpannerOptions.CALL_CONTEXT_CONFIGURATOR_KEY, configurator); + context.run( + () -> { + // First try with a 1ns timeout. This should always cause a DEADLINE_EXCEEDED + // exception. + timeoutHolder.timeout = Duration.ofNanos(1L); + SpannerException e = + assertThrows( + SpannerException.class, + () -> + client + .readWriteTransaction() + .run(transaction -> transaction.executeUpdate(UPDATE_FOO_STATEMENT))); + assertEquals(ErrorCode.DEADLINE_EXCEEDED, e.getErrorCode()); + + // Then try with a longer timeout. This should now succeed. + timeoutHolder.timeout = Duration.ofMinutes(1L); + long updateCount = client .readWriteTransaction() .run(transaction -> transaction.executeUpdate(UPDATE_FOO_STATEMENT)); - fail("missing expected timeout exception"); - } catch (SpannerException e) { - assertThat(e.getErrorCode()).isEqualTo(ErrorCode.DEADLINE_EXCEEDED); - } - - // Then try with a longer timeout. This should now succeed. - timeoutHolder.timeout = Duration.ofMinutes(1L); - Long updateCount = - client - .readWriteTransaction() - .run(transaction -> transaction.executeUpdate(UPDATE_FOO_STATEMENT)); - assertThat(updateCount).isEqualTo(1L); - }); - } + assertEquals(1L, updateCount); + }); } @Test public void testNewCallContextWithNullRequestAndNullMethod() { SpannerOptions options = SpannerOptions.newBuilder().setProjectId("some-project").build(); - GapicSpannerRpc rpc = new GapicSpannerRpc(options); + GapicSpannerRpc rpc = new GapicSpannerRpc(options, false); assertThat(rpc.newCallContext(optionsMap, "/some/resource", null, null)).isNotNull(); rpc.shutdown(); } @@ -477,18 +489,15 @@ public void testAdminRequestsLimitExceededRetryAlgorithm() { @Test public void testDefaultUserAgent() { - final SpannerOptions options = createSpannerOptions(); - try (final Spanner spanner = options.getService()) { - final DatabaseClient databaseClient = - spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); - - try (final ResultSet rs = databaseClient.singleUse().executeQuery(SELECT1AND2)) { - rs.next(); - } + final DatabaseClient databaseClient = + spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]")); - assertThat(seenHeaders.get(Key.of("user-agent", Metadata.ASCII_STRING_MARSHALLER))) - .contains(defaultUserAgent); + try (final ResultSet rs = databaseClient.singleUse().executeQuery(SELECT1AND2)) { + rs.next(); } + + assertThat(lastSeenHeaders.get(Key.of("user-agent", Metadata.ASCII_STRING_MARSHALLER))) + .contains(defaultUserAgent); } @Test @@ -510,13 +519,13 @@ public void testCustomUserAgent() { rs.next(); } - assertThat(seenHeaders.get(Key.of("user-agent", Metadata.ASCII_STRING_MARSHALLER))) + assertThat(lastSeenHeaders.get(Key.of("user-agent", Metadata.ASCII_STRING_MARSHALLER))) .contains("test-agent " + defaultUserAgent); } } } - private SpannerOptions createSpannerOptions() { + private static SpannerOptions createSpannerOptions() { String endpoint = address.getHostString() + ":" + server.getPort(); return SpannerOptions.newBuilder() .setProjectId("[PROJECT]") @@ -535,7 +544,7 @@ private SpannerOptions createSpannerOptions() { .build(); } - private int getNumberOfThreadsWithName(String serviceName, boolean dumpStack) { + private int getNumberOfThreadsWithName(String serviceName, boolean dumpStack, int expected) { Pattern pattern = Pattern.compile(String.format(THREAD_PATTERN, serviceName)); ThreadGroup group = Thread.currentThread().getThreadGroup(); while (group.getParent() != null) { @@ -544,14 +553,18 @@ private int getNumberOfThreadsWithName(String serviceName, boolean dumpStack) { Thread[] threads = new Thread[100 * NUMBER_OF_TEST_RUNS]; int numberOfThreads = group.enumerate(threads); int res = 0; + List found = new ArrayList<>(); for (int i = 0; i < numberOfThreads; i++) { if (pattern.matcher(threads[i].getName()).matches()) { if (dumpStack) { - dumpThread(threads[i]); + found.add(threads[i]); } res++; } } + if (dumpStack && res > expected) { + found.stream().forEach(t -> dumpThread(t)); + } return res; } diff --git a/samples/README.md b/samples/README.md index 6df90704ed..b65ad460b5 100644 --- a/samples/README.md +++ b/samples/README.md @@ -17,7 +17,7 @@ Install [Maven](http://maven.apache.org/). Build your project from the root directory (`java-spanner`): - mvn clean package -DskipTests -DskipUTs -Penable-samples + mvn clean package -DskipTests -Penable-samples Every subsequent command here should be run from a subdirectory (`cd samples/snippets`).