{"record":{"id":"dbf45e6d76bad4e5","repo":"TechnitiumSoftware/DnsServer","slug":"the-dns-web-service-is-already-running","errorCode":null,"errorMessage":"The DNS web service is already running.","messagePattern":"The DNS web service is already running\\.","errorType":"http","errorClass":"DnsWebServiceException","httpStatus":null,"severity":"error","filePath":"DnsServerCore/DnsWebService.cs","lineNumber":2883,"sourceCode":"        {\n            _authManager.RemoveAllPermissions(PermissionSection.Zones, e.ZoneInfo.Name);\n            _authManager.SaveConfigFile();\n\n            //delete cache for this zone to allow rebuilding cache data without using the current zone\n            _dnsServer.CacheZoneManager.DeleteZone(e.ZoneInfo.Name);\n        }\n\n        #endregion\n\n        #region public\n\n        public async Task StartAsync(bool throwIfBindFails = false)\n        {\n            if (_disposed)\n                ObjectDisposedException.ThrowIf(_disposed, this);\n\n            if (_isRunning)\n                throw new DnsWebServiceException(\"The DNS web service is already running.\");\n\n            try\n            {\n                //init dns server\n                _dnsServer = new DnsServer(_configFolder, Path.Combine(_appFolder, \"dohwww\"), _log);\n\n                //init dhcp server\n                _dhcpServer = new DhcpServer(Path.Combine(_configFolder, \"scopes\"), _log);\n                _dhcpServer.DnsServer = _dnsServer;\n                _dhcpServer.AuthManager = _authManager;\n\n                //load web service config file\n                LoadConfigFile();\n\n                //load dns config file\n                _dnsServer.LoadConfigFile();\n\n                //load all dns applications","sourceCodeStart":2865,"sourceCodeEnd":2901,"githubUrl":"https://github.com/TechnitiumSoftware/DnsServer/blob/d0484b6c1e7439cdc53d67d81e9c876cda2ad756/DnsServerCore/DnsWebService.cs#L2865-L2901","documentation":"Thrown by DnsWebService.StartAsync() as a re-entrancy guard: it checks the private _isRunning flag (set true only after a fully successful start at line 2956, cleared by StopAsync()/DisposeAsync at line 2986) and refuses to start a second concurrent instance. It is a DnsWebServiceException (custom Exception subclass), not an InvalidOperationException, so callers catching generic exceptions still see it. The library throws it because StartAsync initializes DNS/DHCP servers, binds TCP/UDP listeners, and rewrites config — running it twice would double-bind ports and corrupt shared managers.","triggerScenarios":"Calling webService.StartAsync() a second time on the same DnsWebService instance without an intervening await StopAsync(). This happens in restart loops that call StartAsync again before the previous start finished, in DI/hosted-service code where StartAsync is invoked from two paths (e.g. a health-check re-trigger and a startup hook), or when the caller assumes a failed start left the service stopped (it does — _isRunning stays false on failure — but the caller retried after a *successful* start).","commonSituations":"A generic-host IHostedService wrapper whose StartAsync is invoked by the host and then again by application code; a 'reload settings' feature that re-runs StartAsync instead of Stop+Start; container orchestrator (k8s liveness) sidecars issuing restart commands; multiple DnsWebService instances pointing at the same config folder where one is already bound to ports 5380/53443.","solutions":["Await StopAsync() (or DisposeAsync) on the existing instance and wait for it to complete before calling StartAsync() again — StartAsync only succeeds from the stopped state.","If restarting, replace the instance entirely: dispose the old DnsWebService, create a new one, then StartAsync. This avoids any half-stopped state.","Ensure StartAsync is driven from a single owner (one IHostedService / one background service) and guard the call site with your own _started flag so retry/idempotency logic never double-invokes it.","If a previous StartAsync threw, _isRunning stays false, so retrying StartAsync is safe — but verify via logs that the prior attempt actually failed rather than partially bound ports before retrying."],"exampleFix":"// before\nawait webService.StartAsync();\n// ... later, 'reload' handler mistakenly calls it again\nawait webService.StartAsync(); // throws DnsWebServiceException\n\n// after\nif (webService is not null)\n    await webService.StopAsync();\nawait webService.StartAsync(throwIfBindFails: true);","handlingStrategy":"try-catch","validationCode":"// _isRunning is private with no public accessor, so track ownership at the call site.\n// The caller must keep its own start/stop bookkeeping.\nprivate int _started; // 0 = stopped, 1 = running\n\npublic async Task StartOnceAsync(DnsWebService svc, bool throwIfBindFails = false)\n{\n    if (Interlocked.CompareExchange(ref _started, 1, 0) != 0)\n        return; // already started; avoid the double-start throw\n    try\n    {\n        await svc.StartAsync(throwIfBindFails);\n    }\n    catch\n    {\n        Volatile.Write(ref _started, 0); // start failed, _isRunning stayed false\n        throw;\n    }\n}\n\npublic async Task StopOnceAsync(DnsWebService svc)\n{\n    if (Interlocked.CompareExchange(ref _started, 0, 1) != 1)\n        return;\n    await svc.StopAsync();\n}","typeGuard":"// No public IsRunning exists; mirror the library's own state with a single-owner flag.\nbool IsServiceRunning(DnsWebService svc, ref int startedFlag)\n    => Volatile.Read(ref startedFlag) == 1 && svc is not null;","tryCatchPattern":"try\n{\n    await webService.StartAsync(throwIfBindFails: true);\n}\ncatch (DnsWebServiceException ex) when (ex.Message.Contains(\"already running\"))\n{\n    // Recover by stopping first, then starting once.\n    await webService.StopAsync();\n    await webService.StartAsync(throwIfBindFails: true);\n}\n// Do NOT swallow other DnsWebServiceException variants (bind failures, etc.) —\n// rethrow them so the caller knows the service is not up.","preventionTips":["Own StartAsync/StopAsync from exactly one component (a single IHostedService or background service); never invoke StartAsync from event handlers or health probes.","Before any restart, always await StopAsync() to completion — StartAsync is not idempotent and _isRunning only flips inside StopAsync.","Keep a local started flag (Interlocked) so retry/idempotency code cannot reach StartAsync while the service is up.","Treat a failed StartAsync as 'definitely stopped': _isRunning is set only at the very end of success, so retrying after a genuine failure is safe — but confirm via logs that it was a failure, not a successful start you mistook for one.","Use one DnsWebService instance per config folder; sharing the folder across instances causes both port-bind races and this guard."],"tags":["dns-server","technitium","lifecycle","double-start","csharp"],"backgroundTag":null,"analyzedSha":"d0484b6c1e7439cdc53d67d81e9c876cda2ad756","analyzedAt":"2026-08-13T22:57:35.508Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}