ppy/osu · error · TimeoutException
Could not obtain a lock to disconnect. A previous attempt is
Error message
Could not obtain a lock to disconnect. A previous attempt is likely stuck.
What it means
Thrown as TimeoutException by PersistentEndpointClientConnector.disconnect when takeLock is true and connectionLock cannot be acquired within 10 seconds. Same semaphore as connect; a stuck prior operation blocks the disconnect.
Source
Thrown at osu.Game/Online/PersistentEndpointClientConnector.cs:188
await handleErrorAndDelay(ex, CancellationToken.None).ConfigureAwait(false);
else
Logger.Log($"{ClientName} disconnected", LoggingTarget.Network);
// make sure a disconnect wasn't triggered (and this is still the active connection).
if (!hasBeenCancelled)
await Task.Run(connect, CancellationToken.None).ConfigureAwait(false);
}
protected Task Disconnect() => disconnect(true);
private async Task disconnect(bool takeLock)
{
cancelExistingConnect();
if (takeLock)
{
if (!await connectionLock.WaitAsync(10000).ConfigureAwait(false))
throw new TimeoutException("Could not obtain a lock to disconnect. A previous attempt is likely stuck.");
}
try
{
if (CurrentConnection != null)
await CurrentConnection.DisposeAsync().ConfigureAwait(false);
}
finally
{
isConnected.Value = false;
CurrentConnection = null;
if (takeLock)
connectionLock.Release();
}
}
private void cancelExistingConnect()
View on GitHub (pinned to d9c73e12ad)
Solutions
- Coordinate connect/disconnect so they don't run simultaneously (the connector's state machine already attempts this; respect it).
- Diagnose the task holding connectionLock (await hang inside the locked region).
- Ensure CurrentConnection.DisposeAsync completes promptly or has its own timeout.
Defensive patterns
Strategy: try-catch
Validate before calling
// cannot pre-validate an async lock; serialize disconnect with connect via the connector state machine.
Try / catch
try { await connector.Disconnect(); }
catch (TimeoutException) { /* prior op holds the lock: investigate, do not hammer */ } Prevention
- Route all connect/disconnect through the connector, not concurrently.
- Guarantee the connection's DisposeAsync completes or times out.
- Treat repeated lock timeouts as a defect to diagnose, not retry.
When it happens
Trigger: Calling Disconnect() (which calls disconnect(true)) while a connect() is mid-flight and holding connectionLock, or a prior disconnect never released it.
Common situations: Disconnect issued concurrently with a connect attempt; a hung connection disposal keeping the lock; shutdown path racing a reconnect.
Related errors
- Could not obtain a lock to connect. A previous attempt is li
- 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/1069fa5fc33fb18b.
Report an issue: GitHub.