Ensure that all FH are closed and the subprocess is reaped upon System::Command object destruction.#31
Ensure that all FH are closed and the subprocess is reaped upon System::Command object destruction.#31djerius wants to merge 1 commit intobook:masterfrom
Conversation
…t destruction.
IO objects are special, and sometimes are not destroyed when
references to them disappear.
Here's some example code:
{
my $cmd = System::Command->new( 'cat' );
$cmd->stdin->say( $_ ) for 0 ... 100;
};
When the block ends, $cmd will be destroyed. Sometimes the
order of destruction is
- System::Command
- IO::Handle
- System::Command::Reaper
and sometimes it is
- System::Command
- System::Command::Reaper
Experimentally, when the Reaper object is destroyed (regardless of
whether the IO::Handles are destroyed first) it has lost access to its
cached IO::Handle objects. They are cached as weak references, and
when the Command object is destroyed, it releases its references to
them, and the Reaper object loses its refereces.
If the second order of destruction happens, the handles still exist
(Perl seems to have an internal cache for them), but Reaper doesn't
see them, and thus can't close them. It will try to reap the child
process, but if the child is awaiting input, it will never exit,
cannot be reaped, and Reaper::_reap will hang forever.
(Note that explicitly closing the handles via $cmd->close
is not safe, as, if the program is interrupted before the close,
and the signal handler throws, e.g. $SIG{INT} = sub { die },
the $cmd->close does not get fired.
So, just make sure things are closed.
|
TBH the client could do this ( I think this handles all of the race conditions) but I think it's cleaner to do it in the |
|
Given the way you produced the check for the issue, I assume there's no easy way to test for this, except running a number of iterations and checking they all pass. |
|
Oh, and because the issue is a hanging process, it's probably not a nice test to write... |
I have written tests that timeout (I had some issues with IPC on CPAN testers running on Windows) but I can't remember much more than that. Will have to go prod my brain cells. |
Found it; no use here, it just timed out when trying to launch the child process. Could set an alarm, but apparently this wouldn't work for this case on Windows; https://metacpan.org/dist/Time-Out/view/lib/Time/Out.pod#CAVEATS |
IO objects are special, and sometimes are not destroyed when references to them disappear.
Here's some example code (for real code to run and exhibit the unwanted behavior, see below)
When the block ends,
$cmdwill be destroyed. Sometimes the order of destruction isSystem::CommandIO::HandleSystem::Command::Reaperand sometimes it is
System::CommandSystem::Command::ReaperExperimentally, when the
Reaperobject is destroyed (regardless of whether the IO::Handles are destroyed first) it has lost access to its cachedIO::Handleobjects. They are cached as weak references, and when theCommandobject is destroyed, it releases its references to them, and the Reaper object loses its references.If the second order of destruction happens, the handles still exist (Perl seems to have an internal cache for them), but
Reaperdoesn't see them, and thus can't close them. It will try to reap the child process, but if the child is awaiting input, it will never exit, cannot be reaped, andReaper::_reapwill hang forever.(Note that explicitly closing the handles via
$cmd->closeis not safe, as, if the program is interrupted before the close, and the signal handler throws, e.g.$SIG{INT} = sub { die }, the$cmd->closedoes not get fired.A bit further below is some real test code. Run it as, e.g.,
In the above example, the last instance hangs because the
stdinIO::Handlehasn't been destroyed.It may need a few iterations to trigger the bug.