Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Signal handler not found

Tags:

perl

For some reason threads doesn't seem to be able to find the $SIG{'KILL') signal. I got the code from a comment here http://www.perlmonks.org/index.pl?node_id=557219 This is on Windows.

use strict;
use warnings;

use threads;

sub test_sub{
    $SIG{'KILL'} = sub { threads->exit(); };
    while(1){}  
}

my $new_thread = threads->create('test_sub');

$new_thread->kill('KILL')->detach();

returns

Signal KILL received in thread 1, but no signal handler set. at test1.pl line 13.
Perl exited with active threads:
        1 running and unjoined
        0 finished and unjoined
        0 running and detached

Edit: looks like I'm using threads version '1.43'. I don't know if that the problem but I'll try a newer version.

like image 869
TheAschr Avatar asked Apr 18 '26 19:04

TheAschr


2 Answers

This appears to be some sort of race condition, and it works when there is more going on.

use warnings;
use strict;
use feature 'say';

use threads;
use threads::shared;

my $thread_handler_on :shared = 0;

sub test_handler {
    say "\tIn thread.";
    $SIG{'KILL'} = sub {
        $thread_handler_on = 1;
        say "\tSet flag, exiting the thread in handler";
        threads->exit();
    };
    sleep 3;
    say "\tSet handler";
    sleep 1;
    say "\tHandler done napping";
}

my $new_thread = threads->create( 'test_handler' );

sleep 1;

say "Sending 'kill' to thread";
$new_thread->kill('KILL')->detach();

sleep 5;

say "Did handler run: $thread_handler_on";

The code in the question is right out of threads docs (along with detach). This prints

        In thread.
Sending 'kill' to thread
        Set flag, exiting the thread in handler
Did handler run: 1

Without those sleeps in the main thread I also get the reported behavior.

Note that these "signals" are not sent via kernel, per Threads signalling

The thread signalling capability provided by this module does not actually send signals via the OS. It emulates signals at the Perl-level such that signal handlers are called in the appropriate thread.

like image 125
zdim Avatar answered Apr 20 '26 18:04

zdim


There are not one but two race conditions that could lead to the behaviour you observe:

  • You're sending the KILL signal before the child has a chance to setup the signal handler.

    Below, I used synchronization to ensure the signal handler is created before the signal is sent.

  • You're exiting the program before the signal handler has a chance to call threads->exit.

    Why do people insist on using ->detach?

Fixed:

use strict;
use warnings;
use feature qw( say );

use threads;
use threads::shared;

sub test_handler {
    say sprintf "[%s %s] Starting thread", time, threads->tid;
    say sprintf "[%s %s] Doing stuff", time, threads->tid;
    sleep 4;
    say sprintf "[%s %s] Done doing stuff", time, threads->tid;
    say sprintf "[%s %s] Exiting thread", time, threads->tid;
}

sub create_thread(&) {
   my ($thread_func) = @_;

   # Reap any threads that might have exited.
   $_->join() for threads->list(threads::joinable);

   my $ready :shared = 0;
   my $thread = async {
      $SIG{KILL} = sub {
         say sprintf "[%s %s] Forcibly exiting thread", time, threads->tid;
         threads->exit();
      };

      # Signal creator that the thread is initialized.
      { lock $ready; $ready = 1; cond_signal($ready); }
      $thread_func->();
   };

   # Wait for the thread to finish initializing.    
   { lock $ready; while (!$ready) { cond_wait($ready); } }
   return $thread;
}

my $thread = create_thread(\&test_handler);

say sprintf "[%s %s] Sending KILL signal", time, threads->tid;
$thread->kill('KILL');

say sprintf "[%s %s] Doing stuff", time, threads->tid;
sleep(2);
say sprintf "[%s %s] Done doing stuff", time, threads->tid;

# Wait for threads to exit.
say sprintf "[%s %s] Waiting for threads to exit", time, threads->tid;
$_->join() for threads->list();
say sprintf "[%s %s] Exiting", time, threads->tid;

Output:

[1513212149 1] Starting thread
[1513212149 1] Doing stuff
[1513212149 0] Sending KILL signal
[1513212149 0] Doing stuff
[1513212151 0] Done doing stuff
[1513212151 0] Waiting for threads to exit
[1513212153 1] Forcibly exiting thread
[1513212153 0] Exiting

IMPORTANT Note that the signal didn't interrupt sleep. No actual POSIX signal is sent[1] (since you can only send those to a process), so OS calls can't be interrupted. Instead, Perl actively checks if a signal was sent between most Perl opcodes[2]. But that means that long-running opcodes like sleep, m// and XS functions calls can delay the signal handler indefinitely.


  1. That's fortunate for you, seeing as you're not using a POSIX system.
  2. Some time ago, it used to be between each opcode, but the number of checks was reduced. I think Perl now checks once per statement.
like image 33
ikegami Avatar answered Apr 20 '26 17:04

ikegami



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!