Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use signal blocking in Ruby

Tags:

ruby

signals

How does one block certain signals in Ruby? (I.e. sigprocmask, as described here: http://www.chemie.fu-berlin.de/chemnet/use/info/libc/libc_21.html#SEC371)

Here is my example code

pid = fork do
  trap('INT') do
    puts "Graceful shutdown..."
    puts "goodbye"
    exit 0
  end

  loop do
    this_could_be_interrupted
    something_that_must_not_be_interrupted
    this_could_be_interrupted_too
  end
end

sleep 5
Process.kill('INT', pid)

Can I wrap something_that_must_not_be_interrupted in a ruby block or something to ensure that SIGINT won't be process while that method is running?

I guess the ideal would be if I could do something like this:

Process.block_signal('INT') do
  something_that_must_not_be_interrupted
end

UPDATE: I'm currently doing something like this:

trap('INT') do
  @interrupted = true
end

loop do
  exit 0 if @interrupted
  something_that_must_not_be_interrupted
end
like image 478
phylae Avatar asked Jun 15 '11 06:06

phylae


2 Answers

I think you're looking for the non-block form of trap:

Signal.trap( signal, command ) → obj
[...]
If the command is the string "IGNORE" or "SIG_IGN", the signal will be ignored. If the command is "DEFAULT" or "SIG_DFL", the Ruby‘s default handler will be invoked.

So you should be able to say this:

trap('INT', 'IGNORE')
something_that_must_not_be_interrupted
trap('INT', 'DEFAULT')

UPDATE: From the comments, it looks like you only want to temporarily ignore the signal. The easiest way to do that with what you already have is to add a flag that your signal handler can see, then remember the signal when it comes in and we're currently ignoring the signal, and when we're not ignoring things anymore you just empty the signal queue by sending them to yourself. If wrap this logic up in a class, you'll have something fairly friendly:

#
# Threading and race condition issues are left as an exercise,
# this is just meant as an illustration. Handling multiple signals
# at once is also left as an exercise.
#
class SignalHandler
  def initialize(signal)
    @interuptable = true
    @enqueued     = [ ]
    trap(signal) do
      if(@interuptable)
        puts "Graceful shutdown..."
        puts "goodbye"
        exit 0
      else
        @enqueued.push(signal)
      end
    end
  end

  # If this is called with a block then the block will be run with
  # the signal temporarily ignored. Without the block, we'll just set
  # the flag and the caller can call `allow_interuptions` themselves.
  def dont_interupt
    @interuptable = false
    @enqueued     = [ ]
    if(block_given?)
      yield
      allow_interuptions
    end
  end

  def allow_interuptions
    @interuptable = true
    # Send the temporarily ignored signals to ourself,
    # see http://ruby-doc.org/core/Process.html#method-c-kill
    @enqueued.each { |signal| Process.kill(signal, 0) }
  end
end

The real functional code was the easiest way to explain the technique (and I had to write it anyway to make sure the technique would work) so there you go. And thanks for the review of signal handling in Ruby :) Then you could so things like this:

sigint = SignalHandler.new('INT')
loop do
  this_could_be_interrupted
  sigint.dont_interupt { something_that_must_not_be_interrupted }
  this_could_be_interrupted_too
end
like image 122
mu is too short Avatar answered Sep 20 '22 12:09

mu is too short


Ruby returns the last handler associated with the signal, so you can use:

def bypass_signal(name)
  old_handler = trap("INT", 'IGNORE')
  yield
  trap("INT", old_handler)
end

bypass_signal "INT" { method_which_shouldnt_be_interrupted }
like image 45
Lori Avatar answered Sep 17 '22 12:09

Lori