/// <summary> /// Utility that simplifies awaiting a task with a timeout. If the given task does not /// complete within <paramref name="timeout"/>, a <see cref="TimeoutException"/> is thrown. /// </summary> /// <param name="task">The task to be awaited</param> /// <param name="timeout">How much time to allow <paramref name="task"/> to complete before throwing a <see cref="TimeoutException"/></param> /// <returns>An awaitable task that represents the original task plus the timeout</returns> internal static async Task WithTimeout(this Task task, NpgsqlTimeout timeout) { if (!timeout.IsSet) { await task; return; } var timeLeft = timeout.TimeLeft; if (timeLeft < TimeSpan.Zero) { throw new TimeoutException(); } var timeoutTask = Task.Delay(timeLeft); if (task != await Task.WhenAny(task, timeoutTask)) { throw new TimeoutException(); } await task; }
async Task Authenticate(string username, NpgsqlTimeout timeout, bool async, CancellationToken cancellationToken) { Log.Trace("Authenticating...", Id); timeout.CheckAndApply(this); var msg = Expect <AuthenticationRequestMessage>(await ReadMessage(async), this); switch (msg.AuthRequestType) { case AuthenticationRequestType.AuthenticationOk: return; case AuthenticationRequestType.AuthenticationCleartextPassword: await AuthenticateCleartext(username, async, cancellationToken); return; case AuthenticationRequestType.AuthenticationMD5Password: await AuthenticateMD5(username, ((AuthenticationMD5PasswordMessage)msg).Salt, async, cancellationToken); return; case AuthenticationRequestType.AuthenticationSASL: await AuthenticateSASL(((AuthenticationSASLMessage)msg).Mechanisms, username, async, cancellationToken); return; case AuthenticationRequestType.AuthenticationGSS: case AuthenticationRequestType.AuthenticationSSPI: await AuthenticateGSS(async); return; case AuthenticationRequestType.AuthenticationGSSContinue: throw new NpgsqlException("Can't start auth cycle with AuthenticationGSSContinue"); default: throw new NotSupportedException($"Authentication method not supported (Received: {msg.AuthRequestType})"); } }
internal ValueTask <NpgsqlConnector> Allocate(NpgsqlConnection conn, NpgsqlTimeout timeout, bool async, CancellationToken cancellationToken) { Monitor.Enter(this); while (Idle.Count > 0) { var connector = Idle.Pop(); // An idle connector could be broken because of a keepalive if (connector.IsBroken) { continue; } connector.Connection = conn; IncrementBusy(); EnsurePruningTimerState(); Monitor.Exit(this); return(new ValueTask <NpgsqlConnector>(connector)); } // No idle connectors available. Have to actually open a new connector or wait for one. return(AllocateLong(conn, timeout, async, cancellationToken)); }
/// <summary> /// Called exactly once per multiplexing pool, when the first connection is opened, with two goals: /// 1. Load types and bind the pool-wide type mapper (necessary for binding parameters) /// 2. Cause any connection exceptions (e.g. bad username) to be thrown from NpgsqlConnection.Open /// </summary> internal async Task BootstrapMultiplexing(NpgsqlConnection conn, NpgsqlTimeout timeout, bool async, CancellationToken cancellationToken = default) { Debug.Assert(_multiplexing); var hasSemaphore = async ? await _bootstrapSemaphore !.WaitAsync(timeout.TimeLeft, cancellationToken) : _bootstrapSemaphore !.Wait(timeout.TimeLeft, cancellationToken); // We've timed out - calling Check, to throw the correct exception if (!hasSemaphore) { timeout.Check(); } try { if (IsBootstrapped) { return; } var connector = await conn.StartBindingScope(ConnectorBindingScope.Connection, timeout, async, cancellationToken); using var _ = Defer(() => conn.EndBindingScope(ConnectorBindingScope.Connection)); // Somewhat hacky. Extract the connector's type mapper as our pool-wide mapper, // and have the connector rebind to ensure it has a different instance. // The latter isn't strictly necessary (type mappers should always be usable // concurrently) but just in case. MultiplexingTypeMapper = connector.TypeMapper; connector.RebindTypeMapper(); IsBootstrapped = true; } finally { _bootstrapSemaphore !.Release(); } }
/// <summary> /// Called exactly once per multiplexing pool, when the first connection is opened, with two goals: /// 1. Load types and bind the pool-wide type mapper (necessary for binding parameters) /// 2. Cause any connection exceptions (e.g. bad username) to be thrown from NpgsqlConnection.Open /// </summary> internal async Task BootstrapMultiplexing(NpgsqlConnection conn, NpgsqlTimeout timeout, bool async, CancellationToken cancellationToken = default) { Debug.Assert(_multiplexing); var hasSemaphore = async ? await _bootstrapSemaphore !.WaitAsync(timeout.TimeLeft, cancellationToken) : _bootstrapSemaphore !.Wait(timeout.TimeLeft, cancellationToken); // We've timed out - calling Check, to throw the correct exception if (!hasSemaphore) { timeout.Check(); } try { if (IsBootstrapped) { return; } var connector = await conn.StartBindingScope(ConnectorBindingScope.Connection, timeout, async, cancellationToken); using var _ = Defer(static conn => conn.EndBindingScope(ConnectorBindingScope.Connection), conn);
internal async ValueTask <NpgsqlConnector> AllocateLong(NpgsqlConnection conn, NpgsqlTimeout timeout, bool async, CancellationToken cancellationToken) { Debug.Assert(Monitor.IsEntered(this)); NpgsqlConnector connector; Debug.Assert(Busy <= _max); if (Busy == _max) { // TODO: Async cancellation var tcs = new TaskCompletionSource <NpgsqlConnector>(); _waiting.Enqueue(new WaitingOpenAttempt { TaskCompletionSource = tcs, IsAsync = async }); Monitor.Exit(this); try { if (async) { if (timeout.IsSet) { var timeLeft = timeout.TimeLeft; if (timeLeft <= TimeSpan.Zero || tcs.Task != await Task.WhenAny(tcs.Task, Task.Delay(timeLeft))) { throw new NpgsqlException($"The connection pool has been exhausted, either raise MaxPoolSize (currently {_max}) or Timeout (currently {Settings.Timeout} seconds)"); } } else { await tcs.Task; } } else { if (timeout.IsSet) { var timeLeft = timeout.TimeLeft; if (timeLeft <= TimeSpan.Zero || !tcs.Task.Wait(timeLeft)) { throw new NpgsqlException($"The connection pool has been exhausted, either raise MaxPoolSize (currently {_max}) or Timeout (currently {Settings.Timeout} seconds)"); } } else { tcs.Task.Wait(); } } } catch { // We're here if the timeout expired or the cancellation token was triggered // Re-lock and check in case the task was set to completed after coming out of the Wait lock (this) { if (!tcs.Task.IsCompleted) { tcs.SetCanceled(); throw; } } } connector = tcs.Task.Result; connector.Connection = conn; return(connector); } // No idle connectors are available, and we're under the pool's maximum capacity. IncrementBusy(); Monitor.Exit(this); try { connector = new NpgsqlConnector(conn) { ClearCounter = _clearCounter }; await connector.Open(timeout, async, cancellationToken); Counters.NumberOfPooledConnections.Increment(); EnsureMinPoolSize(conn); return(connector); } catch { lock (this) DecrementBusy(); throw; } }
internal static Task <T> WithCancellationAndTimeout <T>(this Task <T> task, NpgsqlTimeout timeout, CancellationToken cancellationToken) => task.WithCancellation(cancellationToken).WithTimeout(timeout);
internal static Task WithCancellationAndTimeout(this Task task, NpgsqlTimeout timeout, CancellationToken cancellationToken) { return(task .WithCancellation(cancellationToken) .WithTimeout(timeout)); }
internal async Task <List <PostgresType> > LoadBackendTypes(NpgsqlConnection conn, NpgsqlTimeout timeout, bool async) { var commandTimeout = 0; // Default to infinity if (timeout.IsSet) { commandTimeout = (int)timeout.TimeLeft.TotalSeconds; if (commandTimeout <= 0) { throw new TimeoutException(); } } var typeLoadingQuery = GenerateTypesQuery(SupportsRangeTypes, SupportsEnumTypes, conn.Settings.LoadTableComposites); using (var command = new NpgsqlCommand(typeLoadingQuery, conn)) { command.CommandTimeout = commandTimeout; command.AllResultTypesAreUnknown = true; using (var reader = async ? await command.ExecuteReaderAsync() : command.ExecuteReader()) { var byOID = new Dictionary <uint, PostgresType>(); // First load the types themselves while (async ? await reader.ReadAsync() : reader.Read()) { timeout.Check(); var ns = reader.GetString(reader.GetOrdinal("nspname")); var internalName = reader.GetString(reader.GetOrdinal("typname")); var oid = Convert.ToUInt32(reader[reader.GetOrdinal("oid")]); Debug.Assert(internalName != null); Debug.Assert(oid != 0); var typeChar = reader.GetString(reader.GetOrdinal("type"))[0]; switch (typeChar) { case 'b': // Normal base type var baseType = new PostgresBaseType(ns, internalName, oid); byOID[baseType.OID] = baseType; continue; case 'a': // Array { var elementOID = Convert.ToUInt32(reader[reader.GetOrdinal("elemoid")]); Debug.Assert(elementOID > 0); if (!byOID.TryGetValue(elementOID, out var elementPostgresType)) { Log.Trace($"Array type '{internalName}' refers to unknown element with OID {elementOID}, skipping", conn.ProcessID); continue; } var arrayType = new PostgresArrayType(ns, internalName, oid, elementPostgresType); byOID[arrayType.OID] = arrayType; continue; } case 'r': // Range { var elementOID = Convert.ToUInt32(reader[reader.GetOrdinal("elemoid")]); Debug.Assert(elementOID > 0); if (!byOID.TryGetValue(elementOID, out var subtypePostgresType)) { Log.Trace($"Range type '{internalName}' refers to unknown subtype with OID {elementOID}, skipping", conn.ProcessID); continue; } var rangeType = new PostgresRangeType(ns, internalName, oid, subtypePostgresType); byOID[rangeType.OID] = rangeType; continue; } case 'e': // Enum var enumType = new PostgresEnumType(ns, internalName, oid); byOID[enumType.OID] = enumType; continue; case 'c': // Composite // Unlike other types, we don't var compositeType = new PostgresCompositeType(ns, internalName, oid); byOID[compositeType.OID] = compositeType; continue; case 'd': // Domain var baseTypeOID = Convert.ToUInt32(reader[reader.GetOrdinal("typbasetype")]); Debug.Assert(baseTypeOID > 0); if (!byOID.TryGetValue(baseTypeOID, out var basePostgresType)) { Log.Trace($"Domain type '{internalName}' refers to unknown base type with OID {baseTypeOID}, skipping", conn.ProcessID); continue; } var domainType = new PostgresDomainType(ns, internalName, oid, basePostgresType); byOID[domainType.OID] = domainType; continue; case 'p': // pseudo-type (record, void) // Hack this as a base type goto case 'b'; default: throw new ArgumentOutOfRangeException($"Unknown typtype for type '{internalName}' in pg_type: {typeChar}"); } } if (async) { await reader.NextResultAsync(); } else { reader.NextResult(); } LoadCompositeFields(reader, byOID); if (SupportsEnumTypes) { if (async) { await reader.NextResultAsync(); } else { reader.NextResult(); } LoadEnumLabels(reader, byOID); } return(byOID.Values.ToList()); } } }
internal NpgsqlConnector Allocate(NpgsqlConnection conn, NpgsqlTimeout timeout) { NpgsqlConnector connector; Monitor.Enter(this); while (Idle.Count > 0) { connector = Idle.Pop(); // An idle connector could be broken because of a keepalive if (connector.IsBroken) { continue; } connector.Connection = conn; Busy++; EnsurePruningTimerState(); Monitor.Exit(this); return(connector); } Contract.Assert(Busy <= _max); if (Busy == _max) { // TODO: Async cancellation var tcs = new TaskCompletionSource <NpgsqlConnector>(); EnqueueWaitingOpenAttempt(tcs); Monitor.Exit(this); try { WaitForTask(tcs.Task, timeout.TimeLeft); } catch { // We're here if the timeout expired or the cancellation token was triggered // Re-lock and check in case the task was set to completed after coming out of the Wait lock (this) { if (!tcs.Task.IsCompleted) { tcs.SetCanceled(); throw; } } } connector = tcs.Task.Result; connector.Connection = conn; return(connector); } // No idle connectors are available, and we're under the pool's maximum capacity. Busy++; Monitor.Exit(this); try { connector = new NpgsqlConnector(conn) { ClearCounter = _clearCounter }; connector.Open(timeout); EnsureMinPoolSize(conn); return(connector); } catch { lock (this) Busy--; throw; } }
static CancellationToken GetCombinedCancellationToken(ref CancellationTokenSource?combinedCts, NpgsqlTimeout timeout, CancellationToken cancellationToken) { var finalCt = cancellationToken; if (timeout.IsSet) { combinedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); combinedCts.CancelAfter((int)timeout.CheckAndGetTimeLeft().TotalMilliseconds); finalCt = combinedCts.Token; } return(finalCt); }
internal static async Task <TResult> ExecuteWithTimeout <TResult>(Func <CancellationToken, Task <TResult> > func, NpgsqlTimeout timeout, CancellationToken cancellationToken) { CancellationTokenSource?combinedCts = null; try { var combinedCancellationToken = GetCombinedCancellationToken(ref combinedCts, timeout, cancellationToken); return(await func(combinedCancellationToken)); } finally { combinedCts?.Dispose(); } }
static async Task <AvailablePostgresTypes> LoadBackendTypes(NpgsqlConnector connector, NpgsqlTimeout timeout, bool async) { var commandTimeout = 0; // Default to infinity if (timeout.IsSet) { commandTimeout = (int)timeout.TimeLeft.TotalSeconds; if (commandTimeout <= 0) { throw new TimeoutException(); } } var types = new AvailablePostgresTypes(); using (var command = new NpgsqlCommand(connector.SupportsRangeTypes ? TypesQueryWithRange : TypesQueryWithoutRange, connector.Connection)) { command.CommandTimeout = commandTimeout; command.AllResultTypesAreUnknown = true; using (var reader = async ? await command.ExecuteReaderAsync() : command.ExecuteReader()) { while (async ? await reader.ReadAsync() : reader.Read()) { timeout.Check(); LoadBackendType(reader, types, connector); } } } return(types); }
public Task <NpgsqlDatabaseInfo?> Load(NpgsqlConnection conn, NpgsqlTimeout timeout, bool async) => Task.FromResult( new NpgsqlConnectionStringBuilder(conn.ConnectionString).ServerCompatibilityMode == ServerCompatibilityMode.NoTypeLoading ? (NpgsqlDatabaseInfo) new PostgresMinimalDatabaseInfo(conn) : null );
public Task <NpgsqlDatabaseInfo?> Load(NpgsqlConnector conn, NpgsqlTimeout timeout, bool async) => Task.FromResult( conn.Settings.ServerCompatibilityMode == ServerCompatibilityMode.NoTypeLoading ? (NpgsqlDatabaseInfo) new PostgresMinimalDatabaseInfo(conn) : null );