Problem
On Windows, pressing Ctrl-C (or clicking the console window's close button) does not give wfweb a chance to run its destructors. As a result:
~icomUdpBase() does not run, so the 0x05 token-removal packet is never sent to the radio.
- The rig's LAN slot stays occupied from its point of view until an idle timeout elapses, blocking other apps (RS-BA1, wfview, another wfweb instance) from connecting.
- Serial port handles, audio handles, and the settings
.ini flush may also be skipped.
This behavior is Windows-specific. On Linux/macOS, the existing POSIX signal(SIGINT/SIGTERM, cleanup) handler fires, QCoreApplication::quit() returns from a.exec(), and the normal Qt teardown runs — LAN radios get the 0x05 disconnect cleanly.
Root cause
Our current Windows handler in src/main.cpp:
#ifdef Q_OS_WIN
bool __stdcall cleanup(DWORD sig)
#endif
{
case SIGINT:
case SIGTERM:
qInfo() << \"terminate signal caught\";
if (kb != Q_NULLPTR) kb->terminate();
if (w != Q_NULLPTR) w->deleteLater();
QCoreApplication::quit();
break;
...
}
SetConsoleCtrlHandler((PHANDLER_ROUTINE)cleanup, TRUE);
Three Windows-specific problems:
-
Handler runs on its own OS thread. Windows spawns a fresh thread to run the SetConsoleCtrlHandler callback; it is not the main thread. QCoreApplication::quit() does post QEvent::Quit to the main thread correctly, but the handler itself does not wait for the main thread to process it.
-
Windows has a short deadline after the handler returns. Once the handler returns TRUE, Windows may proceed to terminate the process (via an ExitProcess-style path) without waiting for our main thread to finish cleanup. The practical deadlines are roughly:
CTRL_C_EVENT / CTRL_BREAK_EVENT: ~5 s
CTRL_CLOSE_EVENT (console X button): ~5 s
CTRL_LOGOFF_EVENT / CTRL_SHUTDOWN_EVENT: ~2 s
If the main thread hasn't returned from a.exec() and torn down servermain by then, Qt destructors never run.
-
Using switch(sig) with SIGINT / SIGTERM labels is wrong on Windows. The Windows handler receives CTRL_C_EVENT, CTRL_BREAK_EVENT, CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT, CTRL_SHUTDOWN_EVENT. These are different numeric constants from SIGINT/SIGTERM. The current case SIGINT: branch is reached only coincidentally (or not at all) — the X-button close in particular is missed entirely.
Proposed solution
Rewrite the Windows branch of cleanup() to:
- Dispatch on the correct
CTRL_*_EVENT values.
- Post
QCoreApplication::quit() to the main thread from the handler.
- Block the handler thread on an atomic flag (or a
HANDLE event) set by main() immediately after a.exec() returns, so Windows does not terminate the process before Qt teardown finishes. Use a bounded wait (~4 s for C/BREAK/CLOSE, ~1.5 s for LOGOFF/SHUTDOWN) so we never exceed the platform deadline.
Sketch (src/main.cpp, Windows-only):
#ifdef Q_OS_WIN
static std::atomic<bool> g_mainExited{false};
bool __stdcall cleanup(DWORD sig)
{
int budgetMs = 0;
switch (sig) {
case CTRL_C_EVENT:
case CTRL_BREAK_EVENT:
case CTRL_CLOSE_EVENT: budgetMs = 4000; break;
case CTRL_LOGOFF_EVENT:
case CTRL_SHUTDOWN_EVENT: budgetMs = 1500; break;
default: return FALSE;
}
qInfo() << \"terminate signal caught (Windows)\" << sig;
if (kb != Q_NULLPTR) kb->terminate();
if (w != Q_NULLPTR) QMetaObject::invokeMethod(w, \"deleteLater\", Qt::QueuedConnection);
QMetaObject::invokeMethod(QCoreApplication::instance(), \"quit\", Qt::QueuedConnection);
// Block so Windows doesn't kill us before ~servermain() finishes
// (this is what lets the 0x05 LAN disconnect packet actually go out).
const int step = 50;
for (int waited = 0; waited < budgetMs && !g_mainExited.load(); waited += step) {
Sleep(step);
}
return TRUE;
}
#endif
And in main() after a.exec():
int rc = a.exec();
#ifdef Q_OS_WIN
g_mainExited.store(true);
#endif
return rc;
The POSIX branch is untouched — Linux/macOS continue to use signal(SIGINT/SIGTERM, cleanup) as today.
Acceptance
- On Windows, pressing Ctrl-C while wfweb is connected to a LAN Icom radio results in the 0x05 token-removal packet being sent (visible in wfweb log as 'Sending token removal packet', and the radio's LAN slot is immediately available to another client).
- Closing the console window (X button) triggers the same clean teardown path.
- Linux/macOS behavior is unchanged.
Related
Problem
On Windows, pressing Ctrl-C (or clicking the console window's close button) does not give wfweb a chance to run its destructors. As a result:
~icomUdpBase()does not run, so the 0x05 token-removal packet is never sent to the radio..iniflush may also be skipped.This behavior is Windows-specific. On Linux/macOS, the existing POSIX
signal(SIGINT/SIGTERM, cleanup)handler fires,QCoreApplication::quit()returns froma.exec(), and the normal Qt teardown runs — LAN radios get the 0x05 disconnect cleanly.Root cause
Our current Windows handler in
src/main.cpp:Three Windows-specific problems:
Handler runs on its own OS thread. Windows spawns a fresh thread to run the
SetConsoleCtrlHandlercallback; it is not the main thread.QCoreApplication::quit()does postQEvent::Quitto the main thread correctly, but the handler itself does not wait for the main thread to process it.Windows has a short deadline after the handler returns. Once the handler returns TRUE, Windows may proceed to terminate the process (via an
ExitProcess-style path) without waiting for our main thread to finish cleanup. The practical deadlines are roughly:CTRL_C_EVENT/CTRL_BREAK_EVENT: ~5 sCTRL_CLOSE_EVENT(console X button): ~5 sCTRL_LOGOFF_EVENT/CTRL_SHUTDOWN_EVENT: ~2 sIf the main thread hasn't returned from
a.exec()and torn downservermainby then, Qt destructors never run.Using
switch(sig)withSIGINT/SIGTERMlabels is wrong on Windows. The Windows handler receivesCTRL_C_EVENT,CTRL_BREAK_EVENT,CTRL_CLOSE_EVENT,CTRL_LOGOFF_EVENT,CTRL_SHUTDOWN_EVENT. These are different numeric constants fromSIGINT/SIGTERM. The currentcase SIGINT:branch is reached only coincidentally (or not at all) — the X-button close in particular is missed entirely.Proposed solution
Rewrite the Windows branch of
cleanup()to:CTRL_*_EVENTvalues.QCoreApplication::quit()to the main thread from the handler.HANDLEevent) set bymain()immediately aftera.exec()returns, so Windows does not terminate the process before Qt teardown finishes. Use a bounded wait (~4 s for C/BREAK/CLOSE, ~1.5 s for LOGOFF/SHUTDOWN) so we never exceed the platform deadline.Sketch (
src/main.cpp, Windows-only):And in
main()aftera.exec():The POSIX branch is untouched — Linux/macOS continue to use
signal(SIGINT/SIGTERM, cleanup)as today.Acceptance
Related
closeComm()in~servermain(), UDP thread delete afterwait()) already landed in Missing UI element to disconnect LAN after radio Power Down. #17 / branchlan-disconnect-reconnect-ui. This issue only needs to fix the handler entry path so that teardown actually runs on Windows.