Skip to content

Commit

Permalink
Waitable mixin as a concurrency primitive for waiting on things
Browse files Browse the repository at this point in the history
  • Loading branch information
loganb committed Apr 4, 2012
1 parent 3500720 commit 72ed856
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 0 deletions.
1 change: 1 addition & 0 deletions lib/tapestry.rb
Expand Up @@ -57,6 +57,7 @@ def sleep(*args)
end

require 'tapestry/fiber'
require 'tapestry/waitable'
require 'tapestry/io'
require 'tapestry/tcp_socket'
require 'tapestry/tcp_server'
83 changes: 83 additions & 0 deletions lib/tapestry/waitable.rb
@@ -0,0 +1,83 @@
#
# Waitable includes methods and attributes to enable a Fiber
# to block on the object
#
#
module Tapestry::Waitable
def self.included(base)
base.extend(ClassMethods)
end

module ClassMethods
#
# Defines the methods:
# * wait_for_sym
# * on_sym
# * signal_sym
#
# If a block is supplied, it is called in the (context of the instance
# object) whenever wait_for_sym or on_sym is called and can be used to
# arm the signal or otherwise set it up to fire.
#
# Example:
#
# TBD
#
def signal_on(sym, &block)
sig_var = "@_#{sym}_wait_set".to_sym

add_to_set = ->(f) do
wait_set = instance_variable_get sig_var
#The common case is that there's only one waiter, so we only
#create the Array if there's more than one
if(wait_set.is_a? Array)
wait_set << f
else
if wait_set.nil?
#No waiters, we're the first
v = f
else
#One waiter already, promote to array
v = [wait_set, f]
end
instance_variable_set sig_var, v
end
end

define_method "wait_for_#{sym}".to_sym, ->(timeout = :forever) do
instance_exec Fiber.current.tapestry_fiber, &add_to_set

Fiber.sleep timeout
end

define_method "on_#{sym}" do |&block|
instance_exec block, &add_to_set
end

exec_cb = lambda do |cb|
if cb.is_a? Proc
cb.call
else #Its a Fiber
cb.signal :signalled
end
end

define_method "signal_#{sym}" do
wait_set = instance_variable_get sig_var

if wait_set.nil?
false
else
if wait_set.is_a? Array
wait_set.each &exec_cb
else
exec_cb.call wait_set
end
true
end
end
end
end

protected
end
45 changes: 45 additions & 0 deletions spec/waitable_spec.rb
@@ -0,0 +1,45 @@
require File.expand_path('../spec_helper', __FILE__)

describe Tapestry::Waitable do
#Make a class with a single signal and instantiate it
class TestClass
include Tapestry::Waitable

signal_on :test_it
end

it "waits for a signal" do
results = []

Tapestry.boot! do
obj = TestClass.new

Tapestry::Fiber.new do
results << 1
obj.wait_for_test_it
results << 3
end

Tapestry::Fiber.sleep(0.01)
results << 2
obj.signal_test_it
end

results.should == [1,2,3]
end

it "times out waiting for a signal" do
results = []

Tapestry.boot! do
obj = TestClass.new

Tapestry::Fiber.new do

end

Tapestry::Fiber.sleep 0.01
end
end

end

0 comments on commit 72ed856

Please sign in to comment.