Currently it is rather difficult to cleanly terminate a Perl program
using fork() emulation on Windows:
The Perl process will only terminate once the main thread *and* all
forked children have terminated.
So if the child process might be waiting in a blocked system call, we
may end up with a deadlock.
The standard "solution" is to use kill(9, $child), as this is the
only mechanism that will terminate a blocked thread.
However, using kill(9, $pid) may destabilize the process itself if the
child process is not blocked, but actively doing something, like
allocating memory from the shared heap.
I've been wondering if we shouldn't allow one of the other signals to
mark the child as "killed", even though the signal might not be delivered
if the thread is blocked. That way the main thread could exit without
having to wait for these children, and ExitProcess() will eventually
terminate all other threads anyways.
The question then becomes: Which signal should get this special treatment?
In some ways it would be most convenient to use SIGTERM, as that means
the same code would work on Unix and Windows (which is the main point
behind the fork() emulation). However, this would mean that a child
process would no longer be guaranteed to be able to catch SIGTERM and
do its own cleanup; it could get reaped at any time after catching the
signal.
Given that currently a pseudo-forked process is not guaranteed to receive
a SIGTERM signal at all, if it is blocked on a system call, this seems
like a sensible compromise to me.
Or would it be better to use a different signal, e.g. the Windows specific
SIGBREAK?
Unless I see any better suggestion (or reasons why doing this is a bad
idea), I'll implement this for SIGTERM in a day or two.
For more background, please check out these bug reports for Test::TCP:
https://rt.cpan.org/Ticket/Display.html?id=66016
https://rt.cpan.org/Ticket/Display.html?id=66437
My recent change to always surrender the remainder of the timeslice after
calling TerminateThread() on a child process seems to have reduced the
frequency of receiving the wrong exit code, but unfortunately the race
conditions inherent in terminating a thread in an unknown state can still
be observed.
Cheers,
-Jan
using fork() emulation on Windows:
The Perl process will only terminate once the main thread *and* all
forked children have terminated.
So if the child process might be waiting in a blocked system call, we
may end up with a deadlock.
The standard "solution" is to use kill(9, $child), as this is the
only mechanism that will terminate a blocked thread.
However, using kill(9, $pid) may destabilize the process itself if the
child process is not blocked, but actively doing something, like
allocating memory from the shared heap.
I've been wondering if we shouldn't allow one of the other signals to
mark the child as "killed", even though the signal might not be delivered
if the thread is blocked. That way the main thread could exit without
having to wait for these children, and ExitProcess() will eventually
terminate all other threads anyways.
The question then becomes: Which signal should get this special treatment?
In some ways it would be most convenient to use SIGTERM, as that means
the same code would work on Unix and Windows (which is the main point
behind the fork() emulation). However, this would mean that a child
process would no longer be guaranteed to be able to catch SIGTERM and
do its own cleanup; it could get reaped at any time after catching the
signal.
Given that currently a pseudo-forked process is not guaranteed to receive
a SIGTERM signal at all, if it is blocked on a system call, this seems
like a sensible compromise to me.
Or would it be better to use a different signal, e.g. the Windows specific
SIGBREAK?
Unless I see any better suggestion (or reasons why doing this is a bad
idea), I'll implement this for SIGTERM in a day or two.
For more background, please check out these bug reports for Test::TCP:
https://rt.cpan.org/Ticket/Display.html?id=66016
https://rt.cpan.org/Ticket/Display.html?id=66437
My recent change to always surrender the remainder of the timeslice after
calling TerminateThread() on a child process seems to have reduced the
frequency of receiving the wrong exit code, but unfortunately the race
conditions inherent in terminating a thread in an unknown state can still
be observed.
Cheers,
-Jan