Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perlipc.pod: fix the "exec from signal handler" example #22136

Open
wants to merge 3 commits into
base: blead
Choose a base branch
from

Conversation

stepnem
Copy link

@stepnem stepnem commented Apr 11, 2024

I was scratching my head wondering why my signal handlers
calling exec[1] didn't work, and neither did the perlipc
example.

Fortunately a search turned up
https://www.perlmonks.org/?node_id=440900 which still cites
the old (and apparently still correct) documentation.

I don't know if something changed since 2011 or if the
pertinent changes in commit de7ba51 were wrong to begin
with.

For convenience I've also put the test script demonstrating
the issue (included in the commit message, with further
details) into a gist:

https://gist.github.com/stepnem/738d7d2e21bc4ab3d1d0a715c4a8bf86

[1] Note that the exec seems important; a simpler handler
(e.g., just printing or setting a variable) appears to work
fine without POSIX.

@Leont
Copy link
Contributor

Leont commented Apr 11, 2024

I don't know if something changed since 2011 or if the
pertinent changes in commit de7ba51 were wrong to begin
with.

Yeah I may have removed too much in that example.

This is a partial revert of de7ba51 ("Doc patch to
perlipc", 2011-09-11).

I tested both the pre-de7ba5179657 and current version of
the example program (section "Handling the SIGHUP Signal in
Daemons") on Arch Linux (perl v5.38.2 built for x86_64-linux-thread-multi)
and OpenBSD -current (perl v5.36.3 built for amd64-openbsd).
On both systems, the handler is called only once with the
current (broken) version of the example program.
With the pre-de7ba5179657 version the handler is called on
every delivery of the signal, as expected.

POSIX shell test script (perlipc-sighandler.sh):
================================================
-->8--
#!/bin/sh
check() {
  printf '\nTesting %s\n' "$*"
  "$@" &
  sleep 1
  kill -HUP $!
  sleep 1
  kill -HUP $!
  sleep 1
  kill $!
}

bad=./perlipc-sighandler-bad.perl
good=./perlipc-sighandler-good.perl

# from current perlipc.pod (perl5 commit e78caca v5.39.9-35-ge78caca8144e)
cat <<\EOF >"$bad"
#!/usr/bin/perl

use v5.36;

use POSIX ();
use FindBin ();
use File::Basename ();
use File::Spec::Functions qw(catfile);

$| = 1;

# make the daemon cross-platform, so exec always calls the script
# itself with the right path, no matter how the script was invoked.
my $script = File::Basename::basename($0);
my $SELF   = catfile( $FindBin::Bin, $script );

# POSIX unmasks the sigprocmask properly
$SIG{HUP} = sub {
  print "got SIGHUP\n";
  exec( $SELF, @argv ) || die "$0: couldn't restart: $!";
};

code();

sub code {
  print "PID: $$\n";
  print "ARGV: @argv\n";
  my $count = 0;
  while (1) {
    sleep 2;
    print ++$count, "\n";
  }
}
EOF

# from perlipc.pod before perl5 commit de7ba51 v5.15.2-343-gde7ba5179657
cat <<\EOF >"$good"
#!/usr/bin/perl

use v5.36;

use POSIX ();
use FindBin ();
use File::Basename ();
use File::Spec::Functions qw(catfile);

$| = 1;

# make the daemon cross-platform, so exec always calls the script
# itself with the right path, no matter how the script was invoked.
my $script = File::Basename::basename($0);
my $SELF   = catfile( $FindBin::Bin, $script );

# POSIX unmasks the sigprocmask properly
my $sigset = POSIX::SigSet->new();
my $action = POSIX::SigAction->new("sigHUP_handler",
                                   $sigset,
                                   &POSIX::SA_NODEFER);
POSIX::sigaction(&POSIX::SIGHUP, $action);

sub sigHUP_handler {
  print "got SIGHUP\n";
  exec( $SELF, @argv ) || die "$0: couldn't restart: $!";
}

code();

sub code {
  print "PID: $$\n";
  print "ARGV: @argv\n";
  my $count = 0;
  while (1) {
    sleep 2;
    print ++$count, "\n";
  }
}
EOF

chmod +x "$bad" "$good"

check "$bad"
check "$good"
-->8--

Example observed output:
========================
; ./perlipc-sighandler.sh

Testing ./perlipc-sighandler-bad.perl
PID: 19120
ARGV:
got SIGHUP
PID: 19120
ARGV:

Testing ./perlipc-sighandler-good.perl
PID: 19980
ARGV:
got SIGHUP
PID: 19980
ARGV:
got SIGHUP
PID: 19980
ARGV:

Cc: Leon Timmermans <fawaka@gmail.com>
Fixes: de7ba51 ("Doc patch to perlipc")
"POSIX unmasks the sigprocmask properly" seems rather opaque.
Instead, explain the SA_NODEFER motivation directly.
@stepnem
Copy link
Author

stepnem commented Apr 11, 2024 via email

@Leont
Copy link
Contributor

Leont commented Apr 11, 2024

Perhaps let's also improve the code comment a bit to explain
the SA_NODEFER usage.

Maybe, or maybe it should instead use sigprocmask in the sighandler to reset the mask right before the exec. That sounds easier to me.

Instead of using SA_NODEFER, which makes the handler
function itself interruptible by another SIGHUP, use
a plain $SIG{HUP} handler and unblock SIGHUP using
sigprocmask from the main program.
@stepnem
Copy link
Author

stepnem commented Apr 12, 2024 via email

@jkeenan
Copy link
Contributor

jkeenan commented May 4, 2024

@stepnem, we have certain metadata checks that are preventing your pull request from being tested by our continuous integration rig. Could you please do the following:

  1. Run perl Porting/updateAUTHORS.pl. This will add your name and email address to the correct location in our AUTHORS file.
  2. Apply the following patch to pod/perlipc.pod. This will have your p.r. conform to our line-length standards.
diff --git a/pod/perlipc.pod b/pod/perlipc.pod
index b14e8344b7..5169aed767 100644
--- a/pod/perlipc.pod
+++ b/pod/perlipc.pod
@@ -211,7 +211,8 @@ info to show that it works; it should be replaced with the real code.
 
   # Make sure SIGHUP isn't blocked (as it normally would be, due to the
   # handler function calling exec rather than returning)
-  POSIX::sigprocmask(&POSIX::SIG_UNBLOCK, POSIX::SigSet->new(&POSIX::SIGHUP));
+  POSIX::sigprocmask(&POSIX::SIG_UNBLOCK,
+    POSIX::SigSet->new(&POSIX::SIGHUP));
 
   code();

Please run make test_porting to make sure all metadata tests pass, then re-push the p.r. Thanks.

@Leont
Copy link
Contributor

Leont commented May 4, 2024

Yeah, although if the concern is avoiding the window for
another SIGHUP in the handler function before exec, wouldn't
it be better to unblock SIGHUP unconditionally in the main
program (outside the handler)?

I don't think that is a concern.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants