eventbox 0.1.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/.appveyor.yml +5 -3
- data/.travis.yml +4 -4
- data/.yardopts +2 -2
- data/CHANGELOG.md +19 -0
- data/README.md +91 -48
- data/docs/downloads.md +6 -5
- data/docs/images/my_queue_calls.svg +761 -0
- data/docs/my_queue_calls_github.md +1 -0
- data/docs/my_queue_calls_local.md +1 -0
- data/docs/server.md +25 -14
- data/docs/threadpool.md +4 -1
- data/eventbox.gemspec +3 -2
- data/lib/eventbox.rb +63 -11
- data/lib/eventbox/argument_wrapper.rb +11 -10
- data/lib/eventbox/boxable.rb +41 -33
- data/lib/eventbox/call_context.rb +47 -0
- data/lib/eventbox/event_loop.rb +167 -70
- data/lib/eventbox/sanitizer.rb +155 -39
- data/lib/eventbox/thread_pool.rb +10 -0
- data/lib/eventbox/timer.rb +17 -7
- data/lib/eventbox/version.rb +1 -1
- metadata +50 -30
- metadata.gz.sig +0 -0
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
class Eventbox
|
4
|
+
module CallContext
|
5
|
+
# @private
|
6
|
+
def __answer_queue__
|
7
|
+
@__answer_queue__
|
8
|
+
end
|
9
|
+
|
10
|
+
# @private
|
11
|
+
attr_writer :__answer_queue__
|
12
|
+
end
|
13
|
+
|
14
|
+
class BlockingExternalCallContext
|
15
|
+
include CallContext
|
16
|
+
end
|
17
|
+
|
18
|
+
class ActionCallContext
|
19
|
+
include CallContext
|
20
|
+
|
21
|
+
# @private
|
22
|
+
def initialize(event_loop)
|
23
|
+
answer_queue = Queue.new
|
24
|
+
meth = proc do
|
25
|
+
event_loop.callback_loop(answer_queue, nil, self.class)
|
26
|
+
end
|
27
|
+
@action = event_loop.start_action(meth, self.class, [])
|
28
|
+
|
29
|
+
def answer_queue.gc_stop(object_id)
|
30
|
+
close
|
31
|
+
end
|
32
|
+
ObjectSpace.define_finalizer(self, answer_queue.method(:gc_stop))
|
33
|
+
|
34
|
+
@__answer_queue__ = answer_queue
|
35
|
+
end
|
36
|
+
|
37
|
+
# The action that drives the call context.
|
38
|
+
attr_reader :action
|
39
|
+
|
40
|
+
# Terminate the call context and the driving action.
|
41
|
+
#
|
42
|
+
# The method returns immediately and the corresponding action is terminated asynchonously.
|
43
|
+
def shutdown!
|
44
|
+
@__answer_queue__.close
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/eventbox/event_loop.rb
CHANGED
@@ -10,22 +10,40 @@ class Eventbox
|
|
10
10
|
class EventLoop
|
11
11
|
def initialize(threadpool, guard_time)
|
12
12
|
@threadpool = threadpool
|
13
|
+
@shutdown = false
|
14
|
+
@guard_time = guard_time
|
15
|
+
_init_variables
|
16
|
+
end
|
17
|
+
|
18
|
+
def marshal_dump
|
19
|
+
raise TypeError, "Eventbox objects can't be serialized within event scope" if event_scope?
|
20
|
+
@mutex.synchronize do
|
21
|
+
raise TypeError, "Eventbox objects can't be serialized while actions are running" unless @running_actions.empty?
|
22
|
+
[@threadpool, @shutdown, @guard_time]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def marshal_load(array)
|
27
|
+
@threadpool, @shutdown, @guard_time = array
|
28
|
+
_init_variables
|
29
|
+
end
|
30
|
+
|
31
|
+
def _init_variables
|
13
32
|
@running_actions = []
|
14
33
|
@running_actions_for_gc = []
|
15
34
|
@mutex = Mutex.new
|
16
|
-
@
|
17
|
-
@guard_time_proc = case guard_time
|
35
|
+
@guard_time_proc = case @guard_time
|
18
36
|
when NilClass
|
19
37
|
nil
|
20
38
|
when Numeric
|
21
|
-
guard_time and proc do |dt, name|
|
22
|
-
if dt > guard_time
|
39
|
+
@guard_time and proc do |dt, name|
|
40
|
+
if dt > @guard_time
|
23
41
|
ecaller = caller.find{|t| !(t=~/lib\/eventbox(\/|\.rb:)/) }
|
24
|
-
warn "guard time exceeded: #{"%2.3f" % dt} sec (limit is #{guard_time}) in `#{name}' called from `#{ecaller}' - please move blocking tasks to actions"
|
42
|
+
warn "guard time exceeded: #{"%2.3f" % dt} sec (limit is #{@guard_time}) in `#{name}' called from `#{ecaller}' - please move blocking tasks to actions"
|
25
43
|
end
|
26
44
|
end
|
27
45
|
when Proc
|
28
|
-
guard_time
|
46
|
+
@guard_time
|
29
47
|
else
|
30
48
|
raise ArgumentError, "guard_time should be Numeric, Proc or nil"
|
31
49
|
end
|
@@ -53,6 +71,10 @@ class Eventbox
|
|
53
71
|
nil
|
54
72
|
end
|
55
73
|
|
74
|
+
def inspect
|
75
|
+
"#<#{self.class}:#{self.object_id} @threadpool=#{@threadpool.inspect}, @shutdown=#{@shutdown.inspect}, @guard_time=#{@guard_time.inspect}, @running_actions=#{@running_actions.length}>"
|
76
|
+
end
|
77
|
+
|
56
78
|
def shutdown(&completion_block)
|
57
79
|
send_shutdown
|
58
80
|
if event_scope?
|
@@ -111,28 +133,47 @@ class Eventbox
|
|
111
133
|
@latest_answer_queue = nil
|
112
134
|
@latest_call_name = nil
|
113
135
|
@mutex.unlock
|
136
|
+
Thread.current.thread_variable_set(:__event_loop__, source_event_loop)
|
114
137
|
diff_time = Time.now - start_time
|
115
138
|
@guard_time_proc&.call(diff_time, name)
|
116
|
-
Thread.current.thread_variable_set(:__event_loop__, source_event_loop)
|
117
139
|
end
|
118
140
|
source_event_loop
|
119
141
|
end
|
120
142
|
|
121
|
-
def
|
143
|
+
def _latest_call_context
|
144
|
+
if @latest_answer_queue
|
145
|
+
ctx = BlockingExternalCallContext.new
|
146
|
+
ctx.__answer_queue__ = @latest_answer_queue
|
147
|
+
end
|
148
|
+
ctx
|
149
|
+
end
|
150
|
+
|
151
|
+
def with_call_context(ctx)
|
152
|
+
orig_context = @latest_answer_queue
|
153
|
+
raise ArgumentError, "invalid argument #{ctx.inspect} instead of Eventbox::CallContext" unless CallContext === ctx
|
154
|
+
@latest_answer_queue = ctx.__answer_queue__
|
155
|
+
yield
|
156
|
+
ensure
|
157
|
+
@latest_answer_queue = orig_context
|
158
|
+
end
|
159
|
+
|
160
|
+
def async_call(box, name, args, kwargs, block, wrapper)
|
122
161
|
with_call_frame(name, nil) do |source_event_loop|
|
123
|
-
args = wrapper.call(source_event_loop, *args) if wrapper
|
162
|
+
args, kwargs = wrapper.call(source_event_loop, self, *args, **kwargs) if wrapper
|
124
163
|
args = Sanitizer.sanitize_values(args, source_event_loop, self, name)
|
164
|
+
kwargs = Sanitizer.sanitize_kwargs(kwargs, source_event_loop, self, name)
|
125
165
|
block = Sanitizer.sanitize_value(block, source_event_loop, self, name)
|
126
|
-
box.send("__#{name}__", *args, &block)
|
166
|
+
box.send("__#{name}__", *args, **kwargs, &block)
|
127
167
|
end
|
128
168
|
end
|
129
169
|
|
130
|
-
def sync_call(box, name, args, block, answer_queue, wrapper)
|
170
|
+
def sync_call(box, name, args, kwargs, block, answer_queue, wrapper)
|
131
171
|
with_call_frame(name, answer_queue) do |source_event_loop|
|
132
|
-
args = wrapper.call(source_event_loop, *args) if wrapper
|
172
|
+
args, kwargs = wrapper.call(source_event_loop, self, *args, **kwargs) if wrapper
|
133
173
|
args = Sanitizer.sanitize_values(args, source_event_loop, self, name)
|
174
|
+
kwargs = Sanitizer.sanitize_kwargs(kwargs, source_event_loop, self, name)
|
134
175
|
block = Sanitizer.sanitize_value(block, source_event_loop, self, name)
|
135
|
-
res = box.send("__#{name}__", *args, &block)
|
176
|
+
res = box.send("__#{name}__", *args, **kwargs, &block)
|
136
177
|
res = Sanitizer.sanitize_value(res, self, source_event_loop)
|
137
178
|
answer_queue << res
|
138
179
|
end
|
@@ -140,32 +181,34 @@ class Eventbox
|
|
140
181
|
|
141
182
|
def yield_call(box, name, args, kwargs, block, answer_queue, wrapper)
|
142
183
|
with_call_frame(name, answer_queue) do |source_event_loop|
|
143
|
-
args <<
|
144
|
-
args
|
145
|
-
args = wrapper.call(source_event_loop, *args) if wrapper
|
184
|
+
args << new_completion_proc(answer_queue, name, source_event_loop)
|
185
|
+
args, kwargs = wrapper.call(source_event_loop, self, *args, **kwargs) if wrapper
|
146
186
|
args = Sanitizer.sanitize_values(args, source_event_loop, self, name)
|
187
|
+
kwargs = Sanitizer.sanitize_kwargs(kwargs, source_event_loop, self, name)
|
147
188
|
block = Sanitizer.sanitize_value(block, source_event_loop, self, name)
|
148
|
-
box.send("__#{name}__", *args, &block)
|
189
|
+
box.send("__#{name}__", *args, **kwargs, &block)
|
149
190
|
end
|
150
191
|
end
|
151
192
|
|
152
193
|
# Anonymous version of async_call
|
153
|
-
def async_proc_call(pr, args, arg_block, wrapper)
|
194
|
+
def async_proc_call(pr, args, kwargs, arg_block, wrapper)
|
154
195
|
with_call_frame(AsyncProc, nil) do |source_event_loop|
|
155
|
-
args = wrapper.call(source_event_loop, *args) if wrapper
|
196
|
+
args, kwargs = wrapper.call(source_event_loop, self, *args, **kwargs) if wrapper
|
156
197
|
args = Sanitizer.sanitize_values(args, source_event_loop, self)
|
198
|
+
kwargs = Sanitizer.sanitize_kwargs(kwargs, source_event_loop, self)
|
157
199
|
arg_block = Sanitizer.sanitize_value(arg_block, source_event_loop, self)
|
158
|
-
pr.yield(*args, &arg_block)
|
200
|
+
pr.yield(*args, **kwargs, &arg_block)
|
159
201
|
end
|
160
202
|
end
|
161
203
|
|
162
204
|
# Anonymous version of sync_call
|
163
|
-
def sync_proc_call(pr, args, arg_block, answer_queue, wrapper)
|
205
|
+
def sync_proc_call(pr, args, kwargs, arg_block, answer_queue, wrapper)
|
164
206
|
with_call_frame(SyncProc, answer_queue) do |source_event_loop|
|
165
|
-
args = wrapper.call(source_event_loop, *args) if wrapper
|
207
|
+
args, kwargs = wrapper.call(source_event_loop, self, *args, **kwargs) if wrapper
|
166
208
|
args = Sanitizer.sanitize_values(args, source_event_loop, self)
|
209
|
+
kwargs = Sanitizer.sanitize_kwargs(kwargs, source_event_loop, self)
|
167
210
|
arg_block = Sanitizer.sanitize_value(arg_block, source_event_loop, self)
|
168
|
-
res = pr.yield(*args, &arg_block)
|
211
|
+
res = pr.yield(*args, **kwargs, &arg_block)
|
169
212
|
res = Sanitizer.sanitize_value(res, self, source_event_loop)
|
170
213
|
answer_queue << res
|
171
214
|
end
|
@@ -174,18 +217,20 @@ class Eventbox
|
|
174
217
|
# Anonymous version of yield_call
|
175
218
|
def yield_proc_call(pr, args, kwargs, arg_block, answer_queue, wrapper)
|
176
219
|
with_call_frame(YieldProc, answer_queue) do |source_event_loop|
|
177
|
-
args <<
|
178
|
-
args
|
179
|
-
args = wrapper.call(source_event_loop, *args) if wrapper
|
220
|
+
args << new_completion_proc(answer_queue, pr, source_event_loop)
|
221
|
+
args, kwargs = wrapper.call(source_event_loop, self, *args, **kwargs) if wrapper
|
180
222
|
args = Sanitizer.sanitize_values(args, source_event_loop, self)
|
223
|
+
kwargs = Sanitizer.sanitize_kwargs(kwargs, source_event_loop, self)
|
181
224
|
arg_block = Sanitizer.sanitize_value(arg_block, source_event_loop, self)
|
182
|
-
pr.yield(*args, &arg_block)
|
225
|
+
pr.yield(*args, **kwargs, &arg_block)
|
183
226
|
end
|
184
227
|
end
|
185
228
|
|
186
|
-
# Called when an external
|
187
|
-
def
|
188
|
-
with_call_frame(
|
229
|
+
# Called when an external object call finished
|
230
|
+
def external_call_result(cbresult, res, answer_queue, wrapper)
|
231
|
+
with_call_frame(ExternalObject, answer_queue) do |source_event_loop|
|
232
|
+
res, _ = wrapper.call(source_event_loop, self, res) if wrapper
|
233
|
+
res = Sanitizer.sanitize_value(res, source_event_loop, self)
|
189
234
|
cbresult.yield(*res)
|
190
235
|
end
|
191
236
|
end
|
@@ -193,13 +238,13 @@ class Eventbox
|
|
193
238
|
def new_async_proc(name=nil, klass=AsyncProc, &block)
|
194
239
|
raise InvalidAccess, "async_proc outside of the event scope is not allowed" unless event_scope?
|
195
240
|
wrapper = ArgumentWrapper.build(block, "async_proc #{name}")
|
196
|
-
pr = klass.new do |*args, &arg_block|
|
241
|
+
pr = klass.new do |*args, **kwargs, &arg_block|
|
197
242
|
if event_scope?
|
198
243
|
# called in the event scope
|
199
|
-
block.yield(*args, &arg_block)
|
244
|
+
block.yield(*args, **kwargs, &arg_block)
|
200
245
|
else
|
201
246
|
# called externally
|
202
|
-
async_proc_call(block, args, arg_block, wrapper)
|
247
|
+
async_proc_call(block, args, kwargs, arg_block, wrapper)
|
203
248
|
end
|
204
249
|
pr
|
205
250
|
end
|
@@ -208,15 +253,15 @@ class Eventbox
|
|
208
253
|
def new_sync_proc(name=nil, &block)
|
209
254
|
raise InvalidAccess, "sync_proc outside of the event scope is not allowed" unless event_scope?
|
210
255
|
wrapper = ArgumentWrapper.build(block, "sync_proc #{name}")
|
211
|
-
SyncProc.new do |*args, &arg_block|
|
256
|
+
SyncProc.new do |*args, **kwargs, &arg_block|
|
212
257
|
if event_scope?
|
213
258
|
# called in the event scope
|
214
|
-
block.yield(*args, &arg_block)
|
259
|
+
block.yield(*args, **kwargs, &arg_block)
|
215
260
|
else
|
216
261
|
# called externally
|
217
262
|
answer_queue = Queue.new
|
218
|
-
sel = sync_proc_call(block, args, arg_block, answer_queue, wrapper)
|
219
|
-
callback_loop(answer_queue, sel)
|
263
|
+
sel = sync_proc_call(block, args, kwargs, arg_block, answer_queue, wrapper)
|
264
|
+
callback_loop(answer_queue, sel, block)
|
220
265
|
end
|
221
266
|
end
|
222
267
|
end
|
@@ -227,20 +272,19 @@ class Eventbox
|
|
227
272
|
YieldProc.new do |*args, **kwargs, &arg_block|
|
228
273
|
if event_scope?
|
229
274
|
# called in the event scope
|
230
|
-
|
231
|
-
args
|
232
|
-
block.yield(*args, &arg_block)
|
275
|
+
internal_yield_result(args, block)
|
276
|
+
block.yield(*args, **kwargs, &arg_block)
|
233
277
|
nil
|
234
278
|
else
|
235
279
|
# called externally
|
236
280
|
answer_queue = Queue.new
|
237
281
|
sel = yield_proc_call(block, args, kwargs, arg_block, answer_queue, wrapper)
|
238
|
-
callback_loop(answer_queue, sel)
|
282
|
+
callback_loop(answer_queue, sel, block)
|
239
283
|
end
|
240
284
|
end
|
241
285
|
end
|
242
286
|
|
243
|
-
def
|
287
|
+
def internal_yield_result(args, name)
|
244
288
|
complete = args.last
|
245
289
|
unless Proc === complete
|
246
290
|
if Proc === name
|
@@ -252,9 +296,9 @@ class Eventbox
|
|
252
296
|
args[-1] = proc do |*cargs, &cblock|
|
253
297
|
unless complete
|
254
298
|
if Proc === name
|
255
|
-
raise MultipleResults, "
|
299
|
+
raise MultipleResults, "second result yielded for #{name.inspect} that already returned"
|
256
300
|
else
|
257
|
-
raise MultipleResults, "
|
301
|
+
raise MultipleResults, "second result yielded for method `#{name}' that already returned"
|
258
302
|
end
|
259
303
|
end
|
260
304
|
res = complete.yield(*cargs, &cblock)
|
@@ -263,13 +307,15 @@ class Eventbox
|
|
263
307
|
end
|
264
308
|
end
|
265
309
|
|
266
|
-
private def
|
267
|
-
new_async_proc(name, CompletionProc) do |*resu|
|
310
|
+
private def new_completion_proc(answer_queue, name, source_event_loop)
|
311
|
+
pr = new_async_proc(name, CompletionProc) do |*resu|
|
268
312
|
unless answer_queue
|
313
|
+
# It could happen, that two threads call the CompletionProc simultanously so that nothing is raised here.
|
314
|
+
# In this case the failure is caught in callback_loop instead, but in all other cases the failure is raised early here at the caller side.
|
269
315
|
if Proc === name
|
270
|
-
raise MultipleResults, "
|
316
|
+
raise MultipleResults, "second result yielded for #{name.inspect} that already returned"
|
271
317
|
else
|
272
|
-
raise MultipleResults, "
|
318
|
+
raise MultipleResults, "second result yielded for method `#{name}' that already returned"
|
273
319
|
end
|
274
320
|
end
|
275
321
|
resu = Sanitizer.sanitize_values(resu, self, source_event_loop)
|
@@ -277,29 +323,51 @@ class Eventbox
|
|
277
323
|
answer_queue << resu
|
278
324
|
answer_queue = nil
|
279
325
|
end
|
326
|
+
pr.__answer_queue__ = answer_queue
|
327
|
+
pr
|
280
328
|
end
|
281
329
|
|
282
|
-
def callback_loop(answer_queue, source_event_loop)
|
330
|
+
def callback_loop(answer_queue, source_event_loop, name)
|
283
331
|
loop do
|
284
332
|
rets = answer_queue.deq
|
285
333
|
case rets
|
286
|
-
when
|
287
|
-
cbres = rets.
|
334
|
+
when ExternalObjectCall
|
335
|
+
cbres = rets.object.send(rets.method, *rets.args, **rets.kwargs, &rets.arg_block)
|
288
336
|
|
289
337
|
if rets.cbresult
|
290
|
-
|
291
|
-
external_proc_result(rets.cbresult, cbres)
|
338
|
+
external_call_result(rets.cbresult, cbres, answer_queue, rets.result_wrapper)
|
292
339
|
end
|
293
340
|
when WrappedException
|
294
|
-
answer_queue
|
341
|
+
close_answer_queue(answer_queue, name)
|
295
342
|
raise(*rets.exc)
|
296
343
|
else
|
297
|
-
answer_queue
|
344
|
+
close_answer_queue(answer_queue, name)
|
298
345
|
return rets
|
299
346
|
end
|
300
347
|
end
|
301
348
|
end
|
302
349
|
|
350
|
+
private def close_answer_queue(answer_queue, name)
|
351
|
+
answer_queue.close
|
352
|
+
unless answer_queue.empty?
|
353
|
+
rets = answer_queue.deq
|
354
|
+
case rets
|
355
|
+
when ExternalObjectCall
|
356
|
+
if Proc === name
|
357
|
+
raise InvalidAccess, "#{rets.objtype} can't be called through #{name.inspect}, since it already returned"
|
358
|
+
else
|
359
|
+
raise InvalidAccess, "#{rets.objtype} can't be called through method `#{name}', since it already returned"
|
360
|
+
end
|
361
|
+
else
|
362
|
+
if Proc === name
|
363
|
+
raise MultipleResults, "second result yielded for #{name.inspect} that already returned"
|
364
|
+
else
|
365
|
+
raise MultipleResults, "second result yielded for method `#{name}' that already returned"
|
366
|
+
end
|
367
|
+
end
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
303
371
|
# Mark an object as to be shared instead of copied.
|
304
372
|
def shared_object(object)
|
305
373
|
if event_scope?
|
@@ -310,6 +378,11 @@ class Eventbox
|
|
310
378
|
object
|
311
379
|
end
|
312
380
|
|
381
|
+
# Wrap an object as ExternalObject.
|
382
|
+
def €(object)
|
383
|
+
Sanitizer.wrap_object(object, nil, self, nil)
|
384
|
+
end
|
385
|
+
|
313
386
|
def thread_finished(action)
|
314
387
|
@mutex.synchronize do
|
315
388
|
@running_actions.delete(action) or raise(ArgumentError, "unknown action has finished: #{action}")
|
@@ -317,20 +390,40 @@ class Eventbox
|
|
317
390
|
end
|
318
391
|
end
|
319
392
|
|
320
|
-
|
393
|
+
class ExternalObjectCall < Struct.new :object, :method, :args, :kwargs, :arg_block, :cbresult, :result_wrapper
|
394
|
+
def proc?
|
395
|
+
Proc === object
|
396
|
+
end
|
321
397
|
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
398
|
+
def objtype
|
399
|
+
proc? ? "closure" : "method `#{method}'"
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
def _external_object_call(object, method, name, args, kwargs, arg_block, cbresult, source_event_loop, call_context)
|
404
|
+
result_wrapper = ArgumentWrapper.build(cbresult, name) if cbresult
|
405
|
+
args = Sanitizer.sanitize_values(args, self, source_event_loop)
|
406
|
+
kwargs = Sanitizer.sanitize_kwargs(kwargs, self, source_event_loop)
|
407
|
+
arg_block = Sanitizer.sanitize_value(arg_block, self, source_event_loop)
|
408
|
+
cb = ExternalObjectCall.new(object, method, args, kwargs, arg_block, cbresult, result_wrapper)
|
409
|
+
|
410
|
+
if call_context
|
411
|
+
# explicit call_context given
|
412
|
+
if call_context.__answer_queue__.closed?
|
413
|
+
raise InvalidAccess, "#{cb.objtype} #{"defined by `#{name}' " if name}was called with a call context that already returned"
|
414
|
+
end
|
415
|
+
call_context.__answer_queue__ << cb
|
416
|
+
elsif @latest_answer_queue
|
417
|
+
# proc called by a sync or yield call/proc context
|
418
|
+
@latest_answer_queue << cb
|
328
419
|
else
|
329
|
-
raise
|
420
|
+
raise InvalidAccess, "#{cb.objtype} #{"defined by `#{name}' " if name}was called by `#{@latest_call_name}', which must a sync_call, yield_call, sync_proc or yield_proc"
|
330
421
|
end
|
422
|
+
|
423
|
+
nil
|
331
424
|
end
|
332
425
|
|
333
|
-
def start_action(meth, name, args)
|
426
|
+
def start_action(meth, name, args, &block)
|
334
427
|
# Actions might not be tagged to a calling event scope
|
335
428
|
source_event_loop = Thread.current.thread_variable_get(:__event_loop__)
|
336
429
|
Thread.current.thread_variable_set(:__event_loop__, nil)
|
@@ -339,23 +432,27 @@ class Eventbox
|
|
339
432
|
|
340
433
|
new_thread = Thread.handle_interrupt(Exception => :never) do
|
341
434
|
@threadpool.new do
|
435
|
+
ac = nil
|
342
436
|
begin
|
343
437
|
Thread.handle_interrupt(AbortAction => :on_blocking) do
|
344
438
|
if meth.arity == args.length
|
345
|
-
meth.call(*args)
|
439
|
+
meth.call(*args, &block)
|
346
440
|
else
|
347
|
-
|
441
|
+
ac ||= qu.deq
|
442
|
+
meth.call(*args, ac, &block)
|
348
443
|
end
|
349
444
|
end
|
350
445
|
rescue AbortAction
|
351
|
-
|
446
|
+
ac ||= qu.deq
|
447
|
+
ac.terminate
|
352
448
|
rescue WeakRef::RefError
|
353
449
|
# It can happen that the GC already swept the Eventbox instance, before some instance action is in a blocking state.
|
354
450
|
# In this case access to the Eventbox instance raises a RefError.
|
355
451
|
# Since it's now impossible to execute the action up to a blocking state, abort the action prematurely.
|
356
452
|
raise unless @shutdown
|
357
453
|
ensure
|
358
|
-
|
454
|
+
ac ||= qu.deq
|
455
|
+
thread_finished(ac)
|
359
456
|
end
|
360
457
|
end
|
361
458
|
end
|
@@ -368,8 +465,8 @@ class Eventbox
|
|
368
465
|
_update_action_threads_for_gc
|
369
466
|
end
|
370
467
|
|
371
|
-
# Enqueue the action
|
372
|
-
qu << a
|
468
|
+
# Enqueue the action for passing as action parameter
|
469
|
+
qu << a
|
373
470
|
|
374
471
|
# @shutdown is set without a lock, so that we need to re-check, if it was set while start_action
|
375
472
|
if @shutdown
|