eventbox 0.1.0 → 1.0.0
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.
- 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
|