Perl code which I'm trying to adapt to running on Windows, among other things, needs to start a separate process and to execute some multi-thread flow (the process might either keep running or exit at some point while the multi-thread flow is being executed).
Essentially, the logic is like this:
use Win32::Process;
use threads;
use strict;
my $proc;
Win32::Process::Create($proc,'dir','',0,NORMAL_PRIORITY_CLASS(),".");
my $t1= threads->create(\&thread_proc);
my $t2= threads->create(\&thread_proc);
sleep 2;
print "Waiting for thread ".$t1->tid." to exit\n";
$t1->join();
print "Waiting for thread ".$t2->tid." to exit\n";
$t2->join();
print "All done.\n";
# Cleanup would be here
sub thread_proc {
foreach (1..5) {
print "Thread ".threads->tid." waiting $_\n";
sleep 1;
}
return 0;
}
When execution reaches the 2nd join, Perl aborts (debugger claims that heap is corrupted).
If I comment out Win32::Process::Create above, it works.
Replacing Win32::Process with Win32::Job doesn't help, although it additionally produces the message
Free to wrong pool 4a47c0 not 328400 at ...
Wrapping up the whole thread logic into a BEGIN {} block seems to make the crash go away, but it's not a desirable workaround, since in the real code the thread-related logic is more complicated and can't be easily localized.
In Win32::Process or Win32::Job documentation (or anywhere else) I couldn't find any information about these modules being thread-unsafe.
So, the question is, whether the code is wrong or it's the generic problem with the modules, and in the latter case, is there any analogue which is known to be reasonably crash-safe?
Strawberry perl 5, version 24, subversion 1 (v5.24.1) built for MSWin32-x64-multi-thread
It is not clear what you are trying to do. Do you intend this process you spawn to outlast your Perl program? Do you intend to communicate with the process you spawn?
In most instances, just using plain fork (which is emulated on Windows, but things mostly work) is going to be better unless you want to interact with $proc in Windows specific ways. Also, keep in mind that normally dir is a cmd.exe builtin so Win32::Process will not be able to run in it unless you have a dir.exe or dir.com or dir.bat or similar on your %PATH%.
I can replicate a crash with the following program:
#!/usr/bin/env perl
$| = 1;
use v5.24; # why not?
use strict;
use warnings;
use threads;
use feature 'signatures';
no warnings 'experimental::signatures';
use autouse Carp => qw( croak );
use Win32::GuiTest qw( FindWindowLike SendKeys SetForegroundWindow );
use Win32::Process;
use Win32;
run();
sub run {
Win32::Process::Create(
my $proc,
'C:\WINDOWS\system32\notepad.exe',
'notepad.exe',
0,
+NORMAL_PRIORITY_CLASS,
'.',
) or croak Win32::FormatMessage(Win32::GetLastError());
my @thr = map threads->create(\&task), 1 .. 2;
threads->yield;
my @windows;
do {
@windows = FindWindowLike(0, 'Notepad');
} until (@windows);
wait_for_thread($_, $windows[0]) for @thr;
say 'All done.';
SetForegroundWindow($windows[0]);
SendKeys('%fxn');
$proc->Wait(+INFINITE);
return;
}
sub task {
my $tid = threads->tid;
say "Thread $tid waiting ($_)" for 1 .. 5;
threads->yield;
return;
}
sub wait_for_thread($thr, $window) {
my $msg = sprintf "Waiting for thread %d\n", $thr->tid;
print $msg;
SetForegroundWindow($window);
SendKeys($msg);
$thr->join;
return;
}
I get this in the debugger:

It is likely that an interaction between threads and Win32::Process is exposing a bug*.
On the other hand, the following version seems to work fine:
#!/usr/bin/env perl
$| = 1;
use v5.24; # why not?
use strict;
use warnings;
use threads;
use feature 'signatures';
no warnings 'experimental::signatures';
use autouse Carp => qw( croak );
use Win32::GuiTest qw( FindWindowLike SendKeys SetForegroundWindow );
run();
sub run {
defined(my $cpid = fork)
or croak "Failed to fork: $!";
if (!$cpid) {
system 'notepad';
exit;
}
my @thr = map threads->create(\&task), 1 .. 2;
threads->yield;
my @windows;
do {
@windows = FindWindowLike(0, 'Notepad');
} until (@windows);
wait_for_thread($_, $windows[0]) for @thr;
say 'All done.';
SetForegroundWindow($windows[0]);
SendKeys('%fxn');
1 while waitpid(-1, 0) > 0;
return;
}
sub task {
my $tid = threads->tid;
say "Thread $tid waiting ($_)" for 1 .. 5;
threads->yield;
return;
}
sub wait_for_thread($thr, $window) {
my $msg = sprintf "Waiting for thread %d\n", $thr->tid;
print $msg;
SetForegroundWindow($window);
SendKeys($msg);
$thr->join;
return;
}
As I mentioned, unless there is a specific reason to use Win32::Process, you are probably better off using fork.
* Feel free to check with P5P.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With