The new Vagrant::Util::Busy.

This commit is contained in:
Mitchell Hashimoto 2010-07-18 08:10:40 -07:00
parent bbd0f0e8cb
commit f1ad7234b9
2 changed files with 165 additions and 0 deletions

59
lib/vagrant/util/busy.rb Normal file
View File

@ -0,0 +1,59 @@
module Vagrant
module Util
# Utility class which allows blocks of code to be marked as "busy"
# with a specified interrupt handler. During busy areas of code, it
# is often undesirable for SIGINTs to immediately kill the application.
# This class is a helper to cleanly register callbacks to handle this
# situation.
class Busy
@@registered = []
@@mutex = Mutex.new
class << self
# Mark a given block of code as a "busy" block of code, which will
# register a SIGINT handler for the duration of the block. When a
# SIGINT occurs, the `sig_callback` proc will be called. It is up
# to the callback to behave properly and exit the application.
def busy(sig_callback)
register(sig_callback)
yield
ensure
unregister(sig_callback)
end
# Registers a SIGINT handler. This typically is called from {busy}.
# Callbacks are only registered once, so calling this multiple times
# with the same callback has no consequence.
def register(sig_callback)
@@mutex.synchronize do
registered << sig_callback
registered.uniq!
# Register the handler if this is our first callback.
Signal.trap("INT") { fire_callbacks } if registered.length == 1
end
end
# Unregisters a SIGINT handler.
def unregister(sig_callback)
@@mutex.synchronize do
registered.delete(sig_callback)
# Remove the signal trap if no more registered callbacks exist
Signal.trap("INT", "DEFAULT") if registered.empty?
end
end
# Fires all the registered callbacks.
def fire_callbacks
registered.each { |r| r.call }
end
# Helper method to get access to the class variable. This is mostly
# exposed for tests. This shouldn't be mucked with directly, since it's
# structure may change at any time.
def registered; @@registered; end
end
end
end
end

View File

@ -0,0 +1,106 @@
require File.join(File.dirname(__FILE__), '..', '..', 'test_helper')
class BusyUtilTest < Test::Unit::TestCase
setup do
@klass = Vagrant::Util::Busy
end
context "registering" do
setup do
@callback = lambda { puts "FOO" }
Signal.stubs(:trap)
end
teardown do
@klass.registered.clear
end
should "trap the signal on the first registration" do
Signal.expects(:trap).with("INT").once
@klass.register(@callback)
@klass.register(lambda { puts "BAR" })
end
should "not register the same callback multiple times" do
@klass.register(@callback)
@klass.register(@callback)
@klass.register(@callback)
assert_equal 1, @klass.registered.length
assert_equal @callback, @klass.registered.first
end
end
context "unregistering" do
setup do
Signal.stubs(:trap)
@callback = lambda { puts "FOO" }
end
teardown do
@klass.registered.clear
end
should "remove the callback and set the trap to DEFAULT when removing final" do
@klass.register(@callback)
Signal.expects(:trap).with("INT", "DEFAULT").once
@klass.unregister(@callback)
assert @klass.registered.empty?
end
should "not reset signal trap if not final callback" do
@klass.register(@callback)
@klass.register(lambda { puts "BAR" })
Signal.expects(:trap).never
@klass.unregister(@callback)
end
end
context "marking for busy" do
setup do
@callback = lambda { "foo" }
end
should "register, call the block, then unregister" do
waiter = mock("waiting")
proc = lambda { waiter.ping! }
seq = sequence('seq')
@klass.expects(:register).with(@callback).in_sequence(seq)
waiter.expects(:ping!).in_sequence(seq)
@klass.expects(:unregister).with(@callback).in_sequence(seq)
@klass.busy(@callback, &proc)
end
should "unregister callback even if block raises exception" do
waiter = mock("waiting")
proc = lambda { waiter.ping! }
seq = sequence('seq')
@klass.expects(:register).with(@callback).in_sequence(seq)
waiter.expects(:ping!).raises(Exception.new("uh oh!")).in_sequence(seq)
@klass.expects(:unregister).with(@callback).in_sequence(seq)
assert_raises(Exception) { @klass.busy(@callback, &proc) }
end
end
context "firing callbacks" do
setup do
Signal.stubs(:trap)
end
teardown do
@klass.registered.clear
end
should "just call the registered callbacks" do
waiting = mock("waiting")
waiting.expects(:ping!).once
@klass.register(lambda { waiting.ping! })
@klass.fire_callbacks
end
end
end