The new Vagrant::Util::Busy.
This commit is contained in:
parent
bbd0f0e8cb
commit
f1ad7234b9
|
@ -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
|
|
@ -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
|
Loading…
Reference in New Issue