Пример #1
0
        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));
        }
Пример #2
0
        public void SessionPoolDefaultTimeout()
        {
            SessionPoolOptions options = new SessionPoolOptions();

            Assert.Equal(options.Timeout,
                         (int)SpannerSettings.GetDefault().CommitSettings.Timing.Retry.TotalExpiration.Timeout.Value.TotalSeconds);
        }
Пример #3
0
        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.
            }
        }
Пример #4
0
    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);
    }
Пример #5
0
            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));
 }
Пример #7
0
 internal FakeSessionPool()
 {
     Mock    = SpannerClientHelpers.CreateMockClient();
     Options = new SessionPoolOptions
     {
         SessionEvictionJitter = RetrySettings.NoJitter,
         SessionRefreshJitter  = RetrySettings.NoJitter
     };
 }
Пример #8
0
        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));
        }
Пример #9
0
        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);
        }
Пример #12
0
 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
     };
 }
Пример #13
0
        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;
            }
        }
Пример #14
0
        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);
        }
Пример #15
0
        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);
        }
Пример #18
0
        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);
            }
        }
Пример #19
0
        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);
        }
Пример #20
0
        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);