-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Description
Summary
On Windows, ProcessRunner::stop() calls Process::requestTermination(pid) which creates a temporary NamedEvent, signals it via SetEvent(), and immediately destroys it. If the child process has not yet created its own NamedEvent handle (the static ServerApplication::_terminate member, initialized during DLL static initialization), the kernel event object is destroyed when the parent's handle closes (refcount drops to zero). When the child later creates its NamedEvent with the same name, it gets a new, unsignaled kernel event object, and the termination signal is lost. The child blocks forever in waitForTerminationRequest() until ProcessRunner times out and force-kills it.
Reproduction
- Use
ProcessRunnerto launch aServerApplication-based process without a PID file (no--pidfileargument) ProcessRunner::start()returns as soon asCreateProcesscompletes and_pidis set -- before the child finishes loading DLLs and static initialization- Call
ProcessRunner::stop()shortly afterstart()returns requestTermination(pid)fires before the child's Util DLL createsServerApplication::_terminate- The child reaches
waitForTerminationRequest()but never receives the signal ProcessRunner::stop()pollsisRunning()for the full timeout (default 10s, can be up to 60s), then force-kills
The race is timing-dependent. With a PID file, start() waits for the child to write the file (ensuring full initialization), so the child's event handle exists before stop() can be called.
Root cause
In Foundation/src/Process_WIN32U.cpp:
void ProcessImpl::requestTerminationImpl(PIDImpl pid)
{
NamedEvent ev(terminationEventName(pid)); // CreateEventW
ev.set(); // SetEvent
} // ~NamedEvent calls CloseHandle -- kernel object destroyed if child hasn't opened itThe NamedEvent is a local variable. Its destructor closes the handle immediately after signaling. If the child's static _terminate member hasn't been constructed yet (DLL init hasn't run), no other handle to this kernel event exists, so the kernel destroys the object. The child later creates a fresh, unsignaled event with the same name.
Fix
In ProcessRunner::stop(), create the NamedEvent directly and keep it alive for the duration of the polling loop. This ensures the kernel event object persists until the child creates its own handle and receives the already-signaled event:
#if defined(POCO_OS_FAMILY_WINDOWS)
// Keep the NamedEvent alive so the kernel object persists until the child
// opens its own handle and receives the signal.
NamedEvent terminateEvent(Process::terminationEventName(pid));
terminateEvent.set();
#else
Process::requestTermination(pid);
#endif
while (Process::isRunning(pid))
{
// ... polling loop (unchanged) ...
}
// terminateEvent destroyed here, after child has exitedThis works because:
- Auto-reset events stay signaled if no thread is waiting when
SetEventis called - When the child's
CreateEventWopens a handle to the existing (already-signaled) kernel object, the child'sWaitForSingleObjectreturns immediately - On Unix,
requestTermination()sendsSIGINT-- a direct kernel operation with no equivalent race
Affected files
platform/Foundation/src/ProcessRunner.cpp-stop()methodplatform/Foundation/src/ProcessRunner.cpp- add#include "Poco/NamedEvent.h"(Windows only)
Note
This issue does not affect Unix/Linux. On Unix, Process::requestTermination() sends SIGINT via kill(), which is a direct kernel operation delivered to the process regardless of initialization state.