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
- Investigate the previous connect/disconnect that holds the lock (capture a hang dump of the task holding connectionLock).
- Avoid issuing concurrent Connect/Disconnect calls; serialize them through the connector's API.
- 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
- Do not call Connect while a Disconnect is in flight.
- Ensure IConnection.DisposeAsync cannot hang.
- On this timeout, capture diagnostics rather than retrying blindly.
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
- Could not obtain a lock to disconnect. A previous attempt is
- Timed out while loading beatmap ({beatmapInfo}).
- Screenshot data did not arrive in a timely fashion
- Beatmap submission not supported in this configuration!
- Beatmap submission not supported in this configuration!
AI-assisted analysis of ppy/osu@d9c73e12ad (2026-08-13).
Data as JSON: /api/errors/d5cdf1d6f3dcec13.
Report an issue: GitHub.