Project

General

Profile

Actions

Bug #9447

closed

Bad interaction between Fibers and Trap handlers in Ruby > 2.0.0

Added by nilsonsfj (Nilson Santos Figueiredo Junior) about 10 years ago. Updated almost 5 years ago.

Status:
Closed
Assignee:
-
Target version:
-
ruby -v:
ruby 2.0.0p247
Backport:
[ruby-core:60061]

Description

Hi there,

I've been using and Alarm class I created, which gives Ruby code access to the alarm system call.
It used to work perfectly on 1.9.3 with no issues and more than 1 year of production usage.

Ruby 2.0.0 doesn't let me use it properly, apparently when there's an alarm timeout, the interpreter will think the code is in the trap context forever, so when I try to use Logger afterwards, it will fail (since it uses a Mutex internally).

Below is my Alarm implementation.

require 'dl'
require 'dl/import'

module Alarm
  module SystemCall
    extend DL::Importer

    # imports the POSIX alarm function 
    begin
      dlload 'libc.so.6'
    rescue
      begin
        dlload 'libc.so.5'
      rescue
        dlload 'libc.dylib'
      end
    end
    extern 'unsigned int alarm(unsigned int)'
  end

  def self.alarm(n)
    # creates new fiber which will the code block passed as argument
    fiber = Fiber.new { yield }
    begin
      # new signal handler for the alarm call
      handler = Signal.trap('ALRM') do
        # yields fiber, which makes the control flow continue after
        # after the last resume call for this fiber
        # (in other words, returns from this function)
        Fiber.yield
      end
      # start timing
      SystemCall.alarm(n)
      # start running timed call
      result = fiber.resume
    ensure
      # cancel alarms and restore the previous signal handler
      SystemCall.alarm(0)
      Signal.trap('ALRM', handler)
    end
    return result
  end
end

Is this sort of thing really no longer supported in Ruby > 2.0.0 or is it a bug? As I've said, it worked perfectly on 1.9.3.
This class is used like this:

Alarm.alarm(timeout_seconds) do
  # potentially time consuming operation which must have a timeout
end

Due to other requirements, I can't use the Timeout module, as it creates another thread.
Thanks in advance.

Updated by nilsonsfj (Nilson Santos Figueiredo Junior) about 10 years ago

Just a clarification, the issue is that:

Alarm.alarm(timeout_seconds) do
# potentially time consuming operation which must have a timeout
end

when a timeout occurs, this code will still be in trap context

so this will fail:

logger.info "Log Message"

Updated by kernigh (George Koehler) almost 10 years ago

I found this bug report while searching for another bug.

This seems a bug in the Alarm implementation, not a bug in Ruby. Alarm::alarm pauses the fiber in the trap, so it never left trap context. The fix is to escape the trap with raise or throw or a simple return.

I made three revisions.

  1. Remove the version number from 'libc.so', so it can find libc.so.73 in my OpenBSD system.
  2. Switch to Fiddle::Importer, to avoid the message "DL is deprecated, please use Fiddle"
  3. Use return to escape the trap.

It still fails if the signal comes to the wrong thread. The old code would yield from the wrong fiber, probably raising "can't yield from root fiber (FiberError)". The new code would raise "unexpected return (LocalJumpError)".

Here is my revised code:


require 'fiddle'
require 'fiddle/import'

module Alarm
  module SystemCall
    extend Fiddle::Importer

    # imports the POSIX alarm function
    begin
      dlload 'libc.so'
    rescue
      dlload 'libc.dylib'
    end
    extern 'unsigned int alarm(unsigned int)'
  end

  def self.alarm(n)
    # new signal handler for the alarm call
    handler = Signal.trap('ALRM') do
      # returns from this function
      return
    end
    # start timing
    SystemCall.alarm(n)
    # start running timed call
    return yield
  ensure
    # cancel alarms and restore the previous signal handler
    SystemCall.alarm(0)
    Signal.trap('ALRM', handler)
  end
end

Here is a test program:

require_relative 'alarm'

Alarm.alarm(4) do
  1.step {|i| sleep 1; puts i }
end
puts "Got the alarm!"
Mutex.new.synchronize { puts "Got the mutex!" }
Actions #3

Updated by jeremyevans0 (Jeremy Evans) almost 5 years ago

  • Status changed from Open to Closed
  • Backport deleted (1.9.3: UNKNOWN, 2.0.0: UNKNOWN, 2.1: UNKNOWN)
Actions

Also available in: Atom PDF

Like0
Like0Like0Like0