{"record":{"id":"11aaebd9ebb775cd","repo":"microsoft/aspire","slug":"failed-to-acquire-semaphore-for-settings-file-settingspath","errorCode":null,"errorMessage":"Failed to acquire semaphore for settings file: {settingsPath}","messagePattern":"Failed to acquire semaphore for settings file: (.+?)","errorType":"exception","errorClass":"DistributedApplicationException","httpStatus":null,"severity":"error","filePath":"src/Aspire.Hosting/Devcontainers/DevcontainerSettingsWriter.cs","lineNumber":114,"sourceCode":"        catch (OperationCanceledException) when (_processingCancellation.IsCancellationRequested)\n        {\n            // Normal shutdown.\n        }\n    }\n\n    private async Task WriteSettingsAsync(IReadOnlyList<PortForwardEntry> newPorts, CancellationToken cancellationToken)\n    {\n        var settingsPaths = GetSettingsPaths();\n        // Collect ports we actually wrote so we can log them AFTER the file save completes.\n        List<(string Label, string Url)> portsToLog = [];\n\n        foreach (var settingsPath in settingsPaths)\n        {\n            var acquired = await _writeLock.WaitAsync(WriteLockTimeoutMs, cancellationToken).ConfigureAwait(false);\n\n            if (!acquired)\n            {\n                throw new DistributedApplicationException($\"Failed to acquire semaphore for settings file: {settingsPath}\");\n            }\n\n            await EnsureSettingsFileExists(settingsPath, cancellationToken).ConfigureAwait(false);\n\n            var settingsContent = await File.ReadAllTextAsync(settingsPath, cancellationToken).ConfigureAwait(false);\n            var settings = (JsonObject)JsonObject.Parse(settingsContent)!;\n\n            JsonObject? portsAttributes;\n            if (!settings.TryGetPropertyValue(PortAttributesFieldName, out var portsAttributesNode))\n            {\n                portsAttributes = [];\n                settings.Add(PortAttributesFieldName, portsAttributes);\n            }\n            else\n            {\n                portsAttributes = (JsonObject)portsAttributesNode!;\n            }\n","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/microsoft/aspire/blob/25830f84bd145686607ad00c057b3f84e2e51d43/src/Aspire.Hosting/Devcontainers/DevcontainerSettingsWriter.cs#L96-L132","documentation":"DevcontainerSettingsWriter serializes concurrent writes to devcontainer settings files with a semaphore that has a timeout (WriteLockTimeoutMs). If the lock cannot be acquired within that time while processing a port update, it throws DistributedApplicationException naming the settings file it failed to lock.","triggerScenarios":"WriteSettingsAsync/ProcessPortUpdatesAsync called while another writer holds the lock longer than WriteLockTimeoutMs — e.g. overlapping port-update operations or a hung prior write to the same settings path.","commonSituations":"Multiple Aspire operations racing to update the devcontainer settings, a very slow disk or network-mounted home directory, another process/tool holding the settings file busy causing long writes, or a leaked lock from a cancelled but still-running task.","solutions":["Retry the settings update after a short delay once competing writes finish.","Serialize your own updates so only one settings write runs at a time in your process.","Increase the write-lock timeout if writes are legitimately slow (adjust WriteLockTimeoutMs or the hosting code).","Check for a stuck Aspire/host process still holding the semaphore and terminate it.","Verify the settings file location is on local disk, not a slow network mount."],"exampleFix":"// before\nawait settingsWriter.WriteSettingsAsync(...); // races with another writer -> lock timeout\n// after\nawait _updateGate.WaitAsync(); // app-level serialization\ntry { await settingsWriter.WriteSettingsAsync(...); }\nfinally { _updateGate.Release(); }","handlingStrategy":"retry","validationCode":"// detect concurrent writers before updating\nif (Interlocked.CompareExchange(ref updating, 1, 1) == 1) Console.WriteLine(\"A settings update is already in progress\");","typeGuard":null,"tryCatchPattern":"const int MaxRetries = 3;\nfor (var attempt = 1; ; attempt++)\n{\n    try { await settingsWriter.WriteSettingsAsync(...); break; }\n    catch (DistributedApplicationException ex) when (ex.Message.StartsWith(\"Failed to acquire semaphore\") && attempt < MaxRetries)\n    { await Task.Delay(TimeSpan.FromSeconds(2 * attempt)); }\n}","preventionTips":["Serialize devcontainer settings updates in your tooling","Avoid writing settings from multiple processes concurrently","Keep settings paths on local disk","Retry transient lock failures with backoff"],"tags":["devcontainer","concurrency","semaphore","timeout"],"backgroundTag":"request-timeout","analyzedSha":"25830f84bd145686607ad00c057b3f84e2e51d43","analyzedAt":"2026-09-16T11:10:06.193Z","contentChangedAt":"2026-09-16T11:10:06.193Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}