private async Task <PooledSession> CreatePooledSessionAsync(CancellationToken cancellationToken) { bool success = false; bool canceled = false; Interlocked.Increment(ref _inFlightSessionCreationCount); Interlocked.Increment(ref _liveOrRequestedSessionCount); try { var callSettings = Client.Settings.CreateSessionSettings .WithExpiration(Expiration.FromTimeout(Options.Timeout)) .WithCancellationToken(cancellationToken); Session sessionProto; bool acquiredSemaphore = false; try { await Parent._sessionAcquisitionSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); acquiredSemaphore = true; sessionProto = await Client.CreateSessionAsync(_createSessionRequest, callSettings).ConfigureAwait(false); success = true; return(PooledSession.FromSessionName(this, sessionProto.SessionName)); } catch (OperationCanceledException) { canceled = true; throw; } finally { if (acquiredSemaphore) { Parent._sessionAcquisitionSemaphore.Release(); } } } finally { // Atomically set _healthy and determine whether we were previously healthy, but only if either we've succeeded, // or we failed for a reason other than cancellation. We don't want to go unhealthy just because a caller cancelled // a cancellation token before we had chance to create the session. if (success || !canceled) { bool wasHealthy = Interlocked.Exchange(ref _healthy, success ? 1 : 0) == 1; if (wasHealthy != success) { Parent._logger.Info(() => $"Session pool for {_databaseName} is now {(success ? "healthy" : "unhealthy")}."); } } // If this call failed, we can make another attempt. if (!success) { Interlocked.Decrement(ref _liveOrRequestedSessionCount); } Interlocked.Decrement(ref _inFlightSessionCreationCount); } }
/// <summary> /// Creates a <see cref="PooledSession"/> with a known name and transaction ID/mode, with the client associated /// with this pool, but is otherwise not part of this pool. This method does not query the server for the session state. /// When the returned <see cref="PooledSession"/> is released, it will not become part of this pool, and the transaction /// will not be rolled back. /// </summary> /// <remarks> /// This is typically used for partitioned queries, where the same session is used across multiple machines, so should /// not be reused by the pool. /// </remarks> /// <param name="sessionName">The name of the transaction. Must not be null.</param> /// <param name="transactionId">The ID of the transaction. Must not be null.</param> /// <param name="transactionMode">The mode of the transaction.</param> /// <returns>A <see cref="PooledSession"/> for the given session and transaction.</returns> public PooledSession CreateDetachedSession(SessionName sessionName, ByteString transactionId, ModeOneofCase transactionMode) { GaxPreconditions.CheckNotNull(sessionName, nameof(sessionName)); GaxPreconditions.CheckNotNull(transactionId, nameof(transactionId)); return(PooledSession.FromSessionName(_detachedSessionPool, sessionName).WithTransaction(transactionId, transactionMode)); }