tags:

views:

438

answers:

1

I'm looking for a portable interface to POSIX alarm(2) (or similar) in Ruby. That's to say, I would like to be able to set a background timer to send a signal to the current process after n seconds.

I have found some good discussion from 2006 on the ruby-talk list that provides a solution using dl/import, but that's a bit of a hack (albeit a neat hack) and not very portable.

I've looked at the much-maligned Timeout module and that won't cut it under JRuby although it works fine with the traditional interpreter. My program is a small command-line shell that uses the Readline library:

TIMEOUT = 5 # seconds
loop do
  input = nil
  begin
    Timeout.timeout(TIMEOUT) do
      input = Readline::readline('> ', nil)
    end
  rescue Timeout::Error
    puts "Timeout"
    next
  end
  # do something with input
end

Under JRuby it seems the process blocks in the readline call and Timeout::Error is only thrown after (a) the timer expires and (b) the user enters a new line. And the exception doesn't get rescued. Hmm.

So I came up with this workaround:

require 'readline'
class TimeoutException < Exception ; end
TIMEOUT = 5 # seconds

loop do
  input = nil
  start_time = Time.now
  thread = Thread.new { input = Readline::readline('> ', nil) }
  begin
    while thread.alive? do
      sleep(1) # prevent CPU from melting
      raise TimeoutException if(Time.now - start_time > TIMEOUT)
    end
  rescue TimeoutException
    thread.exit
    puts "Timeout"
  end
  # do something with input
end

This is... clunky (let's be polite). I just want alarm(2)! I don't really want to drag in non-core libraries (eg Terminator) for this. Is there a better way?

EDIT: I can't get another alternative -- creating a thread that sleeps and then sends a signal to the process -- to work under JRuby either. Does JRuby eat signals? Example:

SIG = 'USR2'
Signal.trap(SIG) { raise }
Process.kill(SIG, Process.pid)

JRuby simply returns, Ruby returns the expected "unhandled exception" error.

+1  A: 

I'm sorry that I don't have an answer to your larger problem of sending a signal after X seconds to a process, but it seems that all you want to do is timeout after X seconds of waiting for input, and if that's the case then I'd say you are looking for Kernel.select :D

I've personally never used this, but after doing a google for "non-blocking gets", and subsequently exploring links, I found these two to be invaluable discussions:

http://www.ruby-forum.com/topic/126795 (Discussion of multi-threaded gets)

http://www.ruby-forum.com/topic/121404 (Explanation of Kernel.select in 2nd post)

Here's a sample of how to use it. This will print out your prompt and wait for input... If there is no input after five seconds, then the program will end. If there is input, as soon as there is input it will spit it back out and end... Obviously you can modify this for your own purposes.

def prompt
  STDOUT.write "> "
  STDOUT.flush
end

def amusing_messages
  [ "You must enter something!", 
    "Why did you even start me if you just wanted to stare at me?", 
    "Isn't there anything better you could be doing?", 
    "Just terminate me already... this is getting old",
    "I'm waiting..."]
end

prompt

loop do
  read_array, write_array, error_array = Kernel.select [STDIN], nil, nil, 5

  if read_array.nil?
    puts amusing_messages[rand(amusing_messages.length)]
  else
    puts "Result is: #{read_array[0].read_nonblock(30)}" 
  end

  prompt 

end

It's probably not as elegant as you might like, but it definitely gets the job done without mucking around with threads. Unfortunately, this won't help you should you want something more robust (timer/sending a signal to the process), and sadly, I have no clue if this works in JRuby. Would love to know if it does though :)

Keith Hanson
+1 for neat idea, but I want to keep readline (commandline editing, history, completion,..). I've rewritten one of my classes so I can drop JRuby. Quick prototyping with BouncyCastle was great, but JRuby still seems young for serious work (and don't talk to me about Kernel#select docs!!). Cheers!
Martin Carpenter