{"record":{"id":"5de5df02e022b21e","repo":"TechnitiumSoftware/DnsServer","slug":"failed-to-delete-secondary-node-please-try-again","errorCode":null,"errorMessage":"Failed to delete Secondary node: please try again.","messagePattern":"Failed to delete Secondary node: please try again\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"warning","filePath":"DnsServerCore/Cluster/ClusterManager.cs","lineNumber":794,"sourceCode":"                throw new DnsServerException(\"Failed to delete Secondary node: the specified node does not exist in the Cluster.\");\n\n            if (secondaryNode.Type == ClusterNodeType.Primary)\n                throw new DnsServerException(\"Failed to delete Secondary node: the specified node is the Cluster Primary node and cannot be deleted.\");\n\n            //delete secondary node from cluster nodes\n            Dictionary<int, ClusterNode> updatedClusterNodes = new Dictionary<int, ClusterNode>(existingClusterNodes.Count - 1);\n\n            foreach (KeyValuePair<int, ClusterNode> existingClusterNode in existingClusterNodes)\n            {\n                if (existingClusterNode.Key == secondaryNodeId)\n                    continue;\n\n                updatedClusterNodes[existingClusterNode.Key] = existingClusterNode.Value;\n            }\n\n            IReadOnlyDictionary<int, ClusterNode> originalValue = Interlocked.CompareExchange(ref _clusterNodes, updatedClusterNodes, existingClusterNodes);\n            if (!ReferenceEquals(originalValue, existingClusterNodes))\n                throw new InvalidOperationException(\"Failed to delete Secondary node: please try again.\");\n\n            secondaryNode.Dispose();\n\n            //update cluster zone and save zone file\n            RemoveClusterPrimaryZoneRecordsFor(secondaryNode);\n\n            //update cluster catalog zone ACLs and save zone file\n            UpdateClusterCatalogZoneOptions();\n\n            //save all changes\n            SaveConfigFile();\n\n            //notify all secondary nodes\n            TriggerNotifyAllSecondaryNodes();\n\n            //trigger NS and SOA update for member zones\n            TriggerRecordUpdateForClusterCatalogMemberZones();\n","sourceCodeStart":776,"sourceCodeEnd":812,"githubUrl":"https://github.com/TechnitiumSoftware/DnsServer/blob/d0484b6c1e7439cdc53d67d81e9c876cda2ad756/DnsServerCore/Cluster/ClusterManager.cs#L776-L812","documentation":"Thrown as InvalidOperationException when the lock-free swap of the cluster node table fails. DeleteSecondaryNode snapshots _clusterNodes, builds a copy without the target, then publishes it via Interlocked.CompareExchange; if another thread changed _clusterNodes in between, the swap is aborted and you are told to retry. This is a transient optimistic-concurrency conflict, not data corruption.","triggerScenarios":"Two concurrent Delete/Add/Update Secondary calls on the same Primary; a 'remove node' button clicked twice quickly; parallel admin scripts mutating cluster topology.","commonSituations":"Concurrent cluster mutations under load; a retry storm from an impatient operator; a race between a heartbeat-driven update and an admin delete.","solutions":["Retry the identical DeleteSecondaryNode call - the snapshot refreshes and the swap should succeed","Serialize cluster-mutation calls behind a single client-side queue/lock","Allow only one outstanding cluster topology change at a time"],"exampleFix":"// before\nclusterManager.DeleteSecondaryNode(id);\n\n// after\nfor (int attempt = 0; ; attempt++)\n{\n    try { clusterManager.DeleteSecondaryNode(id); break; }\n    catch (InvalidOperationException ex) when (ex.Message.Contains(\"please try again\") && attempt < 5)\n        await Task.Delay(TimeSpan.FromMilliseconds(200 * (attempt + 1)));\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"catch (InvalidOperationException ex) when (ex.Message.Contains(\"please try again\"))\n{\n    // transient optimistic-concurrency conflict; retry after brief backoff\n}","preventionTips":["Treat InvalidOperationException 'please try again' as transient and retry with backoff","Funnel all cluster topology mutations through a single serialized queue"],"tags":["cluster","concurrency","optimistic-locking","retry","dns-server"],"backgroundTag":null,"analyzedSha":"d0484b6c1e7439cdc53d67d81e9c876cda2ad756","analyzedAt":"2026-08-13T22:57:35.508Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}