Forum: Ruby GServer in Ruby 2.0.0

Posted by Joe L. (joe_l)
on 2013-01-01 17:21
(Received via mailing list)
I'm working on updating a gem to be compatible with Ruby 2.0 and am
currently using Ruby 2.0.0-preview2. I'm running into trouble with a
class which inherits from GServer and I think it's related to this bug
and subsequent patch <https://bugs.ruby-lang.org/issues/6416>. Please
look at the gist:https://gist.github.com/4424479

Using Ruby <= 1.9.3, when sending an interrupt signal this code would
exit cleanly. Using Ruby 2.0, an exception is thrown on launcher.stop:

~/.rvm/rubies/ruby-2.0.0-preview2/lib/ruby/2.0.0/gserver.rb:116:in
`synchronize': can't be called from trap context (ThreadError)
    from
~/.rvm/rubies/ruby-2.0.0-preview2/lib/ruby/2.0.0/gserver.rb:116:in
`stop'

As you can see, I am not explicitly calling 'join' in the trap context
but 'synchronize' is called as part of GServer#close

gserver.rb
114   # Stop the server
115   def stop
116     @connectionsMutex.synchronize  {
117       if @tcpServerThread
118         @tcpServerThread.raise "stop"
119       end
120     }
121   end

I'm open to any ideas or suggestions. Thanks!
Posted by Sean O'halpin (sean)
on 2013-01-04 14:45
(Received via mailing list)
On Tue, Jan 1, 2013 at 4:20 PM, Joe Leo <joseph.leo3@gmail.com> wrote:
> `synchronize': can't be called from trap context (ThreadError)
>     from
> ~/.rvm/rubies/ruby-2.0.0-preview2/lib/ruby/2.0.0/gserver.rb:116:in
> `stop'
>

I can confirm this is new behaviour in 2.0.0-preview2 - it also
happens when you use Queue#enq from within a trap handler (which is an
issue for me unfortunately).

You could use a variant of the self-pipe trick to signal a thread as
in the example below. Regards, Sean.

# CODE

require "gserver"

# example server
class MyServer < GServer
  def initialize(port=10001, *args)
    super(port, *args)
  end
  def serve(io)
    io.puts(Time.now.to_s)
  end
end

class Launcher
  def initialize(servers)
    @servers = servers
  end

  def start
    @servers.each { |server| server.start }
  end

  def join
    @servers.each { |server| server.join }
  end

  def stop
    @servers.each { |server| server.stop }
  end
end

servers = [MyServer.new]
launcher = Launcher.new(servers)

# create self-pipe
p_read, p_write = IO.pipe

Thread.start(launcher, p_read) do |l, pr|
  pr.read # blocks until write end closed
  pr.close
  l.stop
  exit
end

launcher.start
trap("SIGINT") { p_write.close } # close pipe to signal thread
launcher.join
Posted by Joe L. (joe_l)
on 2013-01-05 01:51
(Received via mailing list)
Wow, this is a really interesting (and effective) solution. Reading the
docs on IO.pipe (http://www.ruby-doc.org/core-1.9.3/IO.html) this looks
pretty safe. And it works on Ruby 1.9.3 and 2.0.0-preview2.

My objective was to clean up my resources when the program receives a
SIGTERM. (As a counter example, calling "exit!" inside the original trap
context in my gist would have successfully killed my program without
throwing an exception but would not have cleaned up the utilized
resources.) There is the ominous "does not work on all systems" warning
with this method, but if I can test it out on some of the major ones
successfully, I'll go with it.

For what it's worth, I've filed my issue with ruby-core here:
http://bugs.ruby-lang.org/issues/7648. It hasn't gained any traction 
yet,
but if you want to post your own experience with Queue#enq it might be 
an
addendum to this bug report or a new bug altogether.

Thanks for your help! This was educational.

Joe
Please log in before posting. Registration is free and takes only a minute.
Existing account (Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
No account? Register here.