I have set up a multithreaded application using Perl Threads and am trying to detect a timeout within the thread (the thread is supposed to go through several system calls which could time out and I need to know when it stopped).
I've tried using alarm() and encountered the well documented issues of the Alarm signal being received by thread0 rather than by the thread which triggered the alarm. So I moved to using Alarm::Concurrent.
use threads ('yield',
'stack_size' => 64*4096,
'exit' => 'threads_only',
'stringify');
use Alarm::Concurrent qw(setalarm clearalarm);
threads->create(\&MyThreadFunc,1);
threads->create(\&MyThreadFunc,1);
threads->create(\&MyThreadFunc,5);
threads->create(\&MyThreadFunc,5);
sub MyThreadFunc {
my $result = "Never Ran";
my $timeToSleep = $_[0];
my $tid = threads->tid();
eval {
setalarm 3, sub { die "Timedout\n" };
sleep ($timeToSleep);
clearalarm;
$result = "TID $tid EXITED\n";
};
if ($@ eq "Timedout\n")
{
$result = "TID $tid TIMED OUT\n";
}
elsif ($@)
{
$result = "$@";
}
print "Thread returning $result";
return $result;
}
while (scalar threads::list(threads::running))
{
foreach my $thread (threads->list(threads::joinable)) { $thread->join(); }
}
What I expected:
I expected threads TID 1 and 2 to exit normally, and threads 3 & 4 to take the TimedOut Path (because the alarm would have been called and the eval() would have caught the die "Timedout\n". Instead they all took the EXITED path.
Thread returning TID 1 EXITED
Thread returning TID 2 EXITED
Thread returning TID 4 EXITED
Thread returning TID 3 EXITED
According to the threads documentation:
Signals are caught by the main thread (thread ID = 0) of a script. Therefore, setting up signal handlers in threads for purposes other than THREAD SIGNALLING as documented above will not accomplish what is intended.
This is especially true if trying to catch SIGALRM in a thread. To handle alarms in threads, set up a signal handler in the main thread, and then use THREAD SIGNALLING to relay the signal to the thread:
my $thr = threads->create(sub { threads->yield(); eval { $SIG{ALRM} = sub { die("Timeout\n"); }; alarm(10); ... # Do work here alarm(0); }; if ($@ =~ /Timeout/) { warn("Task in thread timed out\n"); } }; # Set signal handler to relay SIGALRM to thread $SIG{ALRM} = sub { $thr->kill('ALRM') }; ... # Main thread continues working
Here is an example of how it could be used:
use threads ('yield',
'stack_size' => 64*4096,
'exit' => 'threads_only',
'stringify');
use feature qw(say);
use strict;
use warnings;
use Alarm::Concurrent qw(setalarm clearalarm);
{
create_thread(sleep_time => 1, timeout => 3);
create_thread(sleep_time => 1, timeout => 3);
create_thread(sleep_time => 5, timeout => 3);
create_thread(sleep_time => 5, timeout => 3);
while (scalar threads::list(threads::running)) {
foreach my $thread (threads->list(threads::joinable)) { $thread->join(); }
}
}
sub create_thread {
my (%options) = @_;
my $thr = threads->create(\&MyThreadFunc, $options{sleep_time});
setalarm $options{timeout}, sub {
$thr->kill('SIGTERM');
};
}
sub MyThreadFunc {
my $result = "Never Ran";
my $timeToSleep = $_[0];
my $tid = threads->tid();
eval {
local $SIG{TERM} = sub { die "Timedout\n" };
sleep ($timeToSleep);
$result = "TID $tid EXITED\n";
};
if ($@ eq "Timedout\n") {
$result = "TID $tid TIMED OUT\n";
}
elsif ($@) {
$result = "$@";
}
print "Thread returning $result";
return $result;
}
Output:
Thread returning TID 1 EXITED
Thread returning TID 2 EXITED
Thread returning TID 3 TIMED OUT
Thread returning TID 4 TIMED OUT