public async Task GetStatisticsSnapshot_MultipleDatabases() { var client = new SessionTestingSpannerClient(); var options = new SessionPoolOptions { MinimumPooledSessions = 10, }; var sessionPool = new SessionPool(client, options); var acquisitionTask1 = sessionPool.AcquireSessionAsync(s_sampleDatabaseName, new TransactionOptions(), default); var acquisitionTask2 = sessionPool.AcquireSessionAsync(s_sampleDatabaseName2, new TransactionOptions(), default); var stats = sessionPool.GetStatisticsSnapshot(); Assert.Equal(2, stats.PerDatabaseStatistics.Count); Assert.Equal(2, stats.TotalActiveSessionCount); Assert.Equal(0, stats.TotalReadPoolCount); // We've asked for 2 sessions, and the databases "know" they need 10 in the pool (each), so // there will be 22 in-flight requests in total. Assert.Equal(22, stats.TotalInFlightCreationCount); Assert.Contains(stats.PerDatabaseStatistics, s => s.DatabaseName == s_sampleDatabaseName); Assert.Contains(stats.PerDatabaseStatistics, s => s.DatabaseName == s_sampleDatabaseName2); // xUnit waits until tasks registered in its synchronization context have completed before considering the // test itself complete, so we need to let the pool complete the acquisition tasks. await client.Scheduler.RunAsync(TimeSpan.FromMinutes(2)); }
public void SessionPoolDefaultTimeout() { SessionPoolOptions options = new SessionPoolOptions(); Assert.Equal(options.Timeout, (int)SpannerSettings.GetDefault().CommitSettings.Timing.Retry.TotalExpiration.Timeout.Value.TotalSeconds); }
public void ClientCreatedWithEmulatorDetection() { Mock <SpannerClient> spannerClientMock = SetupExecuteStreamingSql(); var spannerClient = spannerClientMock.Object; var sessionPoolOptions = new SessionPoolOptions { MaintenanceLoopDelay = TimeSpan.Zero }; var sessionPoolManager = new SessionPoolManager( sessionPoolOptions, spannerClient.Settings.Logger, (_o, _s, _l) => { Assert.True(_o.UsesEmulator); return(Task.FromResult(spannerClient)); }); SpannerConnectionStringBuilder builder = new SpannerConnectionStringBuilder { DataSource = DatabaseName.Format(SpannerClientHelpers.ProjectId, SpannerClientHelpers.Instance, SpannerClientHelpers.Database), SessionPoolManager = sessionPoolManager, EmulatorDetection = EmulatorDetection.EmulatorOrProduction, EnvironmentVariableProvider = key => key == "SPANNER_EMULATOR_HOST" ? "localhost" : null }; var connection = new SpannerConnection(builder); var command = connection.CreateSelectCommand("SELECT * FROM FOO"); using (var reader = command.ExecuteReader()) { // Do nothing. } }
public async Task <long> LogCommitStatsAsync(string projectId, string instanceId, string databaseId) { // Commit statistics are logged at level Info by the default logger. // This sample uses a custom logger to access the commit statistics. // See https://googleapis.github.io/google-cloud-dotnet/docs/Google.Cloud.Spanner.Data/logging.html // for more information on how to use loggers. var logger = new CommitStatsSampleLogger(); var options = new SessionPoolOptions(); var poolManager = SessionPoolManager.Create(options, logger); var connectionStringBuilder = new SpannerConnectionStringBuilder { ConnectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}", // Set LogCommitStats to true to enable logging commit statistics for all transactions on the connection. // LogCommitStats can also be enabled/disabled for individual Spanner transactions. LogCommitStats = true, SessionPoolManager = poolManager, }; using var connection = new SpannerConnection(connectionStringBuilder); await connection.OpenAsync(); using var cmd = connection.CreateDmlCommand("INSERT Singers (SingerId, FirstName, LastName) VALUES (110, 'Virginia', 'Watson')"); var rowCount = await cmd.ExecuteNonQueryAsync(); var mutationCount = logger._lastCommitResponse.CommitStats.MutationCount; Console.WriteLine($"{rowCount} row(s) inserted..."); Console.WriteLine($"{mutationCount} mutation(s) in transaction..."); return(mutationCount); }
private static TargetedSessionPool CreatePool(bool acquireSessionsImmediately) { var client = new SessionTestingSpannerClient(); client.Scheduler.RealTimeTimeout = TimeSpan.FromSeconds(15); // Fixed session pool options so we can hard-code values without worrying about the defaults changing var options = new SessionPoolOptions { IdleSessionRefreshDelay = TimeSpan.FromMinutes(15), MaintenanceLoopDelay = TimeSpan.FromSeconds(0), // Disable automatic pool maintenance PoolEvictionDelay = TimeSpan.FromMinutes(100), // Deliberately not a multiple of 15 SessionEvictionJitter = RetrySettings.NoJitter, SessionRefreshJitter = RetrySettings.NoJitter, MaximumActiveSessions = 100, MaximumConcurrentSessionCreates = 20, CreateSessionMaximumBatchSize = 5, MinimumPooledSessions = 10, Timeout = TimeSpan.FromSeconds(60), WaitOnResourcesExhausted = ResourcesExhaustedBehavior.Block, WriteSessionsFraction = 0.2 }; var parent = new SessionPool(client, options); return(new TargetedSessionPool(parent, s_databaseName, acquireSessionsImmediately)); }
/// <summary> /// Constructor for test purposes, allowing the SpannerClient creation to be customized (e.g. for /// fake clients). /// </summary> /// <param name="options">The session pool options to use. Must not be null.</param> /// <param name="logger">The logger to use. Must not be null.</param> /// <param name="clientFactory">The client factory delegate to use. Must not be null.</param> internal SessionPoolManager( SessionPoolOptions options, Logger logger, Func <SpannerClientCreationOptions, Task <SpannerClient> > clientFactory) { SessionPoolOptions = GaxPreconditions.CheckNotNull(options, nameof(options)); _logger = GaxPreconditions.CheckNotNull(logger, nameof(logger)); _clientFactory = GaxPreconditions.CheckNotNull(clientFactory, nameof(clientFactory)); }
internal FakeSessionPool() { Mock = SpannerClientHelpers.CreateMockClient(); Options = new SessionPoolOptions { SessionEvictionJitter = RetrySettings.NoJitter, SessionRefreshJitter = RetrySettings.NoJitter }; }
public void GetStatisticsSnapshot_UnrepresentedDatabase() { var client = new SessionTestingSpannerClient(); var options = new SessionPoolOptions(); var sessionPool = new SessionPool(client, options); // We haven't used the database in this session pool, so there are no statistics for it. Assert.Null(sessionPool.GetStatisticsSnapshot(s_sampleDatabaseName)); }
public void SessionPoolDefaultTimeout() { SessionPoolOptions options = new SessionPoolOptions(); Assert.Equal(options.Timeout, // ReSharper disable once PossibleInvalidOperationException (int)SpannerSettings.GetDefault().CommitSettings.Timing.Retry .TotalExpiration.Timeout.Value.TotalSeconds); }
/// <summary> /// Constructor for test purposes, allowing the SpannerClient creation to be customized (e.g. for /// fake clients). /// </summary> /// <param name="options">The session pool options to use. Must not be null.</param> /// <param name="spannerSettings">The SpannerSettings to use. Must not be null.</param> /// <param name="logger">The logger to use. Must not be null.</param> /// <param name="clientFactory">The client factory delegate to use. Must not be null.</param> internal SessionPoolManager( SessionPoolOptions options, SpannerSettings spannerSettings, Logger logger, Func <SpannerClientCreationOptions, SpannerSettings, Logger, Task <SpannerClient> > clientFactory) { SessionPoolOptions = GaxPreconditions.CheckNotNull(options, nameof(options)); SpannerSettings = AppendAssemblyVersionHeader(GaxPreconditions.CheckNotNull(spannerSettings, nameof(spannerSettings))); Logger = GaxPreconditions.CheckNotNull(logger, nameof(logger)); _clientFactory = GaxPreconditions.CheckNotNull(clientFactory, nameof(clientFactory)); }
public async Task ScheduledMaintenanceEvictsSessions_DifferentEvictionTimes() { var client = new SessionTestingSpannerClient(); var options = new SessionPoolOptions { // We'll never actually hit a refresh, as the eviction delay is shorter. IdleSessionRefreshDelay = TimeSpan.FromMinutes(30), PoolEvictionDelay = TimeSpan.FromMinutes(3), MaintenanceLoopDelay = TimeSpan.FromMinutes(1), SessionEvictionJitter = RetrySettings.NoJitter, MinimumPooledSessions = 10, MaximumConcurrentSessionCreates = 20, WriteSessionsFraction = 0 }; var sessionPool = new SessionPool(client, options); var acquisitionTask = sessionPool.AcquireSessionAsync(s_sampleDatabaseName, new TransactionOptions(), default); await client.Scheduler.RunAsync(TimeSpan.FromMinutes(1)); // Our session should be ready, the pool should be up to size, and we should // have created 11 sessions in total. var session = await acquisitionTask; var stats = sessionPool.GetStatisticsSnapshot(s_sampleDatabaseName); Assert.Equal(10, stats.ReadPoolCount); Assert.Equal(11, client.SessionsCreated); Assert.Equal(0, client.SessionsDeleted); // Force the creation of a newer session by acquiring one. // The new one will be created to satisfy the minimum size of the pool // and will sit on top of the stack. // First move the pool to T=2, so that the new session will be created in T=3 // so that its eviction time will be T=6 so as to make sure that it's not // being evicted when we check at T=5. await client.Scheduler.RunAsync(TimeSpan.FromMinutes(1)); acquisitionTask = sessionPool.AcquireSessionAsync(s_sampleDatabaseName, new TransactionOptions(), default); await client.Scheduler.RunAsync(TimeSpan.FromMinutes(1)); session = await acquisitionTask; stats = sessionPool.GetStatisticsSnapshot(s_sampleDatabaseName); Assert.Equal(10, stats.ReadPoolCount); Assert.Equal(12, client.SessionsCreated); Assert.Equal(0, client.SessionsDeleted); // If we allow the maintenance pool to run until T=5 minutes, we should have evicted // all the old 9 sessions in the pool and replaced them with 9 new ones. await client.Scheduler.RunAsync(TimeSpan.FromMinutes(2)); stats = sessionPool.GetStatisticsSnapshot(s_sampleDatabaseName); Assert.Equal(10, stats.ReadPoolCount); Assert.Equal(21, client.SessionsCreated); Assert.Equal(9, client.SessionsDeleted); }
internal FakeSessionPool() { Mock = new Mock <SpannerClient>(MockBehavior.Strict); Mock.SetupProperty(client => client.Settings, SpannerSettings.GetDefault()); Options = new SessionPoolOptions { Clock = new FakeClock(), Scheduler = new FakeScheduler(), SessionEvictionJitter = RetrySettings.NoJitter, SessionRefreshJitter = RetrySettings.NoJitter }; }
private static void SetProperty(string propertyName, object value) { var options = new SessionPoolOptions(); var property = typeof(SessionPoolOptions).GetTypeInfo().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); try { property.SetValue(options, value); } catch (TargetInvocationException e) { throw e.InnerException; } }
public async Task EmulatorDetection_AlwaysUsesRegularOptions(string emulatorHost) { var regularOptions = new SessionPoolOptions(); var manager = new SessionPoolManager(regularOptions, SessionPoolManager.CreateDefaultSpannerSettings(), Logger.DefaultLogger, FailingSpannerClient.Factory); var builder = new SpannerConnectionStringBuilder(ConnectionString) { EmulatorDetection = EmulatorDetection.EmulatorOrProduction, // Effectively "there are no environment variables" EnvironmentVariableProvider = key => key == "SPANNER_EMULATOR_HOST" ? emulatorHost: null }; var clientCreationOptions = new SpannerClientCreationOptions(new SpannerConnectionStringBuilder(ConnectionString)); var pool = await manager.AcquireSessionPoolAsync(clientCreationOptions); Assert.Same(regularOptions, pool.Options); }
public async Task ShutdownPoolAsync() { var client = new SessionTestingSpannerClient(); var options = new SessionPoolOptions { IdleSessionRefreshDelay = TimeSpan.FromMinutes(30), PoolEvictionDelay = TimeSpan.FromMinutes(30), MaintenanceLoopDelay = TimeSpan.FromMinutes(1), MinimumPooledSessions = 10, MaximumConcurrentSessionCreates = 20, WriteSessionsFraction = 0 }; var sessionPool = new SessionPool(client, options); var acquisitionTask = sessionPool.AcquireSessionAsync(s_sampleDatabaseName, new TransactionOptions(), default); // After a minute, we should have a session. Release it immediately for simplicity. await client.Scheduler.RunAsync(TimeSpan.FromMinutes(1)); var session = await acquisitionTask; session.ReleaseToPool(false); // Shut the pool down, and wait a minute. (It won't take that long, as nothing's pending.) var shutdownTask = sessionPool.ShutdownPoolAsync(s_sampleDatabaseName, default); await client.Scheduler.RunAsync(TimeSpan.FromMinutes(1)); // Now the shutdown task should have completed, and the stats will know that it's shut down. await shutdownTask; var stats = sessionPool.GetStatisticsSnapshot(s_sampleDatabaseName); Assert.True(stats.Shutdown); // We can't get sessions any more for this database await Assert.ThrowsAsync <InvalidOperationException>(() => sessionPool.AcquireSessionAsync(s_sampleDatabaseName, new TransactionOptions(), default)); // But we can for a different database. (It shuts down a single database pool, not the whole session pool.) var acquisitionTask2 = sessionPool.AcquireSessionAsync(s_sampleDatabaseName2, new TransactionOptions(), default); await client.Scheduler.RunAsync(TimeSpan.FromMinutes(1)); await acquisitionTask2; }
public void ClientCreatedWithEmulatorDetection() { Mock <SpannerClient> spannerClientMock = SpannerClientHelpers .CreateMockClient(Logger.DefaultLogger, MockBehavior.Strict); spannerClientMock .SetupBatchCreateSessionsAsync() .SetupExecuteStreamingSql(); var spannerClient = spannerClientMock.Object; var sessionPoolOptions = new SessionPoolOptions { MaintenanceLoopDelay = TimeSpan.Zero }; var sessionPoolManager = new SessionPoolManager( sessionPoolOptions, spannerClient.Settings.Logger, (_o, _s, _l) => { Assert.True(_o.UsesEmulator); return(Task.FromResult(spannerClient)); }); SpannerConnectionStringBuilder builder = new SpannerConnectionStringBuilder { DataSource = DatabaseName.Format(SpannerClientHelpers.ProjectId, SpannerClientHelpers.Instance, SpannerClientHelpers.Database), SessionPoolManager = sessionPoolManager, EmulatorDetection = EmulatorDetection.EmulatorOrProduction, EnvironmentVariableProvider = key => key == "SPANNER_EMULATOR_HOST" ? "localhost" : null }; var connection = new SpannerConnection(builder); var command = connection.CreateSelectCommand("SELECT * FROM FOO"); using (var reader = command.ExecuteReader()) { Assert.True(reader.HasRows); } spannerClientMock.Verify(client => client.ExecuteStreamingSql( It.IsAny <ExecuteSqlRequest>(), It.IsAny <CallSettings>()), Times.Once()); }
private SpannerConnection BuildSpannerConnection(Mock<SpannerClient> spannerClientMock) { var spannerClient = spannerClientMock.Object; var sessionPoolOptions = new SessionPoolOptions { MaintenanceLoopDelay = TimeSpan.Zero }; var sessionPoolManager = new SessionPoolManager(sessionPoolOptions, spannerClient.Settings.Logger, (_o, _s, _l) => Task.FromResult(spannerClient)); sessionPoolManager.SpannerSettings.Scheduler = spannerClient.Settings.Scheduler; sessionPoolManager.SpannerSettings.Clock = spannerClient.Settings.Clock; SpannerConnectionStringBuilder builder = new SpannerConnectionStringBuilder { DataSource = DatabaseName.Format(SpannerClientHelpers.ProjectId, SpannerClientHelpers.Instance, SpannerClientHelpers.Database), SessionPoolManager = sessionPoolManager }; return new SpannerConnection(builder); }
public async Task MaintenanceTaskCompletesWhenPoolIsGarbageCollected() { var client = new SessionTestingSpannerClient(); var options = new SessionPoolOptions { MinimumPooledSessions = 10, MaintenanceLoopDelay = TimeSpan.FromMinutes(1) }; var sessionPool = new SessionPool(client, options); var waitingTask = sessionPool.WhenPoolReady(s_sampleDatabaseName); await client.Scheduler.RunAsync(TimeSpan.FromMinutes(5.5)); await waitingTask; var maintenanceCount = client.Logger.GetEntries(LogLevel.Debug).Count(entry => entry.Contains("maintenance")); Assert.InRange(maintenanceCount, 5, 6); // Make sure the session pool is "alive" up until this point GC.KeepAlive(sessionPool); var weakReference = new WeakReference <SessionPool>(sessionPool); sessionPool = null; GC.Collect(); GC.WaitForPendingFinalizers(); // Depending on the frameowrk version and the release mode, the pool may or may not be collected // at this point. If this weak reference has been cleared, we'll assume the internal one has been // as well. Otherwise, this test is pointless but harmless. if (!weakReference.TryGetTarget(out _)) { await client.Scheduler.RunAsync(TimeSpan.FromMinutes(10)); maintenanceCount = client.Logger.GetEntries(LogLevel.Debug).Count(entry => entry.Contains("maintenance")); // We're really just checking that at *some* point, we stopped logging. // If the maintenance loop hadn't stopped, we'd have 15 entries. Assert.InRange(maintenanceCount, 5, 8); } }
public async Task ScheduledMaintenanceEvictsSessions() { var client = new SessionTestingSpannerClient(); var options = new SessionPoolOptions { // We'll never actually hit a refresh, as the eviction delay is shorter. IdleSessionRefreshDelay = TimeSpan.FromMinutes(30), PoolEvictionDelay = TimeSpan.FromMinutes(3), MaintenanceLoopDelay = TimeSpan.FromMinutes(1), SessionEvictionJitter = RetrySettings.NoJitter, MinimumPooledSessions = 10, MaximumConcurrentSessionCreates = 20, WriteSessionsFraction = 0 }; var sessionPool = new SessionPool(client, options); var acquisitionTask = sessionPool.AcquireSessionAsync(s_sampleDatabaseName, new TransactionOptions(), default); await client.Scheduler.RunAsync(TimeSpan.FromMinutes(1)); // Our session should be ready, the pool should be up to size, and we should // have created 11 sessions in total. var session = await acquisitionTask; var stats = sessionPool.GetStatisticsSnapshot(s_sampleDatabaseName); Assert.Equal(10, stats.ReadPoolCount); Assert.Equal(11, client.SessionsCreated); Assert.Equal(0, client.SessionsDeleted); // If we allow the maintenance pool to run until T=5 minutes, we should have evicted // all the 10 sessions in the pool and replaced them. await client.Scheduler.RunAsync(TimeSpan.FromMinutes(4)); stats = sessionPool.GetStatisticsSnapshot(s_sampleDatabaseName); Assert.Equal(10, stats.ReadPoolCount); Assert.Equal(21, client.SessionsCreated); Assert.Equal(10, client.SessionsDeleted); }
public async Task WhenPoolReady() { var client = new SessionTestingSpannerClient(); var options = new SessionPoolOptions { IdleSessionRefreshDelay = TimeSpan.FromMinutes(30), PoolEvictionDelay = TimeSpan.FromMinutes(30), MaintenanceLoopDelay = TimeSpan.FromMinutes(1), MinimumPooledSessions = 10, MaximumConcurrentSessionCreates = 20, WriteSessionsFraction = 0 }; var sessionPool = new SessionPool(client, options); // Ask when the pool is ready, which shouldn't take a minute. var poolReadyTask = sessionPool.WhenPoolReady(s_sampleDatabaseName, default); await client.Scheduler.RunAsync(TimeSpan.FromMinutes(1)); await poolReadyTask; // When the pool *is* ready, we should be able to acquire a session directly from the pool, // without any further delays. await sessionPool.AcquireSessionAsync(s_sampleDatabaseName, new TransactionOptions(), default); }
/// <summary> /// Creates a <see cref="SessionPoolManager"/> with the specified options. /// </summary> /// <param name="options">The options to use. Must not be null.</param> /// <param name="logger">The logger to use. May be null, in which case the default logger is used.</param> /// <returns>A <see cref="SessionPoolManager"/> with the given options.</returns> public static SessionPoolManager Create(SessionPoolOptions options, Logger logger = null) => new SessionPoolManager(options, logger ?? Logger.DefaultLogger, CreateClientAsync);
/// <summary> /// Creates a <see cref="SessionPoolManager"/> with the specified SpannerSettings and options. /// </summary> /// <param name="options">The options to use. Must not be null.</param> /// <param name="spannerSettings">The SpannerSettings to use. Must not be null.</param> /// <returns>A <see cref="SessionPoolManager"/> with the given options.</returns> public static SessionPoolManager CreateWithSettings(SessionPoolOptions options, SpannerSettings spannerSettings) => new SessionPoolManager(options, GaxPreconditions.CheckNotNull(spannerSettings, nameof(spannerSettings)).Clone(), spannerSettings.Logger ?? Logger.DefaultLogger, CreateClientAsync);