dramatis 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.
- data/History.txt +7 -0
- data/License.txt +20 -0
- data/Manifest.txt +119 -0
- data/README.txt +57 -0
- data/Rakefile +4 -0
- data/config/hoe.rb +70 -0
- data/config/requirements.rb +17 -0
- data/examples/README.txt +20 -0
- data/examples/auction.rb +90 -0
- data/examples/bank/bank.rb +7 -0
- data/examples/bank/bank_test.rb +7 -0
- data/examples/exception.rb +40 -0
- data/examples/fib/conservative.rb +50 -0
- data/examples/fib/future.rb +5 -0
- data/examples/fib/original.rb +33 -0
- data/examples/fib/threads.rb +51 -0
- data/examples/im/distributed/chat/client.rb +49 -0
- data/examples/im/distributed/chat/screen/fox.rb +92 -0
- data/examples/im/distributed/chat/screen.rb +11 -0
- data/examples/im/distributed/chat/server.rb +72 -0
- data/examples/im/distributed/chat.rb +5 -0
- data/examples/im/distributed/client.rb +9 -0
- data/examples/im/distributed/run.rb +18 -0
- data/examples/im/distributed/server.rb +11 -0
- data/examples/im/single/chat/client.rb +50 -0
- data/examples/im/single/chat/screen/fox.rb +96 -0
- data/examples/im/single/chat/screen/wxs.rb +63 -0
- data/examples/im/single/chat/screen.rb +11 -0
- data/examples/im/single/chat/server.rb +72 -0
- data/examples/im/single/chat.rb +5 -0
- data/examples/im/single/fox.rb +18 -0
- data/examples/im/single/wxchat.rb +19 -0
- data/examples/pingpong/actor.rb +33 -0
- data/examples/pingpong/actor_rec.rb +34 -0
- data/examples/pingpong/pingpong.txt +315 -0
- data/examples/pingpong/scala.rb +41 -0
- data/examples/pingpong/serial.rb +26 -0
- data/examples/pretty.txt +108 -0
- data/examples/telephone/.irbrc +2 -0
- data/examples/telephone/3esl.txt +21877 -0
- data/examples/telephone/fifth/kid.rb +36 -0
- data/examples/telephone/fifth/run.rb +26 -0
- data/examples/telephone/first/kid.rb +31 -0
- data/examples/telephone/first/run.rb +20 -0
- data/examples/telephone/fourth/kid.rb +31 -0
- data/examples/telephone/fourth/run.rb +26 -0
- data/examples/telephone/mangler.rb +53 -0
- data/examples/telephone/second/kid.rb +26 -0
- data/examples/telephone/second/run.rb +20 -0
- data/examples/telephone/seventh/kid.rb +40 -0
- data/examples/telephone/seventh/run.rb +35 -0
- data/examples/telephone/seventh/test.rb +28 -0
- data/examples/telephone/seventh/test2.rb +10 -0
- data/examples/telephone/sixth/kid.rb +39 -0
- data/examples/telephone/sixth/run.rb +26 -0
- data/examples/telephone/third/kid.rb +31 -0
- data/examples/telephone/third/run.rb +21 -0
- data/lib/dramatis/actor/interface.rb +118 -0
- data/lib/dramatis/actor/name/interface.rb +128 -0
- data/lib/dramatis/actor/name.rb +44 -0
- data/lib/dramatis/actor.rb +96 -0
- data/lib/dramatis/deadlock.rb +123 -0
- data/lib/dramatis/error/uncaught.rb +19 -0
- data/lib/dramatis/error.rb +125 -0
- data/lib/dramatis/future/interface.rb +45 -0
- data/lib/dramatis/future.rb +32 -0
- data/lib/dramatis/runtime/actor/main.rb +3 -0
- data/lib/dramatis/runtime/actor.rb +294 -0
- data/lib/dramatis/runtime/gate.rb +244 -0
- data/lib/dramatis/runtime/scheduler.rb +374 -0
- data/lib/dramatis/runtime/task.rb +390 -0
- data/lib/dramatis/runtime/thread_pool.rb +149 -0
- data/lib/dramatis/runtime/timer.rb +5 -0
- data/lib/dramatis/runtime.rb +129 -0
- data/lib/dramatis/shoes/runtime.rb +7 -0
- data/lib/dramatis/shoes.rb +14 -0
- data/lib/dramatis/version.rb +8 -0
- data/lib/dramatis.rb +73 -0
- data/log/debug.log +0 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/script/txt2html +74 -0
- data/setup.rb +1585 -0
- data/spec/dramatis/actor/become_spec.rb +17 -0
- data/spec/dramatis/actor/future_spec.rb +189 -0
- data/spec/dramatis/actor/name_spec.rb +141 -0
- data/spec/dramatis/actor/task_spec.rb +75 -0
- data/spec/dramatis/actor_spec.rb +492 -0
- data/spec/dramatis/dramatis_spec.rb +23 -0
- data/spec/dramatis/exc_spec.rb +78 -0
- data/spec/dramatis/runtime/gate_spec.rb +57 -0
- data/spec/dramatis/runtime/thread_pool.rb +30 -0
- data/spec/dramatis/shoes_spec.rb +11 -0
- data/spec/dramatis/simple_spec.rb +32 -0
- data/spec/exp_spec.rb +21 -0
- data/spec/simple2_spec.rb +36 -0
- data/spec/simple_spec.rb +30 -0
- data/spec/spec.opts +0 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/thread_spec.rb +13 -0
- data/tasks/deployment.rake +34 -0
- data/tasks/environment.rake +7 -0
- data/tasks/rspec.rake +21 -0
- data/tasks/website.rake +17 -0
- data/test/jruby_lm.rb +13 -0
- data/test/test.rb +19 -0
- data/test/test10.rb +43 -0
- data/test/test11.rb +45 -0
- data/test/test12.rb +60 -0
- data/test/test13.rb +71 -0
- data/test/test2.rb +12 -0
- data/test/test3.rb +10 -0
- data/test/test4.rb +29 -0
- data/test/test5.rb +8 -0
- data/test/test6.rb +32 -0
- data/test/test7.rb +48 -0
- data/test/test8.rb +133 -0
- data/test/test9.rb +105 -0
- data/test/test_exc.rb +22 -0
- metadata +180 -0
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
module Dramatis; end
|
|
2
|
+
class Dramatis::Runtime; end
|
|
3
|
+
|
|
4
|
+
require 'dramatis/runtime/scheduler'
|
|
5
|
+
require 'dramatis/future'
|
|
6
|
+
require 'thread'
|
|
7
|
+
|
|
8
|
+
class Dramatis::Runtime::Task #:nodoc: all
|
|
9
|
+
|
|
10
|
+
attr_reader :actor
|
|
11
|
+
|
|
12
|
+
def type
|
|
13
|
+
@dest
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def method
|
|
17
|
+
@args[0]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def arguments
|
|
21
|
+
@args[1,@args.length]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
attr_reader :call_thread
|
|
25
|
+
|
|
26
|
+
def initialize actor, dest, args, options
|
|
27
|
+
@actor = actor
|
|
28
|
+
@dest = dest
|
|
29
|
+
@args = args.dup
|
|
30
|
+
|
|
31
|
+
@call_thread = nil
|
|
32
|
+
|
|
33
|
+
name = Dramatis::Runtime::Scheduler.actor
|
|
34
|
+
actor = name.instance_eval { @actor }
|
|
35
|
+
|
|
36
|
+
object_id = actor.object.object_id
|
|
37
|
+
|
|
38
|
+
@args.each_with_index do |arg,i|
|
|
39
|
+
if arg.object_id == object_id
|
|
40
|
+
@args[i] = name
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
if actor.call_threading?
|
|
45
|
+
# warn "oct #{options[:call_thread]} act #{actor.call_thread}"
|
|
46
|
+
raise "hell" if options[:call_thread] and
|
|
47
|
+
actor.call_thread and
|
|
48
|
+
options[:call_thread] != actor.call_thread
|
|
49
|
+
@call_thread = actor.call_thread
|
|
50
|
+
if @call_thread == nil
|
|
51
|
+
@call_thread = self.to_s
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# warn "task #{self} #{args[0]} call thread [ #{@call_thread} ] #{options.to_a.join(' ')}"
|
|
56
|
+
|
|
57
|
+
case options[:continuation]
|
|
58
|
+
when :none
|
|
59
|
+
@continuation = Continuation::None.new name, @call_thread
|
|
60
|
+
when :rpc
|
|
61
|
+
@continuation = Continuation::RPC.new name, @call_thread, options[:nonblocking]
|
|
62
|
+
when :future
|
|
63
|
+
@continuation = Continuation::Future.new name, @call_thread
|
|
64
|
+
when Proc
|
|
65
|
+
@continuation = Continuation::Proc.new name, @call_thread, options[:continuation],
|
|
66
|
+
options[:exception]
|
|
67
|
+
else
|
|
68
|
+
raise Dramatis::Internal.new( "invalid contiunation type" )
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def exception e
|
|
73
|
+
@continuation.exception e
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def queued
|
|
77
|
+
@continuation.queued
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def deliver
|
|
81
|
+
@actor.deliver @dest, @args, @continuation, @call_thread
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
module Continuation
|
|
87
|
+
|
|
88
|
+
class None
|
|
89
|
+
|
|
90
|
+
include Dramatis
|
|
91
|
+
|
|
92
|
+
def queued
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def result result
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def exception exception
|
|
99
|
+
# warn "except nil #{exception}"
|
|
100
|
+
# true and pp exception.backtrace
|
|
101
|
+
interface( release( @name ) ).exception exception
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def initialize name, call_thread
|
|
105
|
+
@name = name
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
class RPC
|
|
110
|
+
|
|
111
|
+
include Dramatis
|
|
112
|
+
|
|
113
|
+
def actor
|
|
114
|
+
@actor.instance_eval { @actor }
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def initialize name, call_thread, nonblocking
|
|
118
|
+
# all the synchronizaton here probably gets tossed
|
|
119
|
+
# proof:
|
|
120
|
+
# to create the continuation, you have to have the actor lock
|
|
121
|
+
# to deliver the continuation you have to have the actor lock
|
|
122
|
+
# QED
|
|
123
|
+
# when this wasn't a message send, you could try to execute before
|
|
124
|
+
# but now that's not possible; it'll get queued
|
|
125
|
+
# i think
|
|
126
|
+
# of course, it should be harmless
|
|
127
|
+
# fix ... might want to seperate the two parts of a continuation
|
|
128
|
+
# the value part and the thread state part
|
|
129
|
+
@blocking = !nonblocking
|
|
130
|
+
@state = :start
|
|
131
|
+
@mutex = Mutex.new
|
|
132
|
+
@wait = ConditionVariable.new
|
|
133
|
+
@call_thread = call_thread
|
|
134
|
+
# warn "contiunation to #{actor}"
|
|
135
|
+
@actor = interface( Dramatis::Runtime::Scheduler.actor ).send :continuation, self, :call_thread => call_thread
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def queued
|
|
139
|
+
|
|
140
|
+
@mutex.synchronize do
|
|
141
|
+
raise "hell " + @state.to_s if @state != :start and @state != :signaled
|
|
142
|
+
if @state == :start
|
|
143
|
+
@state = :waiting
|
|
144
|
+
begin
|
|
145
|
+
tag = to_s
|
|
146
|
+
call_thread = @call_thread
|
|
147
|
+
blocking = @blocking
|
|
148
|
+
@actor.instance_eval do
|
|
149
|
+
@actor.instance_eval do
|
|
150
|
+
# warn "#{self} ct [ #{call_thread} ]"
|
|
151
|
+
@call_thread = call_thread
|
|
152
|
+
end
|
|
153
|
+
if blocking
|
|
154
|
+
@actor.gate.only [ :continuation, tag ], :tag => tag
|
|
155
|
+
end
|
|
156
|
+
@actor.schedule self
|
|
157
|
+
end
|
|
158
|
+
begin
|
|
159
|
+
Dramatis::Runtime::Scheduler.current.suspend_notification self
|
|
160
|
+
@wait.wait @mutex
|
|
161
|
+
# this causes a deadlock if the waking thread, which may be
|
|
162
|
+
# retiring, does so before this thead has awakend and notified
|
|
163
|
+
# the scheduler
|
|
164
|
+
# sleep 1
|
|
165
|
+
ensure
|
|
166
|
+
# Dramatis::Runtime::Scheduler.current.wakeup_notification self
|
|
167
|
+
end
|
|
168
|
+
ensure
|
|
169
|
+
tag = to_s
|
|
170
|
+
@actor.instance_eval do
|
|
171
|
+
@actor.gate.default_by_tag tag
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
raise "hell" if @state != :done
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
raise "hell " + @type.inspect if ![ :return, :exception ].include? @type
|
|
179
|
+
case @type
|
|
180
|
+
when :return
|
|
181
|
+
return @value
|
|
182
|
+
when :exception
|
|
183
|
+
# if Dramatis::Deadlock === @value
|
|
184
|
+
# @value = Dramatis::Deadlock.new nil, :next => @value
|
|
185
|
+
# end
|
|
186
|
+
# pp "reraise", caller
|
|
187
|
+
@value._dramatis_reraise
|
|
188
|
+
raise @value
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def result result
|
|
193
|
+
@actor.result result
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def exception exception
|
|
197
|
+
# warn "4 exception " + exception.to_s
|
|
198
|
+
# warn "4 exception " + exception.backtrace.join("\n")
|
|
199
|
+
@actor.exception exception
|
|
200
|
+
# warn "4 delivered ".to_s
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def continuation_result result
|
|
204
|
+
@mutex.synchronize do
|
|
205
|
+
raise "hell" if @state != :start and @state != :waiting
|
|
206
|
+
@type = :return
|
|
207
|
+
@value = result
|
|
208
|
+
if @state == :start
|
|
209
|
+
@state = :signaled
|
|
210
|
+
else
|
|
211
|
+
@state = :done
|
|
212
|
+
Dramatis::Runtime::Scheduler.current.wakeup_notification self
|
|
213
|
+
@wait.signal
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def continuation_exception exception
|
|
219
|
+
# warn "except rpc"
|
|
220
|
+
@mutex.synchronize do
|
|
221
|
+
raise "hell" if @state != :start and @state != :waiting
|
|
222
|
+
@type = :exception
|
|
223
|
+
@value = exception
|
|
224
|
+
if @state == :start
|
|
225
|
+
@state = :signaled
|
|
226
|
+
else
|
|
227
|
+
@state = :done
|
|
228
|
+
Dramatis::Runtime::Scheduler.current.wakeup_notification self
|
|
229
|
+
@wait.signal
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
class Proc
|
|
237
|
+
|
|
238
|
+
include Dramatis
|
|
239
|
+
|
|
240
|
+
def initialize name, call_thread, result, except
|
|
241
|
+
# p "p.n #{call_thread} #{result} #{except}"
|
|
242
|
+
@result_block = result
|
|
243
|
+
@exception_block = except
|
|
244
|
+
@name = name
|
|
245
|
+
@continuation = \
|
|
246
|
+
interface( Dramatis::Runtime::Scheduler.actor ) \
|
|
247
|
+
.send :continuation, self, :call_thread => call_thread
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def queued; end
|
|
251
|
+
|
|
252
|
+
def result result
|
|
253
|
+
@continuation.result result
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def exception exception
|
|
257
|
+
@continuation.exception exception
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def continuation_result result
|
|
261
|
+
@result_block.call result
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def continuation_exception exception
|
|
265
|
+
# warn "delivering #{exception} => #{@exception_block}"
|
|
266
|
+
# pp exception.backtrace
|
|
267
|
+
if @exception_block
|
|
268
|
+
@exception_block.call exception
|
|
269
|
+
else
|
|
270
|
+
release( @name ).dramatis_exception exception
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
class Future
|
|
277
|
+
|
|
278
|
+
include Dramatis
|
|
279
|
+
|
|
280
|
+
def initialize name, call_thread
|
|
281
|
+
@state = :start
|
|
282
|
+
@mutex = Mutex.new
|
|
283
|
+
@wait = ConditionVariable.new
|
|
284
|
+
@call_thread = call_thread
|
|
285
|
+
# warn "contiunation to #{actor}"
|
|
286
|
+
@actor = interface( Dramatis::Runtime::Scheduler.actor ) \
|
|
287
|
+
.send :continuation, self, :call_thread => call_thread
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def ready?
|
|
291
|
+
@mutex.synchronize { @state == :done or @state == :signaled }
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def value
|
|
295
|
+
@mutex.synchronize do
|
|
296
|
+
if @state == :start
|
|
297
|
+
@state = :waiting
|
|
298
|
+
begin
|
|
299
|
+
tag = to_s
|
|
300
|
+
call_thread = @call_thread
|
|
301
|
+
@actor.instance_eval do
|
|
302
|
+
@actor.instance_eval do
|
|
303
|
+
@call_thread = call_thread
|
|
304
|
+
end
|
|
305
|
+
@actor.gate.only [ :continuation, tag ], :tag => tag
|
|
306
|
+
@actor.schedule self
|
|
307
|
+
end
|
|
308
|
+
begin
|
|
309
|
+
Dramatis::Runtime::Scheduler.current.suspend_notification self
|
|
310
|
+
@wait.wait @mutex
|
|
311
|
+
# this causes a deadlock if the waking thread, which may be
|
|
312
|
+
# retiring, does so before this thead has awakend and notified
|
|
313
|
+
# the scheduler
|
|
314
|
+
# sleep 1
|
|
315
|
+
ensure
|
|
316
|
+
# Dramatis::Runtime::Scheduler.current.wakeup_notification self
|
|
317
|
+
end
|
|
318
|
+
ensure
|
|
319
|
+
tag = to_s
|
|
320
|
+
@actor.instance_eval do
|
|
321
|
+
@actor.gate.default_by_tag tag
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
raise "hell" if @state != :done
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
raise "hell " + @type.inspect if ![ :return, :exception ].include? @type
|
|
329
|
+
case @type
|
|
330
|
+
when :return
|
|
331
|
+
return @value
|
|
332
|
+
when :exception
|
|
333
|
+
begin
|
|
334
|
+
# raise "hell for #{@value}"
|
|
335
|
+
rescue Exception => e
|
|
336
|
+
pp "#{e}", e.backtrace
|
|
337
|
+
end
|
|
338
|
+
raise @value
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
def queued
|
|
343
|
+
Dramatis::Future.new( self )
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
def result result
|
|
347
|
+
@actor.result result
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
def exception exception
|
|
351
|
+
# warn "4 exception " + exception.to_s
|
|
352
|
+
# warn "4 exception " + exception.backtrace.join("\n")
|
|
353
|
+
@actor.exception exception
|
|
354
|
+
# warn "4 delivered ".to_s
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
def continuation_result result
|
|
358
|
+
@mutex.synchronize do
|
|
359
|
+
raise "hell" if @state != :start and @state != :waiting
|
|
360
|
+
@type = :return
|
|
361
|
+
@value = result
|
|
362
|
+
if @state == :start
|
|
363
|
+
@state = :signaled
|
|
364
|
+
else
|
|
365
|
+
@state = :done
|
|
366
|
+
Dramatis::Runtime::Scheduler.current.wakeup_notification self
|
|
367
|
+
@wait.signal
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
def continuation_exception exception
|
|
373
|
+
# warn "except rpc"
|
|
374
|
+
@mutex.synchronize do
|
|
375
|
+
raise "hell" if @state != :start and @state != :waiting
|
|
376
|
+
@type = :exception
|
|
377
|
+
@value = exception
|
|
378
|
+
if @state == :start
|
|
379
|
+
@state = :signaled
|
|
380
|
+
else
|
|
381
|
+
@state = :done
|
|
382
|
+
Dramatis::Runtime::Scheduler.current.wakeup_notification self
|
|
383
|
+
@wait.signal
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
end
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
module Dramatis; end
|
|
2
|
+
class Dramatis::Runtime; end
|
|
3
|
+
|
|
4
|
+
class Dramatis::Runtime::ThreadPool; end
|
|
5
|
+
class Dramatis::Runtime::ThreadPool::PoolThread < Thread; end
|
|
6
|
+
|
|
7
|
+
class Dramatis::Runtime::ThreadPool #:nodoc: all
|
|
8
|
+
|
|
9
|
+
def reset
|
|
10
|
+
shutdown
|
|
11
|
+
@state = :running
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def initialize
|
|
15
|
+
super
|
|
16
|
+
@mutex = Mutex.new
|
|
17
|
+
@threads = []
|
|
18
|
+
@state = :running
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def new &block
|
|
22
|
+
checkout( &block )
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def length
|
|
26
|
+
@mutex.synchronize do
|
|
27
|
+
@threads.length
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def shutdown
|
|
34
|
+
@mutex.synchronize do
|
|
35
|
+
@state = :exiting
|
|
36
|
+
@threads.each do |thread|
|
|
37
|
+
thread.send :shutdown
|
|
38
|
+
end
|
|
39
|
+
@threads.each do |thread|
|
|
40
|
+
thread.true_join
|
|
41
|
+
end
|
|
42
|
+
@threads = []
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def checkin thread
|
|
47
|
+
@mutex.synchronize do
|
|
48
|
+
if @state != :exiting
|
|
49
|
+
@threads << thread
|
|
50
|
+
else
|
|
51
|
+
warn("POST EXIT CHECKIN")
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def checkout &block
|
|
57
|
+
raise "hell" if @state == :exiting
|
|
58
|
+
t = nil
|
|
59
|
+
@mutex.synchronize do
|
|
60
|
+
if @threads.length == 0
|
|
61
|
+
pt = PoolThread.new self
|
|
62
|
+
@threads << pt
|
|
63
|
+
end
|
|
64
|
+
t = @threads.pop
|
|
65
|
+
end
|
|
66
|
+
t.send :awaken, &block
|
|
67
|
+
t
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
class Dramatis::Runtime::ThreadPool::PoolThread < Thread
|
|
73
|
+
|
|
74
|
+
def initialize pool
|
|
75
|
+
@pool = pool
|
|
76
|
+
@mutex = Mutex.new
|
|
77
|
+
@wait = ConditionVariable.new
|
|
78
|
+
@state = :running
|
|
79
|
+
super() do
|
|
80
|
+
self.abort_on_exception = true
|
|
81
|
+
target
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
alias true_join join
|
|
86
|
+
|
|
87
|
+
def join
|
|
88
|
+
# I've thought about this. It would be cool to implement join. It's not
|
|
89
|
+
# too hard to do it when threads aren't reused ... which is kinda dumb.
|
|
90
|
+
# It's possible to do it when threads are reused, by coding an allocation
|
|
91
|
+
# counter in the "thread" object (but not the native thread). It'd be
|
|
92
|
+
# cool, but I don't need it, so, oh well.
|
|
93
|
+
raise "not implemented"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
def shutdown
|
|
99
|
+
@mutex.synchronize do
|
|
100
|
+
old_state, @state = @state, :exiting
|
|
101
|
+
# p "#{Thread.current} shutdown #{old_state} #{@state}"
|
|
102
|
+
if old_state == :waiting
|
|
103
|
+
@wait.signal
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def awaken &block
|
|
109
|
+
@mutex.synchronize do
|
|
110
|
+
@block = block
|
|
111
|
+
old_state, @state = @state, :called
|
|
112
|
+
if old_state == :waiting
|
|
113
|
+
# p "#{Thread.current} signalling"
|
|
114
|
+
@wait.signal
|
|
115
|
+
elsif old_state == :exiting
|
|
116
|
+
warn("AWAKEN AFTER EXIT")
|
|
117
|
+
raise "AWAKEN AFTER EXIT"
|
|
118
|
+
elsif old_state != :running
|
|
119
|
+
warn("AWAKEN BAD STATE " + old_state)
|
|
120
|
+
raise "AWAKEN BAD STATE " + old_state
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def target
|
|
126
|
+
while true do
|
|
127
|
+
@mutex.synchronize do
|
|
128
|
+
if @state == :exiting
|
|
129
|
+
return
|
|
130
|
+
elsif @state == :running
|
|
131
|
+
@state = :waiting
|
|
132
|
+
@wait.wait @mutex
|
|
133
|
+
raise "hell" if @state == :waiting
|
|
134
|
+
elsif @state == :called
|
|
135
|
+
begin
|
|
136
|
+
@block.call
|
|
137
|
+
ensure
|
|
138
|
+
@state = :running
|
|
139
|
+
@pool.send :checkin, self
|
|
140
|
+
end
|
|
141
|
+
else
|
|
142
|
+
warn( "!!FAIL!! " + @state)
|
|
143
|
+
raise "!!FAIL!! " + @state
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
end
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
module Dramatis; end
|
|
2
|
+
|
|
3
|
+
require 'thread'
|
|
4
|
+
|
|
5
|
+
# Dramatis::Runtime is the top level class managing the running of the
|
|
6
|
+
# various pieces of the dramatis runtime. Typically programs don't
|
|
7
|
+
# need to deal with the runtime directly, though some functions are
|
|
8
|
+
# useful, particularly for debugging and testing.
|
|
9
|
+
|
|
10
|
+
class Dramatis::Runtime
|
|
11
|
+
|
|
12
|
+
# Returns a reference to the current Dramatis::Runtime object.
|
|
13
|
+
|
|
14
|
+
def self.current
|
|
15
|
+
@@current ||= self.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Resets the current runtime instance. Note that this method hard
|
|
19
|
+
# resets counters and ignore exceptions which is generally a bad
|
|
20
|
+
# idea. It is typical only used in unit test after methods to keep
|
|
21
|
+
# failing tests from cascading.
|
|
22
|
+
|
|
23
|
+
def self.reset
|
|
24
|
+
# this swallows exceptions: it's assumed to be used to clean up
|
|
25
|
+
# a failed test so there's no connection between tests
|
|
26
|
+
begin
|
|
27
|
+
Dramatis::Runtime.current.quiesce
|
|
28
|
+
rescue Exception => e
|
|
29
|
+
end
|
|
30
|
+
Dramatis::Runtime::Scheduler.reset
|
|
31
|
+
Dramatis::Runtime::Actor::Main.reset
|
|
32
|
+
@@current = nil
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Causes the runtime to suspend the current thread until there are
|
|
36
|
+
# no more tasks that can be executed. If no tasks remain, returns
|
|
37
|
+
# normally. If tasks remain but are gated off,
|
|
38
|
+
# Dramtis::Error::Deadlock is raised.
|
|
39
|
+
#
|
|
40
|
+
# As a side effect, this method releases the current actor to
|
|
41
|
+
# process messages but does not change the task gate.
|
|
42
|
+
|
|
43
|
+
def quiesce
|
|
44
|
+
Dramatis::Runtime::Scheduler.current.quiesce
|
|
45
|
+
maybe_raise_exceptions true
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def maybe_raise_exceptions quiescing #:nodoc:
|
|
49
|
+
@mutex.synchronize do
|
|
50
|
+
if !@exceptions.empty?
|
|
51
|
+
# warn "no maybe about it"
|
|
52
|
+
if !quiescing and warnings?
|
|
53
|
+
warn "the following #{@exceptions.length} exception(s) were raised and not caught"
|
|
54
|
+
@exceptions.each do |exception|
|
|
55
|
+
# warn "#{exception}"
|
|
56
|
+
# pp exception.backtrace
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
raise Dramatis::Error::Uncaught.new( @exceptions )
|
|
60
|
+
end
|
|
61
|
+
@exceptions.clear
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Returns the list of uncaught exceptions.
|
|
66
|
+
|
|
67
|
+
def exceptions
|
|
68
|
+
result = nil
|
|
69
|
+
@mutex.synchronize do
|
|
70
|
+
result = @exceptions.dup
|
|
71
|
+
end
|
|
72
|
+
result
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Clears the list of uncaught exceptions. Used in unit tests to
|
|
76
|
+
# clear expected exceptions. If exceptions are raised and not
|
|
77
|
+
# clear, they will be raised at the end of the program via a
|
|
78
|
+
# Dramatis::Error::Uncaught.
|
|
79
|
+
|
|
80
|
+
def clear_exceptions
|
|
81
|
+
@mutex.synchronize do
|
|
82
|
+
# warn "runtime clearing exceptions"
|
|
83
|
+
@exceptions.clear
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def exception exception #:nodoc:
|
|
88
|
+
@mutex.synchronize do
|
|
89
|
+
@exceptions << exception
|
|
90
|
+
warn "runtime recording exception: #{exception} [#{@exceptions.length}]" if warnings?
|
|
91
|
+
# backtrace
|
|
92
|
+
# pp exception.backtrace
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def backtrace #:nodoc:
|
|
97
|
+
begin
|
|
98
|
+
raise "backtrace"
|
|
99
|
+
rescue ::Exception => e
|
|
100
|
+
pp e.backtrace
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Enables or disables printing warnings, e.g., when uncaugh
|
|
105
|
+
# exceptions are detected.
|
|
106
|
+
|
|
107
|
+
def warnings= value
|
|
108
|
+
@warnings = value
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Returns true if warnings are enabled.
|
|
112
|
+
|
|
113
|
+
def warnings?
|
|
114
|
+
@warnings
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def at_exit #:nodoc:
|
|
118
|
+
Dramatis::Runtime::Actor::Main.current.finalize
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
private
|
|
122
|
+
|
|
123
|
+
def initialize #:nodoc:
|
|
124
|
+
@warnings = true
|
|
125
|
+
@mutex = Mutex.new
|
|
126
|
+
@exceptions = []
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
end
|