celluloid 0.11.0 → 0.11.1

Sign up to get free protection for your applications and to get access to all the features.
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