TechnitiumSoftware/DnsServer · error · DnsServerException
Failed to add Secondary node: please try again.
Error message
Failed to add Secondary node: please try again.
What it means
Thrown by JoinCluster when Interlocked.CompareExchange fails because _clusterNodes was modified by another thread between the initial read (existingClusterNodes snapshot) and the commit attempt. This is optimistic concurrency control — the node list is an immutable snapshot swapped atomically. The message 'please try again' indicates the operation is safe to retry.
Source
Thrown at DnsServerCore/Cluster/ClusterManager.cs:720
throw new DnsServerException("Failed to add Secondary node: A node with the same DNS Server Domain Name already exists in the Cluster. Please try again after changing the Secondary node's DNS Server Domain Name.");
}
//add secondary node to cluster nodes
ClusterNode secondaryNode = new ClusterNode(this, secondaryNodeId, secondaryNodeUrl, secondaryNodeIpAddresses, ClusterNodeType.Secondary, ClusterNodeState.Unknown);
Dictionary<int, ClusterNode> updatedClusterNodes = new Dictionary<int, ClusterNode>(existingClusterNodes.Count + 1);
foreach (KeyValuePair<int, ClusterNode> existingClusterNode in existingClusterNodes)
updatedClusterNodes[existingClusterNode.Value.Id] = existingClusterNode.Value;
if (!updatedClusterNodes.TryAdd(secondaryNode.Id, secondaryNode))
throw new DnsServerException("Failed to add Secondary node: node ID already exists in the Cluster. Please try again.");
if (updatedClusterNodes.Count > 255)
throw new DnsServerException("Failed to add Secondary node: a maximum of 255 nodes are supported by the Cluster.");
IReadOnlyDictionary<int, ClusterNode> originalValue = Interlocked.CompareExchange(ref _clusterNodes, updatedClusterNodes, existingClusterNodes);
if (!ReferenceEquals(originalValue, existingClusterNodes))
throw new DnsServerException("Failed to add Secondary node: please try again.");
secondaryNode.InitializeHeartbeatTimer();
//update cluster zone and save zone file
FindExistingRecordTtlValues(out uint nsTtl, out uint aTtl); //find existing record TTL values
AddClusterPrimaryZoneRecordsFor(secondaryNode, nsTtl, aTtl, secondaryNodeCertificate);
//update cluster catalog zone ACLs and save zone file
UpdateClusterCatalogZoneOptions();
//save all changes
SaveConfigFile();
//notify all secondary nodes
TriggerNotifyAllSecondaryNodes();
//trigger NS and SOA update for member zones
TriggerRecordUpdateForClusterCatalogMemberZones();View on GitHub (pinned to d0484b6c1e)
Solutions
- Retry the JoinCluster call — the optimistic lock failure is transient by design.
- Serialize cluster membership operations (add/remove) so only one runs at a time, eliminating the race.
Example fix
// before
await _clusterManager.JoinCluster(nodeId, nodeUrl, ips, cert);
// after — retry on optimistic-lock conflict
for (int attempt = 0; attempt < 3; attempt++)
{
try
{
await _clusterManager.JoinCluster(nodeId, nodeUrl, ips, cert);
break;
}
catch (DnsServerException ex) when (ex.Message.Contains("please try again"))
{
if (attempt == 2) throw;
await Task.Delay(100 * (attempt + 1));
}
} Defensive patterns
Strategy: retry
Try / catch
for (int attempt = 0; attempt < 3; attempt++)
{
try
{
await _clusterManager.JoinClusterAsync(nodeId, nodeUrl, nodeIps, cert);
break;
}
catch (DnsServerException ex) when (ex.Message.Contains("please try again"))
{
if (attempt == 2) throw;
await Task.Delay(100 * (attempt + 1));
}
} Prevention
- Serialize cluster membership operations (add/remove) to avoid concurrent _clusterNodes modifications.
- Use a distributed lock or queue for cluster-management API calls in multi-admin environments.
- Implement retry-with-backoff for optimistic-concurrency failures on JoinCluster.
When it happens
Trigger: Two or more threads call JoinCluster or DeleteSecondaryNode concurrently. Thread A reads _clusterNodes into existingClusterNodes, builds updatedClusterNodes, but before it calls CompareExchange, thread B already swapped _clusterNodes to a new reference. The CompareExchange at line 718 returns B's reference, which is not ReferenceEquals to A's snapshot, so the guard fires.
Common situations: Concurrent API requests adding multiple Secondaries at once; a delete running in parallel with a join; automated orchestration issuing parallel membership changes.
Related errors
- Failed to delete Secondary node: please try again.
- Failed to initialize Cluster: failed to create the Cluster C
- Unable to load daily stats.
- Cannot set records. Please try again.
- Failed to set DNSSEC records. Please try again.
AI-assisted analysis of TechnitiumSoftware/DnsServer@d0484b6c1e (2026-08-13).
Data as JSON: /api/errors/38c54816397bf6f3.
Report an issue: GitHub.