{"record":{"id":"1069fa5fc33fb18b","repo":"ppy/osu","slug":"could-not-obtain-a-lock-to-disconnect-a-previous","errorCode":null,"errorMessage":"Could not obtain a lock to disconnect. A previous attempt is likely stuck.","messagePattern":"Could not obtain a lock to disconnect\\. A previous attempt is likely stuck\\.","errorType":"exception","errorClass":"TimeoutException","httpStatus":null,"severity":"error","filePath":"osu.Game/Online/PersistentEndpointClientConnector.cs","lineNumber":188,"sourceCode":"                await handleErrorAndDelay(ex, CancellationToken.None).ConfigureAwait(false);\r\n            else\r\n                Logger.Log($\"{ClientName} disconnected\", LoggingTarget.Network);\r\n\r\n            // make sure a disconnect wasn't triggered (and this is still the active connection).\r\n            if (!hasBeenCancelled)\r\n                await Task.Run(connect, CancellationToken.None).ConfigureAwait(false);\r\n        }\r\n\r\n        protected Task Disconnect() => disconnect(true);\r\n\r\n        private async Task disconnect(bool takeLock)\r\n        {\r\n            cancelExistingConnect();\r\n\r\n            if (takeLock)\r\n            {\r\n                if (!await connectionLock.WaitAsync(10000).ConfigureAwait(false))\r\n                    throw new TimeoutException(\"Could not obtain a lock to disconnect. A previous attempt is likely stuck.\");\r\n            }\r\n\r\n            try\r\n            {\r\n                if (CurrentConnection != null)\r\n                    await CurrentConnection.DisposeAsync().ConfigureAwait(false);\r\n            }\r\n            finally\r\n            {\r\n                isConnected.Value = false;\r\n                CurrentConnection = null;\r\n\r\n                if (takeLock)\r\n                    connectionLock.Release();\r\n            }\r\n        }\r\n\r\n        private void cancelExistingConnect()\r","sourceCodeStart":170,"sourceCodeEnd":206,"githubUrl":"https://github.com/ppy/osu/blob/d9c73e12adff2feaae4a3e158d36fe5883faf6ca/osu.Game/Online/PersistentEndpointClientConnector.cs#L170-L206","documentation":"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.","triggerScenarios":"Calling Disconnect() (which calls disconnect(true)) while a connect() is mid-flight and holding connectionLock, or a prior disconnect never released it.","commonSituations":"Disconnect issued concurrently with a connect attempt; a hung connection disposal keeping the lock; shutdown path racing a reconnect.","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."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// cannot pre-validate an async lock; serialize disconnect with connect via the connector state machine.","typeGuard":null,"tryCatchPattern":"try { await connector.Disconnect(); }\ncatch (TimeoutException) { /* prior op holds the lock: investigate, do not hammer */ }","preventionTips":["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."],"tags":["network","connector","deadlock","timeout","concurrency"],"backgroundTag":null,"analyzedSha":"d9c73e12adff2feaae4a3e158d36fe5883faf6ca","analyzedAt":"2026-08-13T14:12:54.015Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}