Esempio n. 1
0
        /// <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;
        }
Esempio n. 2
0
        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})");
            }
        }
Esempio n. 3
0
        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));
        }
Esempio n. 4
0
        /// <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);
Esempio n. 6
0
        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;
            }
        }
Esempio n. 7
0
 internal static Task <T> WithCancellationAndTimeout <T>(this Task <T> task, NpgsqlTimeout timeout, CancellationToken cancellationToken)
 => task.WithCancellation(cancellationToken).WithTimeout(timeout);
Esempio n. 8
0
 internal static Task WithCancellationAndTimeout(this Task task, NpgsqlTimeout timeout, CancellationToken cancellationToken)
 {
     return(task
            .WithCancellation(cancellationToken)
            .WithTimeout(timeout));
 }
Esempio n. 9
0
        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());
                }
            }
        }
Esempio n. 10
0
        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;
            }
        }
Esempio n. 11
0
        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);
        }
Esempio n. 12
0
        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();
            }
        }
Esempio n. 13
0
        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
     );
Esempio n. 15
0
 public Task <NpgsqlDatabaseInfo?> Load(NpgsqlConnector conn, NpgsqlTimeout timeout, bool async)
 => Task.FromResult(
     conn.Settings.ServerCompatibilityMode == ServerCompatibilityMode.NoTypeLoading
             ? (NpgsqlDatabaseInfo) new PostgresMinimalDatabaseInfo(conn)
             : null
     );