ppy/osu · error · TimeoutException

Could not obtain a lock to connect. A previous attempt is li

Error message

Could not obtain a lock to connect. A previous attempt is likely stuck.

What it means

Thrown as TimeoutException by PersistentEndpointClientConnector.connect when it cannot acquire connectionLock within 10 seconds. The semaphore serializes connect/disconnect; a held lock means a previous connect or disconnect is stuck and never released.

Source

Thrown at osu.Game/Online/PersistentEndpointClientConnector.cs:92

                case APIState.Offline:
                    await disconnect(true).ConfigureAwait(true);
                    break;

                case APIState.Online:
                case APIState.RequiresSecondFactorAuth:
                    await connect().ConfigureAwait(true);
                    break;
            }
        }

        private async Task connect()
        {
            cancelExistingConnect();
            // reset retry delay to default.
            retryDelay = 3000;

            if (!await connectionLock.WaitAsync(10000).ConfigureAwait(false))
                throw new TimeoutException("Could not obtain a lock to connect. A previous attempt is likely stuck.");

            try
            {
                while (apiState.Value == APIState.RequiresSecondFactorAuth || apiState.Value == APIState.Online)
                {
                    // ensure any previous connection was disposed.
                    // this will also create a new cancellation token source.
                    await disconnect(false).ConfigureAwait(false);

                    // this token will be valid for the scope of this connection.
                    // if cancelled, we can be sure that a disconnect or reconnect is handled elsewhere.
                    var cancellationToken = connectCancelSource.Token;

                    cancellationToken.ThrowIfCancellationRequested();

                    Logger.Log($"{ClientName} connecting...", LoggingTarget.Network);

                    try

View on GitHub (pinned to d9c73e12ad)

Solutions

  1. Investigate the previous connect/disconnect that holds the lock (capture a hang dump of the task holding connectionLock).
  2. Avoid issuing concurrent Connect/Disconnect calls; serialize them through the connector's API.
  3. Ensure the underlying IConnection.DisposeAsync cannot hang (add its own timeout).
Defensive patterns

Strategy: try-catch

Validate before calling

// cannot pre-validate an async lock; avoid concurrent Connect/Disconnect calls through the connector.

Try / catch

try { await connector.Connect(); }
catch (TimeoutException) { /* previous connect stuck: report, do not immediately retry unchanged */ }

Prevention

When it happens

Trigger: connectionLock.WaitAsync(10000) returns false in connect(). A prior connect()/disconnect() is still running (deadlock, hung await, or an unobserved exception that skipped the lock release).

Common situations: Rapid reconnect/disconnect churn racing on the lock; an await inside the locked region never completing (e.g. DisposeAsync on the connection hanging); a bug where the finally releasing the lock was missed.

Related errors


AI-assisted analysis of ppy/osu@d9c73e12ad (2026-08-13). Data as JSON: /api/errors/d5cdf1d6f3dcec13. Report an issue: GitHub.