rubinius-actor 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ An implementation of the Actor concurrency model, from the Rubinius project.
2
+
3
+ Depends on Rubinius's core API class "Channel", built into Rubinius and
4
+ available for JRuby via the rubinius-core-api gem.
@@ -0,0 +1,427 @@
1
+ # actor.rb - implementation of the actor model
2
+ #
3
+ # Copyright 2007-2008 MenTaLguY <mental@rydia.net>
4
+ # 2007-2011 Evan Phoenix <evan@fallingsnow.net>
5
+ #
6
+ # All rights reserved.
7
+ #
8
+ # Redistribution and use in source and binary forms, with or without
9
+ # modification, are permitted provided that the following conditions are met:
10
+ #
11
+ # * Redistributions of source code must retain the above copyright notice,
12
+ # thi slist of conditions and the following disclaimer.
13
+ # * Redistributions in binary form must reproduce the above copyright notice
14
+ # this list of conditions and the following disclaimer in the documentatio
15
+ # and/or other materials provided with the distribution.
16
+ # * Neither the name of the Evan Phoenix nor the names of its contributors
17
+ # may be used to endorse or promote products derived from this software
18
+ # without specific prior written permission.
19
+ #
20
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
24
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30
+ # POSSIBILITY OF SUCH DAMAGE.
31
+
32
+ require 'rubinius/core-api'
33
+
34
+ class Rubinius::Actor
35
+ class DeadActorError < RuntimeError
36
+ attr_reader :actor
37
+ attr_reader :reason
38
+ def initialize(actor, reason)
39
+ super(reason)
40
+ @actor = actor
41
+ @reason = reason
42
+ end
43
+ end
44
+
45
+ ANY = Object.new
46
+ def ANY.===(other)
47
+ true
48
+ end
49
+
50
+ class << self
51
+ alias_method :private_new, :new
52
+ private :private_new
53
+
54
+ @@registered_lock = Rubinius::Channel.new
55
+ @@registered = {}
56
+ @@registered_lock << nil
57
+
58
+ def current
59
+ Thread.current[:__current_actor__] ||= private_new
60
+ end
61
+
62
+ # Spawn a new Actor that will run in its own thread
63
+ def spawn(*args, &block)
64
+ raise ArgumentError, "no block given" unless block
65
+ spawned = Rubinius::Channel.new
66
+ Thread.new do
67
+ private_new do |actor|
68
+ Thread.current[:__current_actor__] = actor
69
+ spawned << actor
70
+ block.call *args
71
+ end
72
+ end
73
+ spawned.receive
74
+ end
75
+ alias_method :new, :spawn
76
+
77
+ # Atomically spawn an actor and link it to the current actor
78
+ def spawn_link(*args, &block)
79
+ current = self.current
80
+ link_complete = Rubinius::Channel.new
81
+ spawn do
82
+ begin
83
+ Actor.link(current)
84
+ ensure
85
+ link_complete << Actor.current
86
+ end
87
+ block.call *args
88
+ end
89
+ link_complete.receive
90
+ end
91
+
92
+ # Polls for exit notifications
93
+ def check_for_interrupt
94
+ current._check_for_interrupt
95
+ self
96
+ end
97
+
98
+ # Waits until a matching message is received in the current actor's
99
+ # mailbox, and executes the appropriate action. May be interrupted by
100
+ # exit notifications.
101
+ def receive #:yields: filter
102
+ filter = Filter.new
103
+ if block_given?
104
+ yield filter
105
+ else
106
+ filter.when(ANY) { |m| m }
107
+ end
108
+ current._receive(filter)
109
+ end
110
+
111
+ # Send a "fake" exit notification to another actor, as if the current
112
+ # actor had exited with +reason+
113
+ def send_exit(recipient, reason)
114
+ recipient.notify_exited(current, reason)
115
+ self
116
+ end
117
+
118
+ # Link the current Actor to another one.
119
+ def link(actor)
120
+ current = self.current
121
+ current.notify_link actor
122
+ actor.notify_link current
123
+ self
124
+ end
125
+
126
+ # Unlink the current Actor from another one
127
+ def unlink(actor)
128
+ current = self.current
129
+ current.notify_unlink actor
130
+ actor.notify_unlink current
131
+ self
132
+ end
133
+
134
+ # Actors trapping exit do not die when an error occurs in an Actor they
135
+ # are linked to. Instead the exit message is sent to their regular
136
+ # mailbox in the form [:exit, actor, reason]. This allows certain
137
+ # Actors to supervise sets of others and restart them in the event
138
+ # of an error. Setting the trap flag may be interrupted by pending
139
+ # exit notifications.
140
+ #
141
+ def trap_exit=(value)
142
+ current._trap_exit = value
143
+ self
144
+ end
145
+
146
+ # Is the Actor trapping exit?
147
+ def trap_exit
148
+ current._trap_exit
149
+ end
150
+ alias_method :trap_exit?, :trap_exit
151
+
152
+ # Lookup a locally named service
153
+ def lookup(name)
154
+ raise ArgumentError, "name must be a symbol" unless Symbol === name
155
+ @@registered_lock.receive
156
+ begin
157
+ @@registered[name]
158
+ ensure
159
+ @@registered_lock << nil
160
+ end
161
+ end
162
+ alias_method :[], :lookup
163
+
164
+ # Register an Actor locally as a named service
165
+ def register(name, actor)
166
+ raise ArgumentError, "name must be a symbol" unless Symbol === name
167
+ unless actor.nil? or actor.is_a?(Actor)
168
+ raise ArgumentError, "only actors may be registered"
169
+ end
170
+
171
+ @@registered_lock.receive
172
+ begin
173
+ if actor.nil?
174
+ @@registered.delete(name)
175
+ else
176
+ @@registered[name] = actor
177
+ end
178
+ ensure
179
+ @@registered_lock << nil
180
+ end
181
+ end
182
+ alias_method :[]=, :register
183
+
184
+ def _unregister(actor) #:nodoc:
185
+ @@registered_lock.receive
186
+ begin
187
+ @@registered.delete_if { |n, a| actor.equal? a }
188
+ ensure
189
+ @@registered_lock << nil
190
+ end
191
+ end
192
+ end
193
+
194
+ def initialize
195
+ @lock = Rubinius::Channel.new
196
+
197
+ @filter = nil
198
+ @ready = Rubinius::Channel.new
199
+ @action = nil
200
+ @message = nil
201
+
202
+ @mailbox = []
203
+ @interrupts = []
204
+ @links = []
205
+ @alive = true
206
+ @exit_reason = nil
207
+ @trap_exit = false
208
+ @thread = Thread.current
209
+
210
+ @lock << nil
211
+
212
+ if block_given?
213
+ watchdog { yield self }
214
+ else
215
+ Thread.new { watchdog { @thread.join } }
216
+ end
217
+ end
218
+
219
+ def send(message)
220
+ @lock.receive
221
+ begin
222
+ return self unless @alive
223
+ if @filter
224
+ @action = @filter.action_for(message)
225
+ if @action
226
+ @filter = nil
227
+ @message = message
228
+ @ready << nil
229
+ else
230
+ @mailbox << message
231
+ end
232
+ else
233
+ @mailbox << message
234
+ end
235
+ ensure
236
+ @lock << nil
237
+ end
238
+ self
239
+ end
240
+ alias_method :<<, :send
241
+
242
+ def _check_for_interrupt #:nodoc:
243
+ check_thread
244
+ @lock.receive
245
+ begin
246
+ raise @interrupts.shift unless @interrupts.empty?
247
+ ensure
248
+ @lock << nil
249
+ end
250
+ end
251
+
252
+ def _receive(filter) #:nodoc:
253
+ check_thread
254
+
255
+ action = nil
256
+ message = nil
257
+ timed_out = false
258
+
259
+ @lock.receive
260
+ begin
261
+ raise @interrupts.shift unless @interrupts.empty?
262
+
263
+ for i in 0...(@mailbox.size)
264
+ message = @mailbox[i]
265
+ action = filter.action_for(message)
266
+ if action
267
+ @mailbox.delete_at(i)
268
+ break
269
+ end
270
+ end
271
+
272
+ unless action
273
+ @filter = filter
274
+ @lock << nil
275
+ begin
276
+ if filter.timeout?
277
+ timed_out = @ready.receive_timeout(filter.timeout) == false
278
+ else
279
+ @ready.receive
280
+ end
281
+ ensure
282
+ @lock.receive
283
+ end
284
+
285
+ if !timed_out and @interrupts.empty?
286
+ action = @action
287
+ message = @message
288
+ else
289
+ @mailbox << @message if @action
290
+ end
291
+
292
+ @action = nil
293
+ @message = nil
294
+
295
+ raise @interrupts.shift unless @interrupts.empty?
296
+ end
297
+ ensure
298
+ @lock << nil
299
+ end
300
+
301
+ if timed_out
302
+ filter.timeout_action.call
303
+ else
304
+ action.call message
305
+ end
306
+ end
307
+
308
+ # Notify this actor that it's now linked to the given one; this is not
309
+ # intended to be used directly except by actor implementations. Most
310
+ # users will want to use Actor.link instead.
311
+ #
312
+ def notify_link(actor)
313
+ @lock.receive
314
+ alive = nil
315
+ exit_reason = nil
316
+ begin
317
+ alive = @alive
318
+ exit_reason = @exit_reason
319
+ @links << actor if alive and not @links.include? actor
320
+ ensure
321
+ @lock << nil
322
+ end
323
+ actor.notify_exited(self, exit_reason) unless alive
324
+ self
325
+ end
326
+
327
+ # Notify this actor that it's now unlinked from the given one; this is
328
+ # not intended to be used directly except by actor implementations. Most
329
+ # users will want to use Actor.unlink instead.
330
+ #
331
+ def notify_unlink(actor)
332
+ @lock.receive
333
+ begin
334
+ return self unless @alive
335
+ @links.delete(actor)
336
+ ensure
337
+ @lock << nil
338
+ end
339
+ self
340
+ end
341
+
342
+ # Notify this actor that one of the Actors it's linked to has exited;
343
+ # this is not intended to be used directly except by actor implementations.
344
+ # Most users will want to use Actor.send_exit instead.
345
+ #
346
+ def notify_exited(actor, reason)
347
+ to_send = nil
348
+ @lock.receive
349
+ begin
350
+ return self unless @alive
351
+ @links.delete(actor)
352
+ ex = DeadActorError.new(actor, reason)
353
+ if @trap_exit
354
+ to_send = ex
355
+ elsif reason
356
+ @interrupts << ex
357
+ if @filter
358
+ @filter = nil
359
+ @ready << nil
360
+ end
361
+ end
362
+ ensure
363
+ @lock << nil
364
+ end
365
+ send to_send if to_send
366
+ self
367
+ end
368
+
369
+ def watchdog
370
+ reason = nil
371
+ begin
372
+ yield
373
+ rescue Exception => reason
374
+ ensure
375
+ links = nil
376
+ Actor._unregister(self)
377
+ @lock.receive
378
+ begin
379
+ @alive = false
380
+ @mailbox = nil
381
+ @interrupts = nil
382
+ @exit_reason = reason
383
+ links = @links
384
+ @links = nil
385
+ ensure
386
+ @lock << nil
387
+ end
388
+ links.each do |actor|
389
+ begin
390
+ actor.notify_exited(self, reason)
391
+ rescue Exception
392
+ end
393
+ end
394
+ end
395
+ end
396
+ private :watchdog
397
+
398
+ def check_thread
399
+ unless Thread.current == @thread
400
+ raise ThreadError, "illegal cross-actor call"
401
+ end
402
+ end
403
+ private :check_thread
404
+
405
+ def _trap_exit=(value) #:nodoc:
406
+ check_thread
407
+ @lock.receive
408
+ begin
409
+ raise @interrupts.shift unless @interrupts.empty?
410
+ @trap_exit = !!value
411
+ ensure
412
+ @lock << nil
413
+ end
414
+ end
415
+
416
+ def _trap_exit #:nodoc:
417
+ check_thread
418
+ @lock.receive
419
+ begin
420
+ @trap_exit
421
+ ensure
422
+ @lock << nil
423
+ end
424
+ end
425
+ end
426
+
427
+ require 'rubinius/actor/filter'
@@ -0,0 +1,69 @@
1
+ # actor/filter.rb - actor message filters
2
+ #
3
+ # Copyright 2007-2008 MenTaLguY <mental@rydia.net>
4
+ # 2007-2011 Evan Phoenix <evan@fallingsnow.net>
5
+ #
6
+ # All rights reserved.
7
+ #
8
+ # Redistribution and use in source and binary forms, with or without
9
+ # modification, are permitted provided that the following conditions are met:
10
+ #
11
+ # * Redistributions of source code must retain the above copyright notice,
12
+ # thi slist of conditions and the following disclaimer.
13
+ # * Redistributions in binary form must reproduce the above copyright notice
14
+ # this list of conditions and the following disclaimer in the documentatio
15
+ # and/or other materials provided with the distribution.
16
+ # * Neither the name of the Evan Phoenix nor the names of its contributors
17
+ # may be used to endorse or promote products derived from this software
18
+ # without specific prior written permission.
19
+ #
20
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
24
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30
+ # POSSIBILITY OF SUCH DAMAGE.
31
+
32
+ class Rubinius::Actor
33
+ class Filter
34
+ attr_reader :timeout
35
+ attr_reader :timeout_action
36
+
37
+ def initialize
38
+ @pairs = []
39
+ @timeout = nil
40
+ @timeout_action = nil
41
+ end
42
+
43
+ def timeout?
44
+ not @timeout.nil?
45
+ end
46
+
47
+ def when(pattern, &action)
48
+ raise ArgumentError, "no block given" unless action
49
+ @pairs.push [pattern, action]
50
+ self
51
+ end
52
+
53
+ def after(seconds, &action)
54
+ raise ArgumentError, "no block given" unless action
55
+
56
+ seconds = seconds.to_f
57
+ if !@timeout or seconds < @timeout
58
+ @timeout = seconds
59
+ @timeout_action = action
60
+ end
61
+ self
62
+ end
63
+
64
+ def action_for(value)
65
+ pair = @pairs.find { |pattern, action| pattern === value }
66
+ pair ? pair.last : nil
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,15 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{rubinius-actor}
5
+ s.version = "0.0.1"
6
+ s.authors = ["Evan Phoenix", "MenTaLguY"]
7
+ s.date = Time.now
8
+ s.description = "Rubinius's Actor implementation"
9
+ s.email = ["evan@fallingsnow.net", "mental@rydia.net"]
10
+ s.files = Dir['{lib}/**/*'] + Dir['{*.txt,*.gemspec,Rakefile}']
11
+ s.homepage = "http://github.com/rubinius/rubinius-actor"
12
+ s.require_paths = ["lib"]
13
+ s.summary = "Rubinius's Actor implementation"
14
+ s.add_dependency 'rubinius-core-api'
15
+ end
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rubinius-actor
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Evan Phoenix
14
+ - MenTaLguY
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2011-06-16 00:00:00 Z
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rubinius-core-api
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ description: Rubinius's Actor implementation
36
+ email:
37
+ - evan@fallingsnow.net
38
+ - mental@rydia.net
39
+ executables: []
40
+
41
+ extensions: []
42
+
43
+ extra_rdoc_files: []
44
+
45
+ files:
46
+ - lib/rubinius/actor/filter.rb
47
+ - lib/rubinius/actor.rb
48
+ - README.txt
49
+ - rubinius-actor.gemspec
50
+ homepage: http://github.com/rubinius/rubinius-actor
51
+ licenses: []
52
+
53
+ post_install_message:
54
+ rdoc_options: []
55
+
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ hash: 3
64
+ segments:
65
+ - 0
66
+ version: "0"
67
+ required_rubygems_version: !ruby/object:Gem::Requirement
68
+ none: false
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ hash: 3
73
+ segments:
74
+ - 0
75
+ version: "0"
76
+ requirements: []
77
+
78
+ rubyforge_project:
79
+ rubygems_version: 1.8.5
80
+ signing_key:
81
+ specification_version: 3
82
+ summary: Rubinius's Actor implementation
83
+ test_files: []
84
+