kulesa-celluloid 0.10.2

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.
@@ -0,0 +1,78 @@
1
+ module Celluloid
2
+ # Manages a fixed-size pool of workers
3
+ module Worker
4
+ def self.included(klass)
5
+ klass.send :include, Celluloid
6
+ klass.send :extend, ClassMethods
7
+ end
8
+
9
+ # Class methods added to classes which include Celluloid::Worker
10
+ module ClassMethods
11
+ # Create a new pool of workers. Accepts the following options:
12
+ #
13
+ # * size: how many workers to create. Default is worker per CPU core
14
+ # * args: array of arguments to pass when creating a worker
15
+ #
16
+ def pool(options = {})
17
+ Manager.new(self, options)
18
+ end
19
+ end
20
+
21
+ # Delegates work (i.e. methods) and supervises workers
22
+ class Manager
23
+ include Celluloid
24
+ trap_exit :crash_handler
25
+
26
+ def initialize(worker_class, options = {})
27
+ @size = options[:size]
28
+ raise ArgumentError, "minimum pool size is 2" if @size && @size < 2
29
+
30
+ @size ||= [Celluloid.cores, 2].max
31
+ @args = options[:args] ? Array(options[:args]) : []
32
+
33
+ @worker_class = worker_class
34
+ @idle = @size.times.map { worker_class.new_link(*@args) }
35
+ end
36
+
37
+ # Execute the given method within a worker
38
+ def execute(method, *args, &block)
39
+ worker = provision_worker
40
+
41
+ begin
42
+ worker._send_ method, *args, &block
43
+ ensure
44
+ @idle << worker if worker.alive?
45
+ end
46
+ end
47
+
48
+ # Provision a new worker
49
+ def provision_worker
50
+ while @idle.empty?
51
+ # Using exclusive mode blocks incoming messages, so they don't pile
52
+ # up as waiting Celluloid::Tasks
53
+ response = exclusive { receive { |msg| msg.is_a? Response } }
54
+ Thread.current[:actor].handle_message(response)
55
+ end
56
+ @idle.shift
57
+ end
58
+
59
+ # Spawn a new worker for every crashed one
60
+ def crash_handler(actor, reason)
61
+ return unless reason # don't restart workers that exit cleanly
62
+ @idle << @worker_class.new_link(*@args)
63
+ end
64
+
65
+ def respond_to?(method)
66
+ super || (@worker_class ? @worker_class.instance_methods.include?(method.to_sym) : false)
67
+ end
68
+
69
+ def method_missing(method, *args, &block)
70
+ if respond_to?(method)
71
+ execute method, *args, &block
72
+ else
73
+ super
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
data/lib/celluloid.rb ADDED
@@ -0,0 +1,355 @@
1
+ require 'logger'
2
+ require 'thread'
3
+ require 'timeout'
4
+
5
+ module Celluloid
6
+ SHUTDOWN_TIMEOUT = 120 # How long actors have to terminate
7
+ @logger = Logger.new STDERR
8
+
9
+ class << self
10
+ attr_accessor :logger # Thread-safe logger class
11
+
12
+ def included(klass)
13
+ klass.send :extend, ClassMethods
14
+ end
15
+
16
+ # Are we currently inside of an actor?
17
+ def actor?
18
+ !!Thread.current[:actor]
19
+ end
20
+
21
+ # Is current actor running in exclusive mode?
22
+ def exclusive?
23
+ actor? and Thread.current[:actor].exclusive?
24
+ end
25
+
26
+ # Obtain the currently running actor (if one exists)
27
+ def current_actor
28
+ Actor.current
29
+ end
30
+
31
+ # Receive an asynchronous message
32
+ def receive(timeout = nil, &block)
33
+ actor = Thread.current[:actor]
34
+ if actor
35
+ actor.receive(timeout, &block)
36
+ else
37
+ Thread.mailbox.receive(timeout, &block)
38
+ end
39
+ end
40
+
41
+ # Sleep letting the actor continue processing messages
42
+ def sleep(interval)
43
+ actor = Thread.current[:actor]
44
+ if actor
45
+ actor.sleep(interval)
46
+ else
47
+ Kernel.sleep interval
48
+ end
49
+ end
50
+
51
+ # Generate a Universally Unique Identifier
52
+ def uuid
53
+ UUID.generate
54
+ end
55
+
56
+ # Obtain the number of CPUs in the system
57
+ def cores
58
+ CPUCounter.cores
59
+ end
60
+ alias_method :cpus, :cores
61
+ alias_method :ncpus, :cores
62
+
63
+ # Define an exception handler for actor crashes
64
+ def exception_handler(&block)
65
+ Logger.exception_handler(&block)
66
+ end
67
+
68
+ # Shut down all running actors
69
+ # FIXME: This should probably attempt a graceful shutdown of the supervision
70
+ # tree before iterating through all actors and telling them to terminate.
71
+ def shutdown
72
+ Timeout.timeout(SHUTDOWN_TIMEOUT) do
73
+ actors = Actor.all
74
+ Logger.info "Terminating #{actors.size} actors..." if actors.size > 0
75
+
76
+ # Attempt to shut down the supervision tree, if available
77
+ Supervisor.root.terminate if Supervisor.root
78
+
79
+ # Actors cannot self-terminate, you must do it for them
80
+ terminators = Actor.all.each do |actor|
81
+ begin
82
+ actor.future(:terminate)
83
+ rescue DeadActorError, MailboxError
84
+ end
85
+ end
86
+
87
+ terminators.each do |terminator|
88
+ begin
89
+ terminator.value
90
+ rescue DeadActorError, MailboxError
91
+ end
92
+ end
93
+
94
+ Logger.info "Shutdown completed cleanly"
95
+ end
96
+ end
97
+ end
98
+
99
+ # Terminate all actors at exit
100
+ at_exit { shutdown }
101
+
102
+ # Class methods added to classes which include Celluloid
103
+ module ClassMethods
104
+ # Create a new actor
105
+ def new(*args, &block)
106
+ proxy = Actor.new(allocate).proxy
107
+ proxy._send_(:initialize, *args, &block)
108
+ proxy
109
+ end
110
+ alias_method :spawn, :new
111
+
112
+ # Create a new actor and link to the current one
113
+ def new_link(*args, &block)
114
+ current_actor = Actor.current
115
+ raise NotActorError, "can't link outside actor context" unless current_actor
116
+
117
+ proxy = Actor.new(allocate).proxy
118
+ current_actor.link proxy
119
+ proxy._send_(:initialize, *args, &block)
120
+ proxy
121
+ end
122
+ alias_method :spawn_link, :new_link
123
+
124
+ # Create a supervisor which ensures an instance of an actor will restart
125
+ # an actor if it fails
126
+ def supervise(*args, &block)
127
+ Supervisor.supervise(self, *args, &block)
128
+ end
129
+
130
+ # Create a supervisor which ensures an instance of an actor will restart
131
+ # an actor if it fails, and keep the actor registered under a given name
132
+ def supervise_as(name, *args, &block)
133
+ Supervisor.supervise_as(name, self, *args, &block)
134
+ end
135
+
136
+ # Trap errors from actors we're linked to when they exit
137
+ def trap_exit(callback)
138
+ @exit_handler = callback.to_sym
139
+ end
140
+
141
+ # Obtain the exit handler for this actor
142
+ attr_reader :exit_handler
143
+
144
+ # Configure a custom mailbox factory
145
+ def use_mailbox(klass = nil, &block)
146
+ if block
147
+ @mailbox_factory = block
148
+ else
149
+ @mailbox_factory = proc { klass.new }
150
+ end
151
+ end
152
+
153
+ # Create a mailbox for this actor
154
+ def mailbox_factory
155
+ if defined?(@mailbox_factory)
156
+ @mailbox_factory.call
157
+ elsif defined?(super)
158
+ super
159
+ else
160
+ Mailbox.new
161
+ end
162
+ end
163
+
164
+ def ===(other)
165
+ other.kind_of? self
166
+ end
167
+ end
168
+
169
+ #
170
+ # Instance methods
171
+ #
172
+
173
+ # Is this actor alive?
174
+ def alive?
175
+ Thread.current[:actor].alive?
176
+ end
177
+
178
+ # Raise an exception in caller context, but stay running
179
+ def abort(cause)
180
+ raise AbortError.new(cause)
181
+ end
182
+
183
+ # Terminate this actor
184
+ def terminate
185
+ Thread.current[:actor].terminate
186
+ end
187
+
188
+ def inspect
189
+ str = "#<Celluloid::Actor(#{self.class}:0x#{object_id.to_s(16)})"
190
+ ivars = instance_variables.map do |ivar|
191
+ "#{ivar}=#{instance_variable_get(ivar).inspect}"
192
+ end
193
+
194
+ str << " " << ivars.join(' ') unless ivars.empty?
195
+ str << ">"
196
+ end
197
+
198
+ # Send a signal with the given name to all waiting methods
199
+ def signal(name, value = nil)
200
+ Thread.current[:actor].signal name, value
201
+ end
202
+
203
+ # Wait for the given signal
204
+ def wait(name)
205
+ Thread.current[:actor].wait name
206
+ end
207
+
208
+ # Obtain the current_actor
209
+ def current_actor
210
+ Actor.current
211
+ end
212
+
213
+ # Obtain the name of the current actor
214
+ def name
215
+ Actor.name
216
+ end
217
+
218
+ # Obtain the running tasks for this actor
219
+ def tasks
220
+ Thread.current[:actor].tasks.to_a
221
+ end
222
+
223
+ # Obtain the Ruby object the actor is wrapping. This should ONLY be used
224
+ # for a limited set of use cases like runtime metaprogramming. Interacting
225
+ # directly with the wrapped object foregoes any kind of thread safety that
226
+ # Celluloid would ordinarily provide you, and the object is guaranteed to
227
+ # be shared with at least the actor thread. Tread carefully.
228
+ def wrapped_object; self; end
229
+
230
+ # Obtain the Celluloid::Links for this actor
231
+ def links
232
+ Thread.current[:actor].links
233
+ end
234
+
235
+ # Link this actor to another, allowing it to crash or react to errors
236
+ def link(actor)
237
+ actor.notify_link Actor.current
238
+ notify_link actor
239
+ end
240
+
241
+ # Remove links to another actor
242
+ def unlink(actor)
243
+ actor.notify_unlink Actor.current
244
+ notify_unlink actor
245
+ end
246
+
247
+ def notify_link(actor)
248
+ links << actor
249
+ end
250
+
251
+ def notify_unlink(actor)
252
+ links.delete actor
253
+ end
254
+
255
+ # Is this actor linked to another?
256
+ def linked_to?(actor)
257
+ Thread.current[:actor].links.include? actor
258
+ end
259
+
260
+ # Receive an asynchronous message via the actor protocol
261
+ def receive(timeout = nil, &block)
262
+ Celluloid.receive(timeout, &block)
263
+ end
264
+
265
+ # Sleep while letting the actor continue to receive messages
266
+ def sleep(interval)
267
+ Celluloid.sleep(interval)
268
+ end
269
+
270
+ # Run given block in an exclusive mode: all synchronous calls block the whole
271
+ # actor, not only current message processing.
272
+ def exclusive(&block)
273
+ Thread.current[:actor].exclusive(&block)
274
+ end
275
+
276
+ # Call a block after a given interval, returning a Celluloid::Timer object
277
+ def after(interval, &block)
278
+ Thread.current[:actor].after(interval, &block)
279
+ end
280
+
281
+ # Call a block every given interval, returning a Celluloid::Timer object
282
+ def every(interval, &block)
283
+ Thread.current[:actor].every(interval, &block)
284
+ end
285
+
286
+ # Perform a blocking or computationally intensive action inside an
287
+ # asynchronous thread pool, allowing the caller to continue processing other
288
+ # messages in its mailbox in the meantime
289
+ def defer(&block)
290
+ # This implementation relies on the present implementation of
291
+ # Celluloid::Future, which uses a thread from InternalPool to run the block
292
+ Future.new(&block).value
293
+ end
294
+
295
+ # Handle async calls within an actor itself
296
+ def async(meth, *args, &block)
297
+ Actor.async Thread.current[:actor].mailbox, meth, *args, &block
298
+ end
299
+
300
+ # Handle calls to future within an actor itself
301
+ def future(meth, *args, &block)
302
+ Actor.future Thread.current[:actor].mailbox, meth, *args, &block
303
+ end
304
+
305
+ # Process async calls via method_missing
306
+ def method_missing(meth, *args, &block)
307
+ # bang methods are async calls
308
+ if meth.to_s.match(/!$/)
309
+ unbanged_meth = meth.to_s.sub(/!$/, '')
310
+ args.unshift unbanged_meth
311
+
312
+ call = AsyncCall.new(nil, :__send__, args, block)
313
+ begin
314
+ Thread.current[:actor].mailbox << call
315
+ rescue MailboxError
316
+ # Silently swallow asynchronous calls to dead actors. There's no way
317
+ # to reliably generate DeadActorErrors for async calls, so users of
318
+ # async calls should find other ways to deal with actors dying
319
+ # during an async call (i.e. linking/supervisors)
320
+ end
321
+
322
+ return
323
+ end
324
+
325
+ super
326
+ end
327
+ end
328
+
329
+ require 'celluloid/version'
330
+ require 'celluloid/actor_proxy'
331
+ require 'celluloid/calls'
332
+ require 'celluloid/core_ext'
333
+ require 'celluloid/cpu_counter'
334
+ require 'celluloid/events'
335
+ require 'celluloid/fiber'
336
+ require 'celluloid/fsm'
337
+ require 'celluloid/internal_pool'
338
+ require 'celluloid/links'
339
+ require 'celluloid/logger'
340
+ require 'celluloid/mailbox'
341
+ require 'celluloid/pool'
342
+ require 'celluloid/receivers'
343
+ require 'celluloid/registry'
344
+ require 'celluloid/responses'
345
+ require 'celluloid/signals'
346
+ require 'celluloid/task'
347
+ require 'celluloid/thread_handle'
348
+ require 'celluloid/timers'
349
+ require 'celluloid/uuid'
350
+
351
+ require 'celluloid/actor'
352
+ require 'celluloid/future'
353
+ require 'celluloid/group'
354
+ require 'celluloid/supervisor'
355
+ require 'celluloid/worker'