1

I have a daemon running on Ubuntu written in Perl, it is now single threaded. When it starts up it does the usual Proc::Daemon stuff and then goes into a while loop bounded by a boolean. The daemon starts up fine with "service daemon start". But when I want to kill it with "service daemon stop" it doesn't stop.

Stopping is supposed to happen by flipping the boolean using a signal handler:

$SIG{INT} = $SIG{TERM} = $SIG{HUP} = \&signalHandler;

sub signalHandler {
    $continue = 0;
}

Unfortunately the main loop also has a sleep in it. So the daemon doesn't end until the sleep is over. I would like the daemon to end immediately. But I don't want to set a very low sleep time. In fact I want to be able to set a sleep time of several hours if that is what it determines it should do.

What is the best way to do this?

I was thinking about calling some sort of blocking function inside the main loop instead of a sleep. This blocking function would not return until some thread has died. Then in the signal handler I would simply kill that sleeping thread. Is this a good approach? And how would I do that, I've never done multi-threading in Perl before.

thanks for any help

jurgen
  • 325
  • 1
  • 11

3 Answers3

3

Here's a workaround for this situation:

1. Create a named pipe when your software starts running, with, e.g. mkfifo

2. Replace the sleep call with a select system call (as an alternative you can use the perl package IO::Select instead of the select system call).

Set up the select to monitor when the pipe is ready for input.

select allows the specification of a timeout, just like sleep.

The select call returns when either the timeout elapses OR the file condition is met.

3. If the timeout occurs, run your loop's body code. If data appears on the pipe, terminate.

4. To terminate, send data to the named pipe.

Paul
  • 26,170
  • 12
  • 85
  • 119
  • I wouldn't call that a workaround, it's a better solution that sleeping in any case, if you have any IO at all to monitor. – harmic Apr 17 '14 at 13:29
1

You can move any shutdown-related cleanup to its own subroutine, if you haven't already done so, and let signalHandler() call that cleanup sub and then simply exit. Then edit your start/stop script to send SIGALRM instead. SIGALRM will interupt the sleep. If your script is edited to catch this, everything will be stopped at once.

Further reading: sleep()

Jarmund
  • 3,003
  • 4
  • 22
  • 45
  • If I were to take this approach, I'd reverse the interaction of `$sleeptime` and `$sleepsegment` so that `$sleepsegment` is the duration of each segment rather than the number of segments. Since the OP is concerned about responsiveness, it would be better to "do as many 5-second `sleeps` as needed" rather than "divide the `sleep` into 5 segments", since the OP wants to be able to "_set a sleep time of several hours if that is what it determines it should do_". – Dave Sherohman Apr 17 '14 at 09:13
  • @DaveSherohman I was thinking that too, but chose this for easy readability and code compactness. – Jarmund Apr 17 '14 at 10:13
  • This is exactly what I don't want to do. I want a sleep that can last for hours and I want to script to stop immediately when told to. In other words I want to interrupt the sleep. – jurgen Apr 17 '14 at 11:08
  • 1
    @jurgen then go for the other approach i mentioned combined with `SIGALRM`, as that will interrupt the sleep(). Edited my answer accordingly. – Jarmund Apr 17 '14 at 11:16
  • I don't really understand what you mean with SIGALRM. You want me to send a signal to my program from within the program to interrupt the sleep its in? – jurgen Apr 17 '14 at 11:29
  • Right now I'm thinking this: use threads; while ( $continue ) { my $sleeper = threads->create('sleep'); $sleeper->join; } sub sleep { sleep( $any_value ); } sub signalHandler { $sleeper->detach(); }. I have not tested any of that – jurgen Apr 17 '14 at 11:30
  • 1
    Edit your start/stop script to send a SIGALRM, and edit this script to act on that (in addition to the ones you already have) – Jarmund Apr 17 '14 at 11:57
  • it has to be an upstart script and it doesn't give that kind of control. And I can't find any information on what upstart actually does. – jurgen Apr 20 '14 at 12:30
1

Unless you've done something odd to change it, the sleep() function should wake up early and return immediately when a signal is received. You can then change your loop to check the $continue flag after every sleep, to see if you received your shutdown signal.

LeoNerd
  • 8,344
  • 1
  • 29
  • 36
  • It wakes up on its own.. Wow do I feel stupid now. You wouldn't happen to know how to kill a program I started with a system() call wouldn't you? Maybe there is a simple thing I've overlooked there too. – jurgen Apr 17 '14 at 14:23
  • @jurgen - The same way: Send it a signal. `SIGINT` ("interrupt", equivalent to hitting Ctrl-C) would be the most obvious candidate, or `SIGKILL` if you need it to die _right now_ without getting a chance to do any cleanup (probably a bad idea). – Dave Sherohman Apr 17 '14 at 16:24
  • @jurgen `system` doesn't return until the program you started has already `exit`ed. So you can't stop a program you started from it, because it's already stopped. – LeoNerd Apr 19 '14 at 00:21
  • I thought about using kill() to kill the program but I did need the PID to do it, and system doesn't return it. After some googling I found some code that used a fork+exec trick to get the PID. Haven't tested it yet but it looks good. Basically you just fork() and return the child PID in an if, the else block performs an exec(). The caller can then use waitpid() to block until finished. When a signal comes in you can just kill the pid. I hope it will work, will test on monday. – jurgen Apr 20 '14 at 09:01