celluloid 0.7.2 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -12,7 +12,7 @@ objects just as easily as you build sequential programs out of regular objects.
12
12
  Recommended for any developer, including novices, Celluloid should help ease
13
13
  your worries about building multithreaded Ruby programs:
14
14
 
15
- * __Look ma, no mutexes:__ Celluloid automatically synchronizes access to instance
15
+ * __Automatic synchronization:__ Celluloid synchronizes access to instance
16
16
  variables by using a special proxy object system and messaging model.
17
17
  * __[Futures](https://github.com/tarcieri/celluloid/wiki/futures):__
18
18
  Ever wanted to call a method "in the background" and retrieve the
@@ -40,12 +40,15 @@ _asynchronously_, so the receiver to do things in the background for you
40
40
  without the caller having to sit around waiting for the result.
41
41
 
42
42
  You can also build distributed systems with Celluloid using its
43
- [sister project DCell](https://github.com/tarcieri/dcell).
43
+ [sister project DCell](https://github.com/tarcieri/dcell). Evented IO similar
44
+ to EventMachine (albeit with a synchronous API) is available through the
45
+ [Celluloid::IO](https://github.com/tarcieri/celluloid-io) library.
44
46
 
45
47
  [Please see the Celluloid Wiki](https://github.com/tarcieri/celluloid/wiki)
46
48
  for more detailed documentation and usage notes.
47
49
 
48
50
  Like Celluloid? [Join the Google Group](http://groups.google.com/group/celluloid-ruby)
51
+ or visit us on IRC at #celluloid on freenode
49
52
 
50
53
  Supported Platforms
51
54
  -------------------
@@ -144,5 +147,5 @@ Contributing to Celluloid
144
147
  License
145
148
  -------
146
149
 
147
- Copyright (c) 2011 Tony Arcieri. Distributed under the MIT License. See
150
+ Copyright (c) 2012 Tony Arcieri. Distributed under the MIT License. See
148
151
  LICENSE.txt for further details.
@@ -43,13 +43,6 @@ module Celluloid
43
43
  Kernel.sleep interval
44
44
  end
45
45
  end
46
-
47
- # Obtain a hash of active tasks to their current activities
48
- def tasks
49
- actor = Thread.current[:actor]
50
- raise NotActorError, "not in actor scope" unless actor
51
- actor.tasks
52
- end
53
46
  end
54
47
 
55
48
  # Class methods added to classes which include Celluloid
@@ -150,7 +143,7 @@ module Celluloid
150
143
 
151
144
  # Obtain the running tasks for this actor
152
145
  def tasks
153
- Celluloid.tasks
146
+ Thread.current[:actor].tasks.to_a
154
147
  end
155
148
 
156
149
  # Obtain the Ruby object the actor is wrapping. This should ONLY be used
@@ -208,12 +201,17 @@ module Celluloid
208
201
  # Perform a blocking or computationally intensive action inside an
209
202
  # asynchronous thread pool, allowing the caller to continue processing other
210
203
  # messages in its mailbox in the meantime
211
- def async(&block)
204
+ def defer(&block)
212
205
  # This implementation relies on the present implementation of
213
206
  # Celluloid::Future, which uses an Actor to run the block
214
207
  Future.new(&block).value
215
208
  end
216
209
 
210
+ # Deprecated, do not use
211
+ def async
212
+ raise "Celluloid#async is defunct. Please use #defer instead"
213
+ end
214
+
217
215
  # Process async calls via method_missing
218
216
  def method_missing(meth, *args, &block)
219
217
  # bang methods are async calls
@@ -253,9 +251,9 @@ require 'celluloid/responses'
253
251
  require 'celluloid/signals'
254
252
  require 'celluloid/task'
255
253
  require 'celluloid/timers'
254
+ require 'celluloid/thread_pool'
256
255
 
257
256
  require 'celluloid/actor'
258
- require 'celluloid/actor_pool'
259
- require 'celluloid/supervisor'
260
257
  require 'celluloid/future'
261
- require 'celluloid/application'
258
+ require 'celluloid/group'
259
+ require 'celluloid/supervisor'
@@ -20,10 +20,7 @@ module Celluloid
20
20
  # messages.
21
21
  class Actor
22
22
  extend Registry
23
-
24
- attr_reader :proxy
25
- attr_reader :links
26
- attr_reader :mailbox
23
+ attr_reader :proxy, :tasks, :links, :mailbox
27
24
 
28
25
  # Invoke a method on the given actor via its mailbox
29
26
  def self.call(mailbox, meth, *args, &block)
@@ -36,15 +33,16 @@ module Celluloid
36
33
  end
37
34
 
38
35
  if Celluloid.actor?
39
- response = Thread.current[:actor].wait [:call, call.id]
36
+ # The current task will be automatically resumed when we get a response
37
+ Task.suspend :callwait
40
38
  else
41
39
  # Otherwise we're inside a normal thread, so block
42
40
  response = Thread.mailbox.receive do |msg|
43
- msg.respond_to?(:call_id) and msg.call_id == call.id
41
+ msg.respond_to?(:call) and msg.call == call
44
42
  end
45
- end
46
43
 
47
- response.value
44
+ response.value
45
+ end
48
46
  end
49
47
 
50
48
  # Invoke a method asynchronously on an actor via its mailbox
@@ -59,6 +57,13 @@ module Celluloid
59
57
  end
60
58
  end
61
59
 
60
+ # Call a method asynchronously and retrieve its value later
61
+ def self.future(mailbox, meth, *args, &block)
62
+ future = Future.new
63
+ future.execute(mailbox, meth, args, block)
64
+ future
65
+ end
66
+
62
67
  # Wrap the given subject with an Actor
63
68
  def initialize(subject)
64
69
  @subject = subject
@@ -69,14 +74,15 @@ module Celluloid
69
74
  @mailbox = Mailbox.new
70
75
  end
71
76
 
77
+ @proxy = ActorProxy.new(@mailbox, subject.class.to_s)
78
+ @tasks = Set.new
72
79
  @links = Links.new
73
80
  @signals = Signals.new
74
81
  @receivers = Receivers.new
75
82
  @timers = Timers.new
76
- @proxy = ActorProxy.new(@mailbox, subject.class.to_s)
77
83
  @running = true
78
84
 
79
- @thread = Pool.get do
85
+ @thread = ThreadPool.get do
80
86
  Thread.current[:actor] = self
81
87
  Thread.current[:mailbox] = @mailbox
82
88
 
@@ -137,7 +143,7 @@ module Celluloid
137
143
  @running = false
138
144
  handle_crash(ex)
139
145
  ensure
140
- Pool.put @thread
146
+ ThreadPool.put @thread
141
147
  end
142
148
 
143
149
  # How long to wait until the next timer fires
@@ -154,27 +160,6 @@ module Celluloid
154
160
  end
155
161
  end
156
162
 
157
- # Obtain a hash of tasks that are currently waiting
158
- def tasks
159
- # A hash of tasks to what they're waiting on is more meaningful to the
160
- # end-user, and lets us make a copy of the tasks table, rather than
161
- # handing them the one we're using internally across threads, a definite
162
- # thread safety shared state no-no
163
- tasks = {}
164
- current_task = Task.current rescue nil
165
- tasks[current_task] = :running if current_task
166
-
167
- @signals.waiting.each do |waitable, waiters|
168
- if waiters.is_a? Enumerable
169
- waiters.each { |waiter| tasks[waiter] = waitable }
170
- else
171
- tasks[waiters] = waitable
172
- end
173
- end
174
-
175
- tasks
176
- end
177
-
178
163
  # Schedule a block to run at the given time
179
164
  def after(interval)
180
165
  @timers.add(interval) do
@@ -186,7 +171,7 @@ module Celluloid
186
171
  def sleep(interval)
187
172
  task = Task.current
188
173
  @timers.add(interval) { task.resume }
189
- Task.suspend
174
+ Task.suspend :sleeping
190
175
  end
191
176
 
192
177
  # Handle an incoming message
@@ -195,11 +180,7 @@ module Celluloid
195
180
  when Call
196
181
  Task.new(:message_handler) { message.dispatch(@subject) }.resume
197
182
  when Response
198
- handled_successfully = signal [:call, message.call_id], message
199
-
200
- unless handled_successfully
201
- Logger.debug("anomalous message! spurious response to call #{message.call_id}")
202
- end
183
+ message.call.task.resume message.value
203
184
  else
204
185
  @receivers.handle_message(message)
205
186
  end
@@ -230,7 +211,7 @@ module Celluloid
230
211
  def cleanup(exit_event)
231
212
  @mailbox.shutdown
232
213
  @links.send_event exit_event
233
- tasks.each { |task, _| task.terminate }
214
+ tasks.each { |task| task.terminate }
234
215
 
235
216
  begin
236
217
  @subject.finalize if @subject.respond_to? :finalize
@@ -41,7 +41,7 @@ module Celluloid
41
41
 
42
42
  # Create a Celluloid::Future which calls a given method
43
43
  def future(method_name, *args, &block)
44
- Future.new { Actor.call @mailbox, method_name, *args, &block }
44
+ Actor.future @mailbox, method_name, *args, &block
45
45
  end
46
46
 
47
47
  # Terminate the associated actor
@@ -62,14 +62,15 @@ module Celluloid
62
62
 
63
63
  # method_missing black magic to call bang predicate methods asynchronously
64
64
  def method_missing(meth, *args, &block)
65
+ meth = meth.to_s
66
+
65
67
  # bang methods are async calls
66
- if meth.to_s.match(/!$/)
67
- unbanged_meth = meth.to_s.sub(/!$/, '')
68
- Actor.async @mailbox, unbanged_meth, *args, &block
69
- return
68
+ if meth.match(/!$/)
69
+ meth.sub!(/!$/, '')
70
+ Actor.async @mailbox, meth, *args, &block
71
+ else
72
+ Actor.call @mailbox, meth, *args, &block
70
73
  end
71
-
72
- Actor.call @mailbox, meth, *args, &block
73
74
  end
74
75
  end
75
76
  end
@@ -1,10 +1,9 @@
1
1
  module Celluloid
2
2
  # Calls represent requests to an actor
3
3
  class Call
4
- attr_reader :id, :caller, :method, :arguments, :block
4
+ attr_reader :caller, :method, :arguments, :block
5
5
 
6
- def initialize(caller, method, arguments, block)
7
- @id = object_id # memoize object ID for serialization
6
+ def initialize(caller, method, arguments = [], block = nil)
8
7
  @caller, @method, @arguments, @block = caller, method, arguments, block
9
8
  end
10
9
 
@@ -29,11 +28,18 @@ module Celluloid
29
28
 
30
29
  # Synchronous calls wait for a response
31
30
  class SyncCall < Call
31
+ attr_reader :task
32
+
33
+ def initialize(caller, method, arguments = [], block = nil, task = Fiber.current.task)
34
+ super(caller, method, arguments, block)
35
+ @task = task
36
+ end
37
+
32
38
  def dispatch(obj)
33
39
  begin
34
40
  check_signature(obj)
35
41
  rescue Exception => ex
36
- respond ErrorResponse.new(@id, AbortError.new(ex))
42
+ respond ErrorResponse.new(self, AbortError.new(ex))
37
43
  return
38
44
  end
39
45
 
@@ -42,7 +48,7 @@ module Celluloid
42
48
  rescue Exception => exception
43
49
  # Exceptions that occur during synchronous calls are reraised in the
44
50
  # context of the caller
45
- respond ErrorResponse.new(@id, exception)
51
+ respond ErrorResponse.new(self, exception)
46
52
 
47
53
  if exception.is_a? AbortError
48
54
  # Aborting indicates a protocol error on the part of the caller
@@ -54,12 +60,12 @@ module Celluloid
54
60
  end
55
61
  end
56
62
 
57
- respond SuccessResponse.new(@id, result)
63
+ respond SuccessResponse.new(self, result)
58
64
  end
59
65
 
60
66
  def cleanup
61
67
  exception = DeadActorError.new("attempted to call a dead actor")
62
- respond ErrorResponse.new(@id, exception)
68
+ respond ErrorResponse.new(self, exception)
63
69
  end
64
70
 
65
71
  #######
@@ -6,6 +6,15 @@ class Thread
6
6
  def self.mailbox
7
7
  current[:mailbox] ||= Celluloid::Mailbox.new
8
8
  end
9
+
10
+ # Receive a message either as an actor or through the local mailbox
11
+ def self.receive(&block)
12
+ if Celluloid.actor?
13
+ Celluloid.receive(&block)
14
+ else
15
+ mailbox.receive(&block)
16
+ end
17
+ end
9
18
  end
10
19
 
11
20
  class Fiber
@@ -2,29 +2,16 @@ module Celluloid
2
2
  # Turn concurrent objects into finite state machines
3
3
  # Inspired by Erlang's gen_fsm. See http://www.erlang.org/doc/man/gen_fsm.html
4
4
  module FSM
5
+ class UnattachedError < StandardError; end # Not attached to an actor
6
+
5
7
  DEFAULT_STATE = :default # Default state name unless one is explicitly set
6
8
 
7
9
  # Included hook to extend class methods
8
10
  def self.included(klass)
9
- klass.send :include, Celluloid
10
- klass.send :extend, ClassMethods
11
+ klass.send :extend, ClassMethods
11
12
  end
12
13
 
13
14
  module ClassMethods
14
- # Ensure FSMs transition into the default state after they're initialized
15
- def new(*args, &block)
16
- fsm = super
17
- fsm.transition default_state
18
- fsm
19
- end
20
-
21
- # Ensure FSMs transition into the default state after they're initialized
22
- def new_link(*args, &block)
23
- fsm = super
24
- fsm.transition default_state
25
- fsm
26
- end
27
-
28
15
  # Obtain or set the default state
29
16
  # Passing a state name sets the default state
30
17
  def default_state(new_default = nil)
@@ -45,6 +32,7 @@ module Celluloid
45
32
  # * to: a state or array of states this state can transition to
46
33
  def state(*args, &block)
47
34
  if args.last.is_a? Hash
35
+ # Stringify keys :/
48
36
  options = args.pop.inject({}) { |h,(k,v)| h[k.to_s] = v; h }
49
37
  else
50
38
  options = {}
@@ -52,16 +40,29 @@ module Celluloid
52
40
 
53
41
  args.each do |name|
54
42
  name = name.to_sym
43
+ default_state name if options['default']
55
44
  states[name] = State.new(name, options['to'], &block)
56
45
  end
57
46
  end
58
47
  end
59
48
 
49
+ attr_reader :actor
50
+
51
+ # Be kind and call super if you must redefine initialize
52
+ def initialize(actor = nil)
53
+ @state = self.class.default_state
54
+ @actor = actor
55
+ end
56
+
60
57
  # Obtain the current state of the FSM
61
- def current_state
62
- defined?(@state) ? @state : @state = self.class.default_state
58
+ attr_reader :state
59
+
60
+ # Attach this FSM to an actor. This allows FSMs to wait for and initiate
61
+ # events in the context of a particular actor
62
+ def attach(actor)
63
+ @actor = actor
63
64
  end
64
- alias_method :state, :current_state
65
+ alias_method :actor=, :attach
65
66
 
66
67
  # Transition to another state
67
68
  # Options:
@@ -83,33 +84,30 @@ module Celluloid
83
84
 
84
85
  new_state = self.class.states[state_name]
85
86
 
86
- if !new_state and state_name == self.class.default_state
87
- # FIXME This probably isn't thread safe... or wise
88
- new_state = self.class.states[state_name] = State.new(state_name)
87
+ unless new_state
88
+ return if state_name == self.class.default_state
89
+ raise ArgumentError, "invalid state for #{self.class}: #{state_name}"
89
90
  end
90
91
 
91
- if new_state
92
- if options[:delay]
93
- @delayed_transition.cancel if @delayed_transition
92
+ if options[:delay]
93
+ raise UnattachedError, "can't delay unless attached" unless @actor
94
+ @delayed_transition.cancel if @delayed_transition
94
95
 
95
- @delayed_transition = after(options[:delay]) do
96
- transition! new_state.name
97
- new_state.call(self)
98
- end
99
-
100
- return @delayed_transition
96
+ @delayed_transition = @actor.after(options[:delay]) do
97
+ transition! new_state.name
98
+ new_state.call(self)
101
99
  end
102
100
 
103
- if defined?(@delayed_transition) and @delayed_transition
104
- @delayed_transition.cancel
105
- @delayed_transition = nil
106
- end
101
+ return @delayed_transition
102
+ end
107
103
 
108
- transition! new_state.name
109
- new_state.call(self)
110
- else
111
- raise ArgumentError, "invalid state for #{self.class}: #{state_name}"
104
+ if defined?(@delayed_transition) and @delayed_transition
105
+ @delayed_transition.cancel
106
+ @delayed_transition = nil
112
107
  end
108
+
109
+ transition! new_state.name
110
+ new_state.call(self)
113
111
  end
114
112
 
115
113
  # Immediate state transition with no sanity checks. "Dangerous!"
@@ -4,56 +4,104 @@ module Celluloid
4
4
  # Celluloid::Future objects allow methods and blocks to run in the
5
5
  # background, their values requested later
6
6
  class Future
7
- # Create a new Celluloid::Future object, allowing a block to be computed in
8
- # the background and its return value obtained later
7
+ # Create a future bound to a given receiver, or with a block to compute
9
8
  def initialize(*args, &block)
10
- @lock = Mutex.new
11
- @value_obtained = false
9
+ @mutex = Mutex.new
10
+ @ready = false
11
+ @result = nil
12
+ @forwards = nil
12
13
 
13
- @runner = Runner.new(*args, &block)
14
- @runner.run!
14
+ if block
15
+ @call = SyncCall.new(self, :call, args)
16
+ ThreadPool.get do
17
+ begin
18
+ @call.dispatch(block)
19
+ rescue
20
+ # Exceptions in blocks will get raised when the value is retrieved
21
+ ensure
22
+ ThreadPool.put Thread.current
23
+ end
24
+ end
25
+ else
26
+ @call = nil
27
+ end
28
+ end
29
+
30
+ # Execute the given method in future context
31
+ def execute(receiver, method, args, block)
32
+ @mutex.synchronize do
33
+ raise "already calling" if @call
34
+ @call = SyncCall.new(self, method, args, block)
35
+ end
36
+
37
+ receiver << @call
15
38
  end
16
39
 
17
40
  # Obtain the value for this Future
18
41
  def value
19
- @lock.synchronize do
20
- unless @value_obtained
21
- @value = @runner.value
22
- @runner.terminate
23
- @value_obtained = true
42
+ ready = result = nil
43
+
44
+ begin
45
+ @mutex.lock
46
+ raise "no call requested" unless @call
47
+
48
+ if @ready
49
+ ready = true
50
+ result = @result
51
+ else
52
+ case @forwards
53
+ when Array
54
+ @forwards << Thread.mailbox
55
+ when NilClass
56
+ @forwards = Thread.mailbox
57
+ else
58
+ @forwards = [@forwards, Thread.mailbox]
59
+ end
24
60
  end
61
+ ensure
62
+ @mutex.unlock
63
+ end
25
64
 
26
- @value
65
+ unless ready
66
+ result = Thread.receive do |msg|
67
+ msg.is_a?(Future::Result) && msg.future == self
68
+ end
27
69
  end
70
+
71
+ result.value
28
72
  end
29
73
  alias_method :call, :value
30
74
 
31
- # Inspect this Celluloid::Future
32
- alias_method :inspect, :to_s
75
+ # Signal this future with the given result value
76
+ def signal(value)
77
+ result = Result.new(value, self)
33
78
 
34
- # Runner is an internal class which executes the given block/method
35
- class Runner
36
- include Celluloid
79
+ @mutex.synchronize do
80
+ raise "the future has already happened!" if @ready
37
81
 
38
- def initialize(*args, &block)
39
- @args, @block = args, block
40
- @called = nil
41
- @error = nil
82
+ if @forwards
83
+ @forwards.is_a?(Array) ? @forwards.each { |f| f << result } : @forwards << result
84
+ end
85
+
86
+ @result = result
87
+ @ready = true
42
88
  end
89
+ end
90
+ alias_method :<<, :signal
43
91
 
44
- def run
45
- @value = @block.call(*@args)
46
- rescue Exception => error
47
- @error = error
48
- ensure
49
- @called = true
50
- signal :finished
92
+ # Inspect this Celluloid::Future
93
+ alias_method :inspect, :to_s
94
+
95
+ # Wrapper for result values to distinguish them in mailboxes
96
+ class Result
97
+ attr_reader :future
98
+
99
+ def initialize(result, future)
100
+ @result, @future = result, future
51
101
  end
52
102
 
53
103
  def value
54
- wait :finished unless @called
55
- abort @error if @error
56
- @value
104
+ @result.value
57
105
  end
58
106
  end
59
107
  end
@@ -1,6 +1,6 @@
1
1
  module Celluloid
2
- # Applications describe and manage networks of Celluloid actors
3
- class Application
2
+ # Supervise collections of actors as a group
3
+ class Group
4
4
  include Celluloid
5
5
  trap_exit :restart_supervisor
6
6
 
@@ -75,4 +75,7 @@ module Celluloid
75
75
  end
76
76
  end
77
77
  end
78
+
79
+ # Legacy support for the old name. Going away soon!
80
+ Application = Group
78
81
  end
@@ -21,52 +21,52 @@ module Celluloid
21
21
 
22
22
  # Add a message to the Mailbox
23
23
  def <<(message)
24
- @lock.synchronize do
25
- raise MailboxError, "dead recipient" if @dead
24
+ @lock.lock
25
+ raise MailboxError, "dead recipient" if @dead
26
26
 
27
- @messages << message
28
- @condition.signal
29
- end
27
+ @messages << message
28
+ @condition.signal
30
29
  nil
30
+ ensure @lock.unlock
31
31
  end
32
32
 
33
33
  # Add a high-priority system event to the Mailbox
34
34
  def system_event(event)
35
- @lock.synchronize do
36
- unless @dead # Silently fail if messages are sent to dead actors
37
- @messages.unshift event
38
- @condition.signal
39
- end
35
+ @lock.lock
36
+ unless @dead # Silently fail if messages are sent to dead actors
37
+ @messages.unshift event
38
+ @condition.signal
40
39
  end
41
40
  nil
41
+ ensure @lock.unlock
42
42
  end
43
43
 
44
44
  # Receive a message from the Mailbox
45
45
  def receive(timeout = nil, &block)
46
46
  message = nil
47
47
 
48
- @lock.synchronize do
49
- raise MailboxError, "attempted to receive from a dead mailbox" if @dead
50
-
51
- begin
52
- message = next_message(&block)
48
+ @lock.lock
49
+ raise MailboxError, "attempted to receive from a dead mailbox" if @dead
53
50
 
54
- unless message
55
- if timeout
56
- now = Time.now
57
- wait_until ||= now + timeout
58
- wait_interval = wait_until - now
59
- return if wait_interval < 0
60
- else
61
- wait_interval = nil
62
- end
51
+ begin
52
+ message = next_message(&block)
63
53
 
64
- @condition.wait(@lock, wait_interval)
54
+ unless message
55
+ if timeout
56
+ now = Time.now
57
+ wait_until ||= now + timeout
58
+ wait_interval = wait_until - now
59
+ return if wait_interval < 0
60
+ else
61
+ wait_interval = nil
65
62
  end
66
- end until message
67
- end
63
+
64
+ @condition.wait(@lock, wait_interval)
65
+ end
66
+ end until message
68
67
 
69
68
  message
69
+ ensure @lock.unlock
70
70
  end
71
71
 
72
72
  # Retrieve the next message in the mailbox
@@ -91,14 +91,14 @@ module Celluloid
91
91
  def shutdown
92
92
  messages = nil
93
93
 
94
- @lock.synchronize do
95
- messages = @messages
96
- @messages = []
97
- @dead = true
98
- end
94
+ @lock.lock
95
+ messages = @messages
96
+ @messages = []
97
+ @dead = true
99
98
 
100
99
  messages.each { |msg| msg.cleanup if msg.respond_to? :cleanup }
101
100
  true
101
+ ensure @lock.unlock
102
102
  end
103
103
 
104
104
  # Is the mailbox alive?
@@ -108,7 +108,9 @@ module Celluloid
108
108
 
109
109
  # Cast to an array
110
110
  def to_a
111
- @lock.synchronize { @messages.dup }
111
+ @lock.lock
112
+ @messages.dup
113
+ ensure @lock.unlock
112
114
  end
113
115
 
114
116
  # Iterate through the mailbox
@@ -20,7 +20,7 @@ module Celluloid
20
20
  end
21
21
 
22
22
  @receivers << receiver
23
- Task.suspend
23
+ Task.suspend :receiving
24
24
  end
25
25
 
26
26
  # How long to wait until the next timer fires
@@ -56,7 +56,7 @@ module Celluloid
56
56
 
57
57
  # Match a message with this receiver's block
58
58
  def match(message)
59
- @block.call(message) if @block
59
+ @block ? @block.call(message) : true
60
60
  end
61
61
 
62
62
  def resume(message = nil)
@@ -1,10 +1,10 @@
1
1
  module Celluloid
2
2
  # Responses to calls
3
3
  class Response
4
- attr_reader :call_id, :value
4
+ attr_reader :call, :value
5
5
 
6
- def initialize(call_id, value)
7
- @call_id, @value = call_id, value
6
+ def initialize(call, value)
7
+ @call, @value = call, value
8
8
  end
9
9
  end
10
10
 
@@ -20,7 +20,7 @@ module Celluloid
20
20
  @waiting[signal] = [tasks, Task.current]
21
21
  end
22
22
 
23
- Task.suspend
23
+ Task.suspend :sigwait
24
24
  end
25
25
 
26
26
  # Send a signal to all method calls waiting for the given name
@@ -31,20 +31,20 @@ module Celluloid
31
31
  case tasks
32
32
  when Array
33
33
  tasks.each { |task| run_task task, value }
34
+ true if tasks.size > 0
34
35
  when NilClass
35
- Logger.debug("spurious signal: #{name}")
36
+ false
36
37
  else
37
38
  run_task tasks, value
39
+ true
38
40
  end
39
-
40
- value
41
41
  end
42
42
 
43
43
  # Run the given task, reporting errors that occur
44
44
  def run_task(task, value)
45
45
  task.resume(value)
46
46
  rescue => ex
47
- Celluloid::Logger.crash("signaling error", ex)
47
+ Logger.crash("signaling error", ex)
48
48
  end
49
49
  end
50
50
  end
@@ -6,39 +6,47 @@ module Celluloid
6
6
  class Task
7
7
  class TerminatedError < StandardError; end # kill a running fiber
8
8
 
9
- attr_reader :type # what type of task is this?
9
+ attr_reader :type
10
+ attr_accessor :status
10
11
 
11
12
  # Obtain the current task
12
13
  def self.current
13
- task = Fiber.current.task
14
- raise "not in task scope" unless task
15
- task
14
+ Fiber.current.task or raise "no task for this Fiber"
16
15
  end
17
16
 
18
17
  # Suspend the running task, deferring to the scheduler
19
- def self.suspend(value = nil)
20
- result = Fiber.yield(value)
18
+ def self.suspend(status)
19
+ task = Task.current
20
+ task.status = status
21
+
22
+ result = Fiber.yield
21
23
  raise TerminatedError, "task was terminated" if result == TerminatedError
24
+ task.status = :running
25
+
22
26
  result
23
27
  end
24
28
 
25
29
  # Run the given block within a task
26
30
  def initialize(type)
27
- @type = type
31
+ @type = type
32
+ @status = :new
28
33
 
29
34
  actor = Thread.current[:actor]
30
35
  mailbox = Thread.current[:mailbox]
31
36
 
32
37
  @fiber = Fiber.new do
38
+ @status = :running
33
39
  Thread.current[:actor] = actor
34
40
  Thread.current[:mailbox] = mailbox
35
-
36
41
  Fiber.current.task = self
42
+ actor.tasks << self
37
43
 
38
44
  begin
39
45
  yield
40
46
  rescue TerminatedError
41
47
  # Task was explicitly terminated
48
+ ensure
49
+ actor.tasks.delete self
42
50
  end
43
51
  end
44
52
  end
@@ -67,7 +75,7 @@ module Celluloid
67
75
 
68
76
  # Nicer string inspect for tasks
69
77
  def inspect
70
- "<Celluloid::Task:0x#{object_id.to_s(16)} @type=#{@type.inspect}, @running=#{@fiber.alive?}>"
78
+ "<Celluloid::Task:0x#{object_id.to_s(16)} @type=#{@type.inspect}, @status=#{@status.inspect}, @running=#{@fiber.alive?}>"
71
79
  end
72
80
  end
73
81
  end
@@ -0,0 +1,57 @@
1
+ require 'thread'
2
+
3
+ module Celluloid
4
+ # Maintain a thread pool FOR SPEED!!
5
+ module ThreadPool
6
+ @pool = []
7
+ @lock = Mutex.new
8
+
9
+ # TODO: should really adjust this based on usage
10
+ @max_idle = 16
11
+
12
+ class << self
13
+ attr_accessor :max_idle
14
+
15
+ # Get a thread from the pool, running the given block
16
+ def get(&block)
17
+ @lock.synchronize do
18
+ if @pool.empty?
19
+ thread = create
20
+ else
21
+ thread = @pool.shift
22
+ end
23
+
24
+ thread[:queue] << block
25
+ thread
26
+ end
27
+ end
28
+
29
+ # Return a thread to the pool
30
+ def put(thread)
31
+ @lock.synchronize do
32
+ if @pool.size >= @max_idle
33
+ thread[:queue] << nil
34
+ else
35
+ @pool << thread
36
+ end
37
+ end
38
+ end
39
+
40
+ # Create a new thread with an associated queue of procs to run
41
+ def create
42
+ queue = Queue.new
43
+ thread = Thread.new do
44
+ begin
45
+ while func = queue.pop
46
+ func.call
47
+ end
48
+ rescue Exception => ex
49
+ Logger.crash("#{self} internal failure", ex)
50
+ end
51
+ end
52
+ thread[:queue] = queue
53
+ thread
54
+ end
55
+ end
56
+ end
57
+ end
@@ -1,4 +1,4 @@
1
1
  module Celluloid
2
- VERSION = '0.7.2'
2
+ VERSION = '0.8.0'
3
3
  def self.version; VERSION; end
4
4
  end
@@ -327,17 +327,21 @@ shared_context "a Celluloid Actor" do |included_module|
327
327
  end
328
328
  end
329
329
 
330
+ let(:receiver) { @receiver.new }
331
+ let(:message) { Object.new }
332
+
333
+ it "allows unconditional receive" do
334
+ receiver.signal_myself(message).should == message
335
+ end
336
+
330
337
  it "allows arbitrary selective receive" do
331
- obj = Object.new
332
- receiver = @receiver.new
333
- received_obj = receiver.signal_myself(obj) { |o| o == obj }
334
- received_obj.should == obj
338
+ received_obj = receiver.signal_myself(message) { |o| o == message }
339
+ received_obj.should == message
335
340
  end
336
341
 
337
342
  it "times out after the given interval" do
338
343
  interval = 0.1
339
344
  started_at = Time.now
340
- receiver = @receiver.new
341
345
 
342
346
  receiver.receive(interval) { false }.should be_nil
343
347
  (Time.now - started_at).should be_within(Celluloid::Timer::QUANTUM).of interval
@@ -448,7 +452,7 @@ shared_context "a Celluloid Actor" do |included_module|
448
452
  # an alias for Celluloid::Actor#waiting_tasks
449
453
  tasks = actor.tasks
450
454
  tasks.size.should == 1
451
- tasks.values.first.should == :running
455
+ tasks.first.status.should == :running
452
456
 
453
457
  future = actor.future(:blocking_call)
454
458
  sleep 0.1 # hax! waiting for ^^^ call to actually start
@@ -456,14 +460,9 @@ shared_context "a Celluloid Actor" do |included_module|
456
460
  tasks = actor.tasks
457
461
  tasks.size.should == 2
458
462
 
459
- blocking_task = nil
460
- tasks.each do |task, waitable|
461
- next if waitable == :running
462
- blocking_task = task
463
- break
464
- end
465
-
466
- tasks[blocking_task].first.should == :call
463
+ blocking_task = tasks.find { |t| t.status != :running }
464
+ blocking_task.should be_a Celluloid::Task
465
+ blocking_task.status.should == :callwait
467
466
 
468
467
  actor.blocker.unblock
469
468
  sleep 0.1 # hax again :(
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: celluloid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.2
4
+ version: 0.8.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-01-08 00:00:00.000000000 Z
12
+ date: 2012-01-24 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
16
- requirement: &70197910788100 !ruby/object:Gem::Requirement
16
+ requirement: &70145052114120 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *70197910788100
24
+ version_requirements: *70145052114120
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rspec
27
- requirement: &70197910787320 !ruby/object:Gem::Requirement
27
+ requirement: &70145052113520 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ~>
@@ -32,25 +32,35 @@ dependencies:
32
32
  version: 2.7.0
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *70197910787320
35
+ version_requirements: *70145052113520
36
+ - !ruby/object:Gem::Dependency
37
+ name: benchmark_suite
38
+ requirement: &70145052113020 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70145052113020
36
47
  description: Celluloid is a concurrent object framework inspired by the Actor Model
37
48
  email:
38
- - tony@medioh.com
49
+ - tony.arcieri@gmail.com
39
50
  executables: []
40
51
  extensions: []
41
52
  extra_rdoc_files: []
42
53
  files:
43
54
  - README.md
44
55
  - lib/celluloid/actor.rb
45
- - lib/celluloid/actor_pool.rb
46
56
  - lib/celluloid/actor_proxy.rb
47
- - lib/celluloid/application.rb
48
57
  - lib/celluloid/calls.rb
49
58
  - lib/celluloid/core_ext.rb
50
59
  - lib/celluloid/events.rb
51
60
  - lib/celluloid/fiber.rb
52
61
  - lib/celluloid/fsm.rb
53
62
  - lib/celluloid/future.rb
63
+ - lib/celluloid/group.rb
54
64
  - lib/celluloid/links.rb
55
65
  - lib/celluloid/logger.rb
56
66
  - lib/celluloid/mailbox.rb
@@ -61,6 +71,7 @@ files:
61
71
  - lib/celluloid/signals.rb
62
72
  - lib/celluloid/supervisor.rb
63
73
  - lib/celluloid/task.rb
74
+ - lib/celluloid/thread_pool.rb
64
75
  - lib/celluloid/timers.rb
65
76
  - lib/celluloid/version.rb
66
77
  - lib/celluloid.rb
@@ -1,54 +0,0 @@
1
- require 'thread'
2
-
3
- module Celluloid
4
- class Actor
5
- # Maintain a thread pool of actors FOR SPEED!!
6
- class Pool
7
- @pool = []
8
- @lock = Mutex.new
9
- @max_idle = 16
10
-
11
- class << self
12
- attr_accessor :max_idle
13
-
14
- def get(&block)
15
- @lock.synchronize do
16
- if @pool.empty?
17
- thread = create
18
- else
19
- thread = @pool.shift
20
- end
21
-
22
- thread[:queue] << block
23
- thread
24
- end
25
- end
26
-
27
- def put(thread)
28
- @lock.synchronize do
29
- if @pool.size >= @max_idle
30
- thread[:queue] << nil
31
- else
32
- @pool << thread
33
- end
34
- end
35
- end
36
-
37
- def create
38
- queue = Queue.new
39
- thread = Thread.new do
40
- begin
41
- while func = queue.pop
42
- func.call
43
- end
44
- rescue Exception => ex
45
- Logger.crash("#{self} internal failure", ex)
46
- end
47
- end
48
- thread[:queue] = queue
49
- thread
50
- end
51
- end
52
- end
53
- end
54
- end