http://www.perlmonks.org?node_id=956853


in reply to question on exitcode provided by status() method of Win32::Job

It's impossible to say exactly what the problem was without seeing the spawned script. I also use Win32::Job without problems. I think the lack of updates for Win32::Job actually means that it is stable, not that it's buggy.

I notice that your child script is also written in perl. perl has some unintuitive exit code behavior on Windows, so I would consider the possibility that your child process is not exiting with the exit code you think it is.

I've just been bitten myself by exit code issues on Win32, here are my discoveries which might be useful to some readers.

Various problems all come from this conceptual difference between exit codes on Unix and Windows:

Perl doesn't seem to handle large exit codes correctly on Windows. This code comes from win32/win32.c in v5.14.1:

if (status < 0) { if (ckWARN(WARN_EXEC)) Perl_warner(aTHX_ packWARN(WARN_EXEC), "Can't spawn \"%s\": %s", argv[0], strerror(errno)); status = 255 * 256; } else status *= 256; PL_statusvalue = status;

status is a signed int, casted from the unsigned int exit code of the process. PL_statusvalue is the value which eventually ends up in $?.

The first problem is that, if the exit code was large enough that it wrapped around to a negative value while casting from unsigned to signed, perl will output a bogus warning like:

Can't spawn "some command": No error

... and force the status to 255*256 (i.e. 255 << 8, an exit code of 255).

The second problem is that status *= 256 (i.e. status << 8) overflows if status is too large ... irreversibly destroying the value.

As well as these problems with system(), exit() silently takes only the lower 16 bits of whatever integer you pass it.

The truncation/overflow of exit values is not at all a theoretical problem, as Windows uses large exit codes when processes exit abnormally. For example, let's say you used Win32::Job or some other native Win32 API to run an external process, and it dereferences an invalid pointer. The crashing process will exit with an exit code of 3221225477 / 0xC0000005 / STATUS_ACCESS_VIOLATION - that's how crashes work on Windows. Since you used a native Win32 API, you will get the real 32-bit exit code. Then let's say you naively try to propagate that exit code upwards to the calling process. You'll do an exit($status), where $status=3221225477. But Perl truncates the exit code to a confusingly simple value of 5 :)

For what it's worth, this is the simplest way I found so far to get the real exit code from a system() on Windows: (note, you don't need to do anything like this with Win32::Job, it already does the right thing)

sub win32_system { my (@command) = @_; my ($pid, $child_process, $exitcode); # this magic syntax makes system() async and return the # pid of the created process, see "perldoc perlport" $pid = system( 1, @command ); # Get the Win32 native handle to the process we just created Win32::Process::Open( $child_process, $pid, 0 ) || die; # Win32::Process equivalent of waitpid() $child_process->Wait( INFINITE ); $child_process->GetExitCode( $exitcode ); # Returns the real 32-bit exit code, no overflow return $exitcode; }

And the simplest way I found to exit with an exit code larger than 16 bits:

sub win32_exit { my ($exitcode) = @_; my $this_process; # get Win32 native handle to the current process Win32::Process::Open( $this_process, $$, 0 ) || die; # "kill" on Windows simply means # "force the process to exit with that exitcode" $this_process->Kill( $exitcode ); # we killed ourselves, should never get here die "Thou shalt never see this!"; }