Ruby bug with fork and signals (SIGTERM)

Hi,

I writing a C-Extension for ruby using "rb_fork(0,0,0,Qnil)" to create a
new process.

Behaviour:

1) before fork a SIGTERM send to the parent terminate the parent
2) after fork a SIGTERM is ignored

Analyze:

SIGTERM is initialized in "Init_signal" using

#ifdef SIGTERM
    install_sighandler(SIGTERM, sighandler);
#endif

but the fork code does in "rb_fork_err"

...
    for (; before_fork(), (pid = fork()) < 0; prefork()) {
        after_fork();
...
    after_fork();
...

The problem is the "after_fork" is called for parent and child:

#define after_fork() (GET_THREAD()->thrown_errinfo = 0, after_exec())

#define after_exec() \
  (rb_thread_reset_timer_thread(), rb_thread_start_timer_thread(),
forked_child = 0, rb_disable_interrupt())

void
rb_disable_interrupt(void)
{
#if USE_TRAP_MASK
    sigset_t mask;
    sigfillset(&mask);
    sigdelset(&mask, SIGVTALRM);
    sigdelset(&mask, SIGSEGV);
    pthread_sigmask(SIG_SETMASK, &mask, NULL);
#endif
}

and all signals are blocked except SIGVTALRM and SIGSEGV

after adding

sigdelset(&mask, SIGTERM);

to rb_disable_interrupt the code is working but a real solution should
save the initial mask and later on enable the mask again ...

mfg

  Andreas Otto

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Hi,

I writing a C-Extension for ruby using "rb_fork(0,0,0,Qnil)" to create a
new process.

Behaviour:

1) before fork a SIGTERM send to the parent terminate the parent
2) after fork a SIGTERM is ignored

Only in the parent?

Analyze:

SIGTERM is initialized in "Init_signal" using

#ifdef SIGTERM
install_sighandler(SIGTERM, sighandler);
#endif

but the fork code does in "rb_fork_err"

...
for (; before_fork(), (pid = fork()) < 0; prefork()) {
after_fork();
...
after_fork();
...

The problem is the "after_fork" is called for parent and child:

#define after_fork() (GET_THREAD()->thrown_errinfo = 0, after_exec())

#define after_exec() \
(rb_thread_reset_timer_thread(), rb_thread_start_timer_thread(),
forked_child = 0, rb_disable_interrupt())

void
rb_disable_interrupt(void)
{
#if USE_TRAP_MASK
sigset_t mask;
sigfillset(&mask);
sigdelset(&mask, SIGVTALRM);
sigdelset(&mask, SIGSEGV);
pthread_sigmask(SIG_SETMASK, &mask, NULL);
#endif
}

and all signals are blocked except SIGVTALRM and SIGSEGV

after adding

sigdelset(&mask, SIGTERM);

to rb_disable_interrupt the code is working but a real solution should
save the initial mask and later on enable the mask again ...

I doubt it is a good idea to use regular signal handling code to do
this. If at all you should probably use Ruby's methods. Note that
you get the old signal handler back when trapping:

irb(main):010:0> Process.kill "INT", $$
=> 1
irb(main):011:0> Got signal 2

irb(main):012:0* Signal.trap("INT", &old)
=> #<Proc:0x1020e154@(irb):9>
irb(main):013:0> Process.kill "INT", $$
=> 1
irb(main):014:0> ^C
irb(main):014:0> Process.kill "INT", $$
=> 1
irb(main):015:0> ^C

I don't know your specific requirements but in Ruby you would probably do

fork do
  # whatever client code
end

Signal.trap 'TERM' do
  $stderr.puts "Ignoring SIGTERM"
end

Does that help?

Kind regards

robert

···

On Tue, Oct 12, 2010 at 9:10 AM, Andreas Otto <aotto1968@users.sourceforge.net> wrote:

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Hi,

I writing a C-Extension for ruby using "rb_fork(0,0,0,Qnil)" to create a
new process.

Behaviour:

1) before fork a SIGTERM send to the parent terminate the parent
2) after fork a SIGTERM is ignored

Only in the parent?

I just test the parent.

Analyze:

SIGTERM is initialized in "Init_signal" using

#ifdef SIGTERM
   install_sighandler(SIGTERM, sighandler);
#endif

but the fork code does in "rb_fork_err"

...
   for (; before_fork(), (pid = fork()) < 0; prefork()) {
       after_fork();
...
   after_fork();
...

The problem is the "after_fork" is called for parent and child:

#define after_fork() (GET_THREAD()->thrown_errinfo = 0, after_exec())

#define after_exec() \
(rb_thread_reset_timer_thread(), rb_thread_start_timer_thread(),
forked_child = 0, rb_disable_interrupt())

void
rb_disable_interrupt(void)
{
#if USE_TRAP_MASK
   sigset_t mask;
   sigfillset(&mask);
   sigdelset(&mask, SIGVTALRM);
   sigdelset(&mask, SIGSEGV);
   pthread_sigmask(SIG_SETMASK, &mask, NULL);
#endif
}

and all signals are blocked except SIGVTALRM and SIGSEGV

after adding

sigdelset(&mask, SIGTERM);

to rb_disable_interrupt the code is working but a real solution should
save the initial mask and later on enable the mask again ...

I doubt it is a good idea to use regular signal handling code to do
this. If at all you should probably use Ruby's methods. Note that
you get the old signal handler back when trapping:

irb(main):010:0> Process.kill "INT", $$
=> 1
irb(main):011:0> Got signal 2

irb(main):012:0* Signal.trap("INT", &old)
=> #<Proc:0x1020e154@(irb):9>
irb(main):013:0> Process.kill "INT", $$
=> 1
irb(main):014:0> ^C
irb(main):014:0> Process.kill "INT", $$
=> 1
irb(main):015:0> ^C

I don't know your specific requirements but in Ruby you would probably do

fork do
  # whatever client code
end

Signal.trap 'TERM' do
  $stderr.puts "Ignoring SIGTERM"
end

Does that help?

My problem is just the opposite

I want that SIGTERM is *not* ignored ...
the original parent does end on SIGTERM
the parent *after* fork does not stop on SIGTERM

mfg, Andreas Otto

···

Am 12.10.2010 09:32, schrieb Robert Klemme:

On Tue, Oct 12, 2010 at 9:10 AM, Andreas Otto > <aotto1968@users.sourceforge.net> wrote: