rubinius-actor 0.0.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.
@@ -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
+