celluloid 0.11.0 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -50,7 +50,7 @@ features which make concurrent programming simple, easy, and fun:
50
50
  a clean state. Celluloid provides its own implementation of the core
51
51
  fault-tolerance concepts in Erlang including [linking](https://github.com/celluloid/celluloid/wiki/Linking),
52
52
  [supervisors](https://github.com/celluloid/celluloid/wiki/Supervisors),
53
- and [supervision trees](https://github.com/celluloid/celluloid/wiki/Groups).
53
+ and [supervision groups](https://github.com/celluloid/celluloid/wiki/Supervision-Groups).
54
54
 
55
55
  * __[Futures](https://github.com/celluloid/celluloid/wiki/futures):__
56
56
  Ever wanted to call a method "in the background" and retrieve the
@@ -1,6 +1,7 @@
1
1
  require 'logger'
2
2
  require 'thread'
3
3
  require 'timeout'
4
+ require 'set'
4
5
 
5
6
  module Celluloid
6
7
  SHUTDOWN_TIMEOUT = 120 # How long actors have to terminate
@@ -169,6 +170,13 @@ module Celluloid
169
170
  end
170
171
  end
171
172
 
173
+ # Mark methods as running exclusively
174
+ def exclusive(*methods)
175
+ @exclusive_methods ||= Set.new
176
+ @exclusive_methods.merge methods.map(&:to_sym)
177
+ end
178
+ attr_reader :exclusive_methods
179
+
172
180
  # Create a mailbox for this actor
173
181
  def mailbox_factory
174
182
  if defined?(@mailbox_factory)
@@ -297,6 +305,11 @@ module Celluloid
297
305
  Thread.current[:actor].exclusive(&block)
298
306
  end
299
307
 
308
+ # Are we currently exclusive
309
+ def exclusive?
310
+ Celluloid.exclusive?
311
+ end
312
+
300
313
  # Call a block after a given interval, returning a Celluloid::Timer object
301
314
  def after(interval, &block)
302
315
  Thread.current[:actor].after(interval, &block)
@@ -333,7 +346,7 @@ module Celluloid
333
346
  unbanged_meth = meth.to_s.sub(/!$/, '')
334
347
  args.unshift unbanged_meth
335
348
 
336
- call = AsyncCall.new(nil, :__send__, args, block)
349
+ call = AsyncCall.new(:__send__, args, block)
337
350
  begin
338
351
  Thread.current[:actor].mailbox << call
339
352
  rescue MailboxError
@@ -368,7 +381,6 @@ require 'celluloid/responses'
368
381
  require 'celluloid/signals'
369
382
  require 'celluloid/task'
370
383
  require 'celluloid/thread_handle'
371
- require 'celluloid/timers'
372
384
  require 'celluloid/uuid'
373
385
 
374
386
  require 'celluloid/actor'
@@ -376,3 +388,4 @@ require 'celluloid/future'
376
388
  require 'celluloid/pool_manager'
377
389
  require 'celluloid/supervision_group'
378
390
  require 'celluloid/supervisor'
391
+ require 'celluloid/notifications'
@@ -1,3 +1,5 @@
1
+ require 'timers'
2
+
1
3
  module Celluloid
2
4
  # Don't do Actor-like things outside Actor scope
3
5
  class NotActorError < StandardError; end
@@ -19,10 +21,21 @@ module Celluloid
19
21
  # normal Ruby objects wrapped in threads which communicate with asynchronous
20
22
  # messages.
21
23
  class Actor
22
- extend Registry
23
24
  attr_reader :subject, :proxy, :tasks, :links, :mailbox, :thread, :name
24
25
 
25
26
  class << self
27
+ extend Forwardable
28
+
29
+ def_delegators "Celluloid::Registry.root", :[], :[]=
30
+
31
+ def registered
32
+ Registry.root.names
33
+ end
34
+
35
+ def clear_registry
36
+ Registry.root.clear
37
+ end
38
+
26
39
  # Obtain the current actor
27
40
  def current
28
41
  actor = Thread.current[:actor]
@@ -63,7 +76,7 @@ module Celluloid
63
76
  # Invoke a method asynchronously on an actor via its mailbox
64
77
  def async(mailbox, meth, *args, &block)
65
78
  begin
66
- mailbox << AsyncCall.new(Thread.mailbox, meth, args, block)
79
+ mailbox << AsyncCall.new(meth, args, block)
67
80
  rescue MailboxError
68
81
  # Silently swallow asynchronous calls to dead actors. There's no way
69
82
  # to reliably generate DeadActorErrors for async calls, so users of
@@ -92,8 +105,11 @@ module Celluloid
92
105
 
93
106
  # Wrap the given subject with an Actor
94
107
  def initialize(subject)
95
- @subject = subject
96
- @mailbox = subject.class.mailbox_factory
108
+ @subject = subject
109
+ @mailbox = subject.class.mailbox_factory
110
+ @exit_handler = subject.class.exit_handler
111
+ @exclusives = subject.class.exclusive_methods
112
+
97
113
  @tasks = Set.new
98
114
  @links = Links.new
99
115
  @signals = Signals.new
@@ -142,26 +158,19 @@ module Celluloid
142
158
 
143
159
  # Receive an asynchronous message
144
160
  def receive(timeout = nil, &block)
145
- @receivers.receive(timeout, &block)
161
+ begin
162
+ @receivers.receive(timeout, &block)
163
+ rescue SystemEvent => event
164
+ handle_system_event(event)
165
+ retry
166
+ end
146
167
  end
147
168
 
148
169
  # Run the actor loop
149
170
  def run
150
171
  begin
151
172
  while @running
152
- begin
153
- message = @mailbox.receive(timeout)
154
- rescue ExitEvent => exit_event
155
- Task.new(:exit_handler) { handle_exit_event exit_event }.resume
156
- retry
157
- rescue NamingRequest => ex
158
- @name = ex.name
159
- retry
160
- rescue TerminationRequest
161
- break
162
- end
163
-
164
- if message
173
+ if message = @mailbox.receive(timeout)
165
174
  handle_message message
166
175
  else
167
176
  # No message indicates a timeout
@@ -169,6 +178,9 @@ module Celluloid
169
178
  @receivers.fire_timers
170
179
  end
171
180
  end
181
+ rescue SystemEvent => event
182
+ handle_system_event event
183
+ retry
172
184
  rescue MailboxShutdown
173
185
  # If the mailbox detects shutdown, exit the actor
174
186
  end
@@ -195,14 +207,14 @@ module Celluloid
195
207
 
196
208
  # Schedule a block to run at the given time
197
209
  def after(interval)
198
- @timers.add(interval) do
210
+ @timers.after(interval) do
199
211
  Task.new(:timer) { yield }.resume
200
212
  end
201
213
  end
202
214
 
203
215
  # Schedule a block to run at the given time
204
216
  def every(interval)
205
- @timers.add(interval, true) do
217
+ @timers.every(interval) do
206
218
  Task.new(:timer) { yield }.resume
207
219
  end
208
220
  end
@@ -213,34 +225,48 @@ module Celluloid
213
225
  Kernel.sleep(interval)
214
226
  else
215
227
  task = Task.current
216
- @timers.add(interval) { task.resume }
228
+ @timers.after(interval) { task.resume }
217
229
  Task.suspend :sleeping
218
230
  end
219
231
  end
220
232
 
221
- # Handle an incoming message
233
+ # Handle standard low-priority messages
222
234
  def handle_message(message)
223
235
  case message
224
236
  when Call
225
- Task.new(:message_handler) { message.dispatch(@subject) }.resume
237
+ if @exclusives && @exclusives.include?(message.method)
238
+ exclusive { message.dispatch(@subject) }
239
+ else
240
+ Task.new(:message_handler) { message.dispatch(@subject) }.resume
241
+ end
226
242
  when Response
227
- message.call.task.resume message
243
+ message.dispatch
228
244
  else
229
245
  @receivers.handle_message(message)
230
246
  end
231
247
  message
232
248
  end
233
249
 
234
- # Handle exit events received by this actor
235
- def handle_exit_event(exit_event)
236
- exit_handler = @subject.class.exit_handler
237
- if exit_handler
238
- return @subject.send(exit_handler, exit_event.actor, exit_event.reason)
250
+ # Handle high-priority system event messages
251
+ def handle_system_event(event)
252
+ case event
253
+ when ExitEvent
254
+ Task.new(:exit_handler) { handle_exit_event event }.resume
255
+ when NamingRequest
256
+ @name = event.name
257
+ when TerminationRequest
258
+ @running = false
239
259
  end
260
+ end
261
+
262
+ # Handle exit events received by this actor
263
+ def handle_exit_event(event)
264
+ # Run the exit handler if available
265
+ return @subject.send(@exit_handler, event.actor, event.reason) if @exit_handler
240
266
 
241
267
  # Reraise exceptions from linked actors
242
268
  # If no reason is given, actor terminated cleanly
243
- raise exit_event.reason if exit_event.reason
269
+ raise event.reason if event.reason
244
270
  end
245
271
 
246
272
  # Handle any exceptions that occur within a running actor
@@ -65,7 +65,8 @@ module Celluloid
65
65
  # Terminate the associated actor
66
66
  def terminate
67
67
  terminate!
68
- Thread.pass while alive?
68
+ join
69
+ nil
69
70
  end
70
71
 
71
72
  # Terminate the associated actor asynchronously
@@ -1,10 +1,10 @@
1
1
  module Celluloid
2
2
  # Calls represent requests to an actor
3
3
  class Call
4
- attr_reader :caller, :method, :arguments, :block
4
+ attr_reader :method, :arguments, :block
5
5
 
6
- def initialize(caller, method, arguments = [], block = nil)
7
- @caller, @method, @arguments, @block = caller, method, arguments, block
6
+ def initialize(method, arguments = [], block = nil)
7
+ @method, @arguments, @block = method, arguments, block
8
8
  end
9
9
 
10
10
  def check_signature(obj)
@@ -37,10 +37,11 @@ module Celluloid
37
37
 
38
38
  # Synchronous calls wait for a response
39
39
  class SyncCall < Call
40
- attr_reader :task
40
+ attr_reader :caller, :task
41
41
 
42
42
  def initialize(caller, method, arguments = [], block = nil, task = Fiber.current.task)
43
- super(caller, method, arguments, block)
43
+ super(method, arguments, block)
44
+ @caller = caller
44
45
  @task = task
45
46
  end
46
47
 
@@ -0,0 +1,84 @@
1
+ module Celluloid
2
+ module Notifications
3
+ class Fanout
4
+ include Celluloid
5
+ trap_exit :prune
6
+
7
+ def initialize
8
+ @subscribers = []
9
+ @listeners_for = {}
10
+ end
11
+
12
+ def subscribe(actor, pattern, method)
13
+ subscriber = Subscriber.new(actor, pattern, method).tap do |s|
14
+ @subscribers << s
15
+ end
16
+ link actor
17
+ @listeners_for.clear
18
+ subscriber
19
+ end
20
+
21
+ def unsubscribe(subscriber)
22
+ @subscribers.reject! { |s| s.matches?(subscriber) }
23
+ @listeners_for.clear
24
+ end
25
+
26
+ def publish(pattern, *args)
27
+ listeners_for(pattern).each { |s| s.publish(pattern, *args) }
28
+ end
29
+
30
+ def listeners_for(pattern)
31
+ @listeners_for[pattern] ||= @subscribers.select { |s| s.subscribed_to?(pattern) }
32
+ end
33
+
34
+ def listening?(pattern)
35
+ listeners_for(pattern).any?
36
+ end
37
+
38
+ def prune(actor, reason=nil)
39
+ @subscribers.reject! { |s| s.actor == actor }
40
+ @listeners_for.clear
41
+ end
42
+ end
43
+
44
+ class Subscriber
45
+ attr_accessor :actor, :pattern, :method
46
+
47
+ def initialize(actor, pattern, method)
48
+ @actor = actor
49
+ @pattern = pattern
50
+ @method = method
51
+ end
52
+
53
+ def publish(pattern, *args)
54
+ Actor.async(actor.mailbox, method, pattern, *args)
55
+ end
56
+
57
+ def subscribed_to?(pattern)
58
+ !pattern || @pattern === pattern.to_s
59
+ end
60
+
61
+ def matches?(subscriber_or_pattern)
62
+ self === subscriber_or_pattern ||
63
+ @pattern && @pattern === subscriber_or_pattern
64
+ end
65
+ end
66
+
67
+ class << self
68
+ attr_accessor :notifier
69
+ end
70
+ self.notifier = Fanout.new
71
+
72
+ def publish(pattern, *args)
73
+ Celluloid::Notifications.notifier.publish(pattern, *args)
74
+ end
75
+
76
+ def subscribe(pattern, method)
77
+ Celluloid::Notifications.notifier.subscribe(Actor.current, pattern, method)
78
+ end
79
+
80
+ def unsubscribe(*args)
81
+ Celluloid::Notifications.notifier.unsubscribe(*args)
82
+ end
83
+ end
84
+ end
@@ -1,6 +1,7 @@
1
1
  module Celluloid
2
2
  # Manages a fixed-size pool of workers
3
3
  # Delegates work (i.e. methods) and supervises workers
4
+ # Don't use this class directly. Instead use MyKlass.pool
4
5
  class PoolManager
5
6
  include Celluloid
6
7
  trap_exit :crash_handler
@@ -16,21 +17,44 @@ module Celluloid
16
17
  @idle = @size.times.map { worker_class.new_link(*@args) }
17
18
  end
18
19
 
19
- # Execute the given method within a worker
20
- def execute(method, *args, &block)
21
- worker = provision_worker
20
+ def _send_(method, *args, &block)
21
+ worker = __provision_worker
22
22
 
23
23
  begin
24
24
  worker._send_ method, *args, &block
25
- rescue => ex
25
+ rescue Exception => ex
26
26
  abort ex
27
27
  ensure
28
28
  @idle << worker if worker.alive?
29
29
  end
30
30
  end
31
31
 
32
+ def name
33
+ _send_ @mailbox, :name
34
+ end
35
+
36
+ def is_a?(klass)
37
+ _send_ :is_a?, klass
38
+ end
39
+
40
+ def kind_of?(klass)
41
+ _send_ :kind_of?, klass
42
+ end
43
+
44
+ def methods(include_ancestors = true)
45
+ _send_ :methods, include_ancestors
46
+ end
47
+
48
+ def to_s
49
+ _send_ :to_s
50
+ end
51
+
52
+ def inspect
53
+ _send_ :inspect
54
+ end
55
+
32
56
  # Provision a new worker
33
- def provision_worker
57
+ def __provision_worker
34
58
  while @idle.empty?
35
59
  # Using exclusive mode blocks incoming messages, so they don't pile
36
60
  # up as waiting Celluloid::Tasks
@@ -53,7 +77,7 @@ module Celluloid
53
77
 
54
78
  def method_missing(method, *args, &block)
55
79
  if respond_to?(method)
56
- execute method, *args, &block
80
+ _send_ method, *args, &block
57
81
  else
58
82
  super
59
83
  end
@@ -1,4 +1,5 @@
1
1
  require 'set'
2
+ require 'timers'
2
3
 
3
4
  module Celluloid
4
5
  # Allow methods to directly interact with the actor protocol
@@ -16,7 +17,7 @@ module Celluloid
16
17
  receiver = Receiver.new block
17
18
 
18
19
  if timeout
19
- receiver.timer = @timers.add(timeout) do
20
+ receiver.timer = @timers.after(timeout) do
20
21
  @receivers.delete receiver
21
22
  receiver.resume
22
23
  end
@@ -2,9 +2,15 @@ require 'thread'
2
2
 
3
3
  module Celluloid
4
4
  # The Registry allows us to refer to specific actors by human-meaningful names
5
- module Registry
6
- @@registry = {}
7
- @@registry_lock = Mutex.new
5
+ class Registry
6
+ def self.root
7
+ @root ||= new
8
+ end
9
+
10
+ def initialize
11
+ @registry = {}
12
+ @registry_lock = Mutex.new
13
+ end
8
14
 
9
15
  # Register an Actor
10
16
  def []=(name, actor)
@@ -13,8 +19,8 @@ module Celluloid
13
19
  raise TypeError, "not an actor"
14
20
  end
15
21
 
16
- @@registry_lock.synchronize do
17
- @@registry[name.to_sym] = actor
22
+ @registry_lock.synchronize do
23
+ @registry[name.to_sym] = actor
18
24
  end
19
25
 
20
26
  actor.mailbox.system_event NamingRequest.new(name.to_sym)
@@ -22,14 +28,34 @@ module Celluloid
22
28
 
23
29
  # Retrieve an actor by name
24
30
  def [](name)
25
- @@registry_lock.synchronize do
26
- @@registry[name.to_sym]
31
+ @registry_lock.synchronize do
32
+ @registry[name.to_sym]
33
+ end
34
+ end
35
+
36
+ alias_method :get, :[]
37
+ alias_method :set, :[]=
38
+
39
+ def delete(name)
40
+ @registry_lock.synchronize do
41
+ @registry[name.to_sym]
27
42
  end
28
43
  end
29
44
 
30
45
  # List all registered actors by name
31
- def registered
32
- @@registry_lock.synchronize { @@registry.keys }
46
+ def names
47
+ @registry_lock.synchronize { @registry.keys }
48
+ end
49
+
50
+ # removes and returns all registered actors as a hash of `name => actor`
51
+ # can be used in testing to clear the registry
52
+ def clear
53
+ hash = nil
54
+ @registry_lock.synchronize do
55
+ hash = @registry.dup
56
+ @registry.clear
57
+ end
58
+ hash
33
59
  end
34
60
  end
35
61
  end
@@ -6,6 +6,10 @@ module Celluloid
6
6
  def initialize(call, value)
7
7
  @call, @value = call, value
8
8
  end
9
+
10
+ def dispatch
11
+ @call.task.resume self
12
+ end
9
13
  end
10
14
 
11
15
  # Call completed successfully
@@ -6,12 +6,19 @@ module Celluloid
6
6
 
7
7
  class << self
8
8
  # Actors or sub-applications to be supervised
9
- def members
10
- @members ||= []
9
+ def blocks
10
+ @blocks ||= []
11
11
  end
12
12
 
13
13
  # Start this application (and watch it with a supervisor)
14
- alias_method :run!, :supervise
14
+ def run!
15
+ group = new do |group|
16
+ blocks.each do |block|
17
+ block.call(group)
18
+ end
19
+ end
20
+ group
21
+ end
15
22
 
16
23
  # Run the application in the foreground with a simple watchdog
17
24
  def run
@@ -31,52 +38,68 @@ module Celluloid
31
38
  # * as: register this application in the Celluloid::Actor[] directory
32
39
  # * args: start the actor with the given arguments
33
40
  def supervise(klass, options = {})
34
- members << Member.new(klass, options)
41
+ blocks << lambda do |group|
42
+ group.add klass, options
43
+ end
35
44
  end
36
45
 
37
46
  # Register a pool of actors to be launched on group startup
38
47
  def pool(klass)
39
- members << Member.new(klass, method: 'pool_link')
48
+ blocks << lambda do |group|
49
+ group.pool klass
50
+ end
40
51
  end
41
52
  end
42
53
 
43
54
  # Start the group
44
- def initialize
45
- @actors = {}
46
-
47
- # This is some serious lolcode, but like... start the supervisors for
48
- # this group
49
- self.class.members.each do |member|
50
- actor = member.start
51
- @actors[actor] = member
52
- end
55
+ def initialize(registry = nil)
56
+ @members = []
57
+ @registry = registry || Registry.root
58
+
59
+ yield self if block_given?
60
+ end
61
+
62
+ def supervise(klass, *args, &block)
63
+ add(klass, :args => args, :block => block)
64
+ end
65
+
66
+ def supervise_as(name, klass, *args, &block)
67
+ add(klass, :args => args, :block => block, :as => name)
68
+ end
69
+
70
+ def pool(klass)
71
+ add(klass, :method => 'pool_link')
72
+ end
73
+
74
+ def add(klass, options)
75
+ member = Member.new(@registry, klass, options)
76
+ @members << member
77
+ member
78
+ end
79
+
80
+ def actors
81
+ @members.map(&:actor)
53
82
  end
54
83
 
55
84
  # Terminate the group
56
85
  def finalize
57
- @actors.each do |actor, _|
58
- begin
59
- actor.terminate
60
- rescue DeadActorError
61
- end
62
- end
86
+ @members.each(&:terminate)
63
87
  end
64
88
 
65
89
  # Restart a crashed actor
66
90
  def restart_actor(actor, reason)
67
- member = @actors.delete actor
91
+ member = @members.find do |member|
92
+ member.actor == actor
93
+ end
68
94
  raise "a group member went missing. This shouldn't be!" unless member
69
95
 
70
- # Ignore supervisors that shut down cleanly
71
- return unless reason
72
-
73
- actor = member.start
74
- @actors[actor] = member
96
+ member.restart(reason)
75
97
  end
76
98
 
77
99
  # A member of the group
78
100
  class Member
79
- def initialize(klass, options = {})
101
+ def initialize(registry, klass, options = {})
102
+ @registry = registry
80
103
  @klass = klass
81
104
 
82
105
  # Stringify keys :/
@@ -84,12 +107,31 @@ module Celluloid
84
107
 
85
108
  @name = options['as']
86
109
  @args = options['args'] ? Array(options['args']) : []
110
+ @block = options['block']
87
111
  @method = options['method'] || 'new_link'
112
+
113
+ start
88
114
  end
115
+ attr_reader :name, :actor
89
116
 
90
117
  def start
91
- actor = @klass.send(@method, *@args)
92
- Actor[@name] = actor if @name
118
+ @actor = @klass.send(@method, *@args, &@block)
119
+ @registry[@name] = @actor if @name
120
+ end
121
+
122
+ def restart(reason)
123
+ @actor = nil
124
+ @registry.delete(@name) if @name
125
+
126
+ # Ignore supervisors that shut down cleanly
127
+ return unless reason
128
+
129
+ start
130
+ end
131
+
132
+ def terminate
133
+ @actor.terminate if @actor
134
+ rescue DeadActorError
93
135
  end
94
136
  end
95
137
  end
@@ -2,68 +2,21 @@ module Celluloid
2
2
  # Supervisors are actors that watch over other actors and restart them if
3
3
  # they crash
4
4
  class Supervisor
5
- include Celluloid
6
- trap_exit :restart_actor
7
-
8
- # Retrieve the actor this supervisor is supervising
9
- attr_reader :actor
10
-
11
5
  class << self
12
6
  # Define the root of the supervision tree
13
7
  attr_accessor :root
14
8
 
15
9
  def supervise(klass, *args, &block)
16
- new(nil, klass, *args, &block)
10
+ SupervisionGroup.new do |group|
11
+ group.supervise klass, *args, &block
12
+ end
17
13
  end
18
14
 
19
15
  def supervise_as(name, klass, *args, &block)
20
- new(name, klass, *args, &block)
21
- end
22
- end
23
-
24
- def initialize(name, klass, *args, &block)
25
- @name, @klass, @args, @block = name, klass, args, block
26
- @started = false
27
-
28
- start_actor
29
- end
30
-
31
- def finalize
32
- @actor.terminate if @actor and @actor.alive?
33
- end
34
-
35
- def start_actor(start_attempts = 3, sleep_interval = 30)
36
- failures = 0
37
-
38
- begin
39
- @actor = @klass.new_link(*@args, &@block)
40
- rescue
41
- failures += 1
42
- if failures >= start_attempts
43
- failures = 0
44
-
45
- Logger.warn("#{@klass} is crashing on initialize too quickly, sleeping for #{sleep_interval} seconds")
46
- sleep sleep_interval
16
+ SupervisionGroup.new do |group|
17
+ group.supervise_as name, klass, *args, &block
47
18
  end
48
- retry
49
19
  end
50
-
51
- @started = true
52
- Actor[@name] = @actor if @name
53
- end
54
-
55
- # When actors die, regardless of the reason, restart them
56
- def restart_actor(actor, reason)
57
- # If the actor we're supervising exited cleanly, exit the supervisor cleanly too
58
- terminate unless reason
59
-
60
- start_actor if @started
61
- end
62
-
63
- def inspect
64
- str = "#<#{self.class}(#{@klass}):0x#{object_id.to_s(16)}"
65
- str << " " << @args.map { |arg| arg.inspect }.join(' ') unless @args.empty?
66
- str << ">"
67
20
  end
68
21
  end
69
22
  end
@@ -1,4 +1,4 @@
1
1
  module Celluloid
2
- VERSION = '0.11.0'
2
+ VERSION = '0.11.1'
3
3
  def self.version; VERSION; end
4
4
  end
@@ -3,83 +3,7 @@ shared_context "a Celluloid Actor" do |included_module|
3
3
  attr_accessor :foo
4
4
  end
5
5
 
6
- let :actor_class do
7
- Class.new do
8
- include included_module
9
- attr_reader :name
10
-
11
- def initialize(name)
12
- @name = name
13
- @delegate = [:bar]
14
- end
15
-
16
- def change_name(new_name)
17
- @name = new_name
18
- end
19
-
20
- def change_name_async(new_name)
21
- change_name! new_name
22
- end
23
-
24
- def greet
25
- "Hi, I'm #{@name}"
26
- end
27
-
28
- def run(*args)
29
- yield(*args)
30
- end
31
-
32
- def crash
33
- raise ExampleCrash, "the spec purposely crashed me :("
34
- end
35
-
36
- def crash_with_abort(reason, foo = nil)
37
- example_crash = ExampleCrash.new(reason)
38
- example_crash.foo = foo
39
- abort example_crash
40
- end
41
-
42
- def crash_with_abort_raw(reason)
43
- abort reason
44
- end
45
-
46
- def internal_hello
47
- external_hello
48
- end
49
-
50
- def external_hello
51
- "Hello"
52
- end
53
-
54
- def method_missing(method_name, *args, &block)
55
- if delegates?(method_name)
56
- @delegate.send method_name, *args, &block
57
- else
58
- super
59
- end
60
- end
61
-
62
- def respond_to?(method_name)
63
- super || delegates?(method_name)
64
- end
65
-
66
- def call_private
67
- zomg_private!
68
- end
69
-
70
- def zomg_private
71
- @private_called = true
72
- end
73
- private :zomg_private
74
- attr_reader :private_called
75
-
76
- private
77
-
78
- def delegates?(method_name)
79
- @delegate.respond_to?(method_name)
80
- end
81
- end
82
- end
6
+ let(:actor_class) { ExampleActorClass.create(included_module) }
83
7
 
84
8
  it "returns the actor's class, not the proxy's" do
85
9
  actor = actor_class.new "Troy McClure"
@@ -417,6 +341,22 @@ shared_context "a Celluloid Actor" do |included_module|
417
341
  end
418
342
  end
419
343
 
344
+ context :exclusive do
345
+ subject do
346
+ Class.new do
347
+ include included_module
348
+ def exclusive_example
349
+ exclusive?
350
+ end
351
+ exclusive :exclusive_example
352
+ end.new
353
+ end
354
+
355
+ it "supports exclusive methods" do
356
+ subject.exclusive_example.should be_true
357
+ end
358
+ end
359
+
420
360
  context :receiving do
421
361
  before do
422
362
  @receiver = Class.new do
@@ -446,11 +386,14 @@ shared_context "a Celluloid Actor" do |included_module|
446
386
  started_at = Time.now
447
387
 
448
388
  receiver.receive(interval) { false }.should be_nil
449
- (Time.now - started_at).should be_within(Celluloid::Timer::QUANTUM).of interval
389
+ (Time.now - started_at).should be_within(Q).of interval
450
390
  end
451
391
  end
452
392
 
453
393
  context :timers do
394
+ # Level of accuracy enforced by the tests (50ms)
395
+ Q = 0.05
396
+
454
397
  before do
455
398
  @klass = Class.new do
456
399
  include included_module
@@ -487,7 +430,7 @@ shared_context "a Celluloid Actor" do |included_module|
487
430
 
488
431
  # Sleep long enough to ensure we're actually seeing behavior when asleep
489
432
  # but not so long as to delay the test suite unnecessarily
490
- interval = Celluloid::Timer::QUANTUM * 10
433
+ interval = Q * 10
491
434
  started_at = Time.now
492
435
 
493
436
  future = actor.future(:do_sleep, interval)
@@ -495,49 +438,49 @@ shared_context "a Celluloid Actor" do |included_module|
495
438
  actor.should be_sleeping
496
439
 
497
440
  future.value
498
- (Time.now - started_at).should be_within(Celluloid::Timer::QUANTUM).of interval
441
+ (Time.now - started_at).should be_within(Q).of interval
499
442
  end
500
443
 
501
444
  it "schedules timers which fire in the future" do
502
445
  actor = @klass.new
503
446
 
504
- interval = Celluloid::Timer::QUANTUM * 10
447
+ interval = Q * 10
505
448
  started_at = Time.now
506
449
 
507
450
  timer = actor.fire_after(interval)
508
451
  actor.should_not be_fired
509
452
 
510
- sleep(interval + Celluloid::Timer::QUANTUM) # wonky! #/
453
+ sleep(interval + Q) # wonky! #/
511
454
  actor.should be_fired
512
455
  end
513
456
 
514
457
  it "schedules recurring timers which fire in the future" do
515
458
  actor = @klass.new
516
459
 
517
- interval = Celluloid::Timer::QUANTUM * 10
460
+ interval = Q * 10
518
461
  started_at = Time.now
519
462
 
520
463
  timer = actor.fire_every(interval)
521
464
  actor.fired.should be == 0
522
465
 
523
- sleep(interval + Celluloid::Timer::QUANTUM) # wonky! #/
466
+ sleep(interval + Q) # wonky! #/
524
467
  actor.fired.should be == 1
525
468
 
526
- 2.times { sleep(interval + Celluloid::Timer::QUANTUM) } # wonky! #/
469
+ 2.times { sleep(interval + Q) } # wonky! #/
527
470
  actor.fired.should be == 3
528
471
  end
529
472
 
530
473
  it "cancels timers before they fire" do
531
474
  actor = @klass.new
532
475
 
533
- interval = Celluloid::Timer::QUANTUM * 10
476
+ interval = Q * 10
534
477
  started_at = Time.now
535
478
 
536
479
  timer = actor.fire_after(interval)
537
480
  actor.should_not be_fired
538
481
  timer.cancel
539
482
 
540
- sleep(interval + Celluloid::Timer::QUANTUM) # wonky! #/
483
+ sleep(interval + Q) # wonky! #/
541
484
  actor.should_not be_fired
542
485
  end
543
486
  end
@@ -0,0 +1,79 @@
1
+ module ExampleActorClass
2
+ def self.create(included_module)
3
+ Class.new do
4
+ include included_module
5
+ attr_reader :name
6
+
7
+ def initialize(name)
8
+ @name = name
9
+ @delegate = [:bar]
10
+ end
11
+
12
+ def change_name(new_name)
13
+ @name = new_name
14
+ end
15
+
16
+ def change_name_async(new_name)
17
+ change_name! new_name
18
+ end
19
+
20
+ def greet
21
+ "Hi, I'm #{@name}"
22
+ end
23
+
24
+ def run(*args)
25
+ yield(*args)
26
+ end
27
+
28
+ def crash
29
+ raise ExampleCrash, "the spec purposely crashed me :("
30
+ end
31
+
32
+ def crash_with_abort(reason, foo = nil)
33
+ example_crash = ExampleCrash.new(reason)
34
+ example_crash.foo = foo
35
+ abort example_crash
36
+ end
37
+
38
+ def crash_with_abort_raw(reason)
39
+ abort reason
40
+ end
41
+
42
+ def internal_hello
43
+ external_hello
44
+ end
45
+
46
+ def external_hello
47
+ "Hello"
48
+ end
49
+
50
+ def method_missing(method_name, *args, &block)
51
+ if delegates?(method_name)
52
+ @delegate.send method_name, *args, &block
53
+ else
54
+ super
55
+ end
56
+ end
57
+
58
+ def respond_to?(method_name)
59
+ super || delegates?(method_name)
60
+ end
61
+
62
+ def call_private
63
+ zomg_private!
64
+ end
65
+
66
+ def zomg_private
67
+ @private_called = true
68
+ end
69
+ private :zomg_private
70
+ attr_reader :private_called
71
+
72
+ private
73
+
74
+ def delegates?(method_name)
75
+ @delegate.respond_to?(method_name)
76
+ end
77
+ end
78
+ end
79
+ end
@@ -1,4 +1,7 @@
1
1
  shared_context "a Celluloid Mailbox" do
2
+ # Level of timer accuracy enforced by the tests (50ms)
3
+ Q = 0.05
4
+
2
5
  class TestEvent < Celluloid::SystemEvent; end
3
6
 
4
7
  it "receives messages" do
@@ -47,6 +50,6 @@ shared_context "a Celluloid Mailbox" do
47
50
  started_at = Time.now
48
51
 
49
52
  subject.receive(interval) { false }
50
- (Time.now - started_at).should be_within(Celluloid::Timer::QUANTUM).of interval
53
+ (Time.now - started_at).should be_within(Q).of interval
51
54
  end
52
55
  end
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.11.0
4
+ version: 0.11.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,22 +9,22 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-06-01 00:00:00.000000000 Z
12
+ date: 2012-07-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- name: rake
16
- requirement: &70191377653480 !ruby/object:Gem::Requirement
15
+ name: timers
16
+ requirement: &70167227984140 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
20
20
  - !ruby/object:Gem::Version
21
- version: '0'
22
- type: :development
21
+ version: 1.0.0
22
+ type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70191377653480
24
+ version_requirements: *70167227984140
25
25
  - !ruby/object:Gem::Dependency
26
- name: rspec
27
- requirement: &70191377602840 !ruby/object:Gem::Requirement
26
+ name: rake
27
+ requirement: &70167227983760 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *70191377602840
35
+ version_requirements: *70167227983760
36
36
  - !ruby/object:Gem::Dependency
37
- name: guard-rspec
38
- requirement: &70191350440520 !ruby/object:Gem::Requirement
37
+ name: rspec
38
+ requirement: &70167227983300 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *70191350440520
46
+ version_requirements: *70167227983300
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: benchmark_suite
49
- requirement: &70191350401720 !ruby/object:Gem::Requirement
49
+ requirement: &70167227982880 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,7 +54,7 @@ dependencies:
54
54
  version: '0'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *70191350401720
57
+ version_requirements: *70167227982880
58
58
  description: Celluloid enables people to build concurrent programs out of concurrent
59
59
  objects just as easily as they build sequential programs out of sequential objects
60
60
  email:
@@ -77,6 +77,7 @@ files:
77
77
  - lib/celluloid/links.rb
78
78
  - lib/celluloid/logger.rb
79
79
  - lib/celluloid/mailbox.rb
80
+ - lib/celluloid/notifications.rb
80
81
  - lib/celluloid/pool_manager.rb
81
82
  - lib/celluloid/receivers.rb
82
83
  - lib/celluloid/registry.rb
@@ -87,11 +88,11 @@ files:
87
88
  - lib/celluloid/supervisor.rb
88
89
  - lib/celluloid/task.rb
89
90
  - lib/celluloid/thread_handle.rb
90
- - lib/celluloid/timers.rb
91
91
  - lib/celluloid/uuid.rb
92
92
  - lib/celluloid/version.rb
93
93
  - lib/celluloid.rb
94
94
  - spec/support/actor_examples.rb
95
+ - spec/support/example_actor_class.rb
95
96
  - spec/support/mailbox_examples.rb
96
97
  homepage: https://github.com/celluloid/celluloid
97
98
  licenses:
@@ -1,110 +0,0 @@
1
- module Celluloid
2
- # Low precision timers implemented in pure Ruby
3
- class Timers
4
- def initialize
5
- @timers = []
6
- end
7
-
8
- # Call the given block after the given interval
9
- def add(interval, recurring = false, &block)
10
- Timer.new(self, interval, recurring, block)
11
- end
12
-
13
- # Wait for the next timer and fire it
14
- def wait
15
- return if @timers.empty?
16
-
17
- interval = wait_interval
18
- sleep interval if interval >= Timer::QUANTUM
19
- fire
20
- end
21
-
22
- # Interval to wait until when the next timer will fire
23
- def wait_interval
24
- @timers.first.time - Time.now unless empty?
25
- end
26
-
27
- # Fire all timers that are ready
28
- def fire
29
- return if @timers.empty?
30
-
31
- time = Time.now + Timer::QUANTUM
32
- while not empty? and time > @timers.first.time
33
- timer = @timers.shift
34
- timer.call
35
- end
36
- end
37
-
38
- # Insert a timer into the active timers
39
- def insert(timer)
40
- @timers.insert(index(timer), timer)
41
- end
42
-
43
- # Remove a given timer from the set we're monitoring
44
- def cancel(timer)
45
- @timers.delete timer
46
- end
47
-
48
- # Are there any timers pending?
49
- def empty?
50
- @timers.empty?
51
- end
52
-
53
- # Index where a timer would be located in the sorted timers array
54
- def index(timer)
55
- l, r = 0, @timers.size - 1
56
-
57
- while l <= r
58
- m = (r + l) / 2
59
- if timer < @timers.at(m)
60
- r = m - 1
61
- else
62
- l = m + 1
63
- end
64
- end
65
- l
66
- end
67
- end
68
-
69
- # An individual timer set to fire a given proc at a given time
70
- class Timer
71
- include Comparable
72
-
73
- # The timer system is guaranteed (at least by the specs) to be this precise
74
- # during normal operation. Long blocking calls within actors will delay the
75
- # firing of timers
76
- QUANTUM = 0.02
77
-
78
- attr_reader :interval, :time, :recurring
79
-
80
- def initialize(timers, interval, recurring, block)
81
- @timers, @interval, @recurring = timers, interval, recurring
82
- @block = block
83
-
84
- reset
85
- end
86
-
87
- def <=>(other)
88
- @time <=> other.time
89
- end
90
-
91
- # Cancel this timer
92
- def cancel
93
- @timers.cancel self
94
- end
95
-
96
- # Reset this timer
97
- def reset
98
- @timers.cancel self if defined?(@time)
99
- @time = Time.now + @interval
100
- @timers.insert self
101
- end
102
-
103
- # Fire the block
104
- def fire
105
- reset if recurring
106
- @block.call
107
- end
108
- alias_method :call, :fire
109
- end
110
- end