🤖 AI: Every 15 minutes a registered server re-resolves its directory address on the thread that runs the mix, and the mix waits for the resolver.
SetRegistered() calls NetworkUtil::ParseNetworkAddress() inline, driven by a SERVLIST_REGIST_INTERV_MINUTES = 15 timer. CServer::OnTimer is serviced by the same thread — CHighPrecisionTimer reaches it through a queued connection — so no audio is mixed while the resolve is outstanding. QHostInfo::fromName blocks with no timeout of its own and pumps no events. The SRV attempt before it spins processEvents for up to DNS_SRV_RESOLVE_TIMEOUT_MS = 500 instead, and the last row below is that spin costing nothing: only fromName reaches the mix.
Two servers, one connected client each, registered to a directory answering SRR_REGISTERED through a stub resolver with a per-name latency. Each server's own maximum audio-timer lateness, in the record before its re-registration and the record covering it:
| build |
resolver latency |
that server |
the other server, same run |
| unpatched |
300 ms |
87.0 -> 303.5 ms |
218.8 -> 218.8 ms |
| unpatched |
1500 ms |
192.5 -> 1506.5 ms |
150.5 -> 150.5 ms |
| unpatched |
1500 ms |
115.9 -> 1538.6 ms |
164.0 -> 164.0 ms |
| patched |
1500 ms |
131.2 -> 131.2 ms |
144.9 -> 154.0 ms |
| unpatched |
SRV timed out at 498 ms, A/AAAA instant |
94.6 -> 94.6 ms |
145.9 -> 145.9 ms |
The patched build reuses the address resolved earlier for that directory string and refreshes it with an asynchronous QHostInfo::lookupHost. It still performed the lookup: the registration went out at 23:14:12.207Z and its answer arrived at 23:14:13.711Z. The first registration after startup still resolves inline, since nothing is cached yet.
In the third row the unstalled server was given -e <ip>:<port> rather than a hostname, with every name the stub resolver did not recognise also delayed 1500 ms; it registered on its 15-minute schedule and issued no DNS query at all. An address literal is a workaround where the directory address is stable.
The last row used a port-less address, the only form that reaches the SRV path; its own A/AAAA lookup answered instantly, so the 498 ms spent waiting for a deliberately unanswerable SRV record is all that row measures.
The refresh timer is not the only entry to this resolve. On a live server, SetDirectoryAddress() and SetDirectoryType() each call Unregister() then Register() — the old address is resolved, then the new one — reachable mid-session from the server GUI and from jamulusserver/setDirectory. Measured on a main build at this commit (3.12.5dev-c1ceb104), reading the server's own outbound packet stream with a client connected, old name at 300 ms and new name at 1500 ms: outbound audio halts for 301.6 ms, the unregister message leaves for the directory, and audio halts again for 1502.5 ms — 1.8 s of mix outage for one settings change. On quit it resolves once more, and the list manager's handler runs before the one that tells the clients: with a player still connected and --discononquit set, SIGTERM stops the mix and the next packet of any kind leaves at +1.506 s on the delayed server against +0.005 s on the control — the directory's unregister and the player's own CLDisconnection both waiting behind the resolver. A packet that needs no resolver leaves at +0.002 s on both. A cached address cannot remove the change-path stall, because the new directory string has no cached answer by construction.
Fix: resolve asynchronously and register when the answer arrives. Reusing the last resolved address (the patched build above) removes the periodic stall but not the change-path one.
🤖 This message was written by AI and reviewed by @mcfnord.
🤖 AI: Every 15 minutes a registered server re-resolves its directory address on the thread that runs the mix, and the mix waits for the resolver.
SetRegistered()callsNetworkUtil::ParseNetworkAddress()inline, driven by aSERVLIST_REGIST_INTERV_MINUTES= 15 timer.CServer::OnTimeris serviced by the same thread —CHighPrecisionTimerreaches it through a queued connection — so no audio is mixed while the resolve is outstanding.QHostInfo::fromNameblocks with no timeout of its own and pumps no events. The SRV attempt before it spinsprocessEventsfor up toDNS_SRV_RESOLVE_TIMEOUT_MS= 500 instead, and the last row below is that spin costing nothing: onlyfromNamereaches the mix.Two servers, one connected client each, registered to a directory answering
SRR_REGISTEREDthrough a stub resolver with a per-name latency. Each server's own maximum audio-timer lateness, in the record before its re-registration and the record covering it:The patched build reuses the address resolved earlier for that directory string and refreshes it with an asynchronous
QHostInfo::lookupHost. It still performed the lookup: the registration went out at 23:14:12.207Z and its answer arrived at 23:14:13.711Z. The first registration after startup still resolves inline, since nothing is cached yet.In the third row the unstalled server was given
-e <ip>:<port>rather than a hostname, with every name the stub resolver did not recognise also delayed 1500 ms; it registered on its 15-minute schedule and issued no DNS query at all. An address literal is a workaround where the directory address is stable.The last row used a port-less address, the only form that reaches the SRV path; its own A/AAAA lookup answered instantly, so the 498 ms spent waiting for a deliberately unanswerable SRV record is all that row measures.
The refresh timer is not the only entry to this resolve. On a live server,
SetDirectoryAddress()andSetDirectoryType()each callUnregister()thenRegister()— the old address is resolved, then the new one — reachable mid-session from the server GUI and fromjamulusserver/setDirectory. Measured on amainbuild at this commit (3.12.5dev-c1ceb104), reading the server's own outbound packet stream with a client connected, old name at 300 ms and new name at 1500 ms: outbound audio halts for 301.6 ms, the unregister message leaves for the directory, and audio halts again for 1502.5 ms — 1.8 s of mix outage for one settings change. On quit it resolves once more, and the list manager's handler runs before the one that tells the clients: with a player still connected and--discononquitset, SIGTERM stops the mix and the next packet of any kind leaves at +1.506 s on the delayed server against +0.005 s on the control — the directory's unregister and the player's ownCLDisconnectionboth waiting behind the resolver. A packet that needs no resolver leaves at +0.002 s on both. A cached address cannot remove the change-path stall, because the new directory string has no cached answer by construction.Fix: resolve asynchronously and register when the answer arrives. Reusing the last resolved address (the patched build above) removes the periodic stall but not the change-path one.
🤖 This message was written by AI and reviewed by @mcfnord.