eventbox 0.1.0 → 1.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.
@@ -18,18 +18,18 @@ class Eventbox
18
18
  #
19
19
  # In detail this works as following.
20
20
  # Objects which are passed through unchanged are:
21
- # * {Eventbox}, {Eventbox::Action} and `Module` objects
21
+ # * {Eventbox}, {Eventbox::Action} and +Module+ objects
22
22
  # * Proc objects created by {Eventbox#async_proc}, {Eventbox#sync_proc} and {Eventbox#yield_proc}
23
23
  #
24
24
  # The following rules apply for wrapping/unwrapping:
25
- # * If the object has been marked as {Eventbox#shared_object}, it is wrapped as {WrappedObject} depending on the direction of the data flow (return value or call argument).
25
+ # * If the object has been marked as {Eventbox#shared_object}, it is wrapped as {WrappedObject} or {ExternalObject} depending on the direction of the data flow (return value or call argument).
26
26
  # * If the object is a {WrappedObject} or {ExternalProc} and fits to the target scope, it is unwrapped.
27
27
  # Both cases even work if the object is encapsulated by another object.
28
28
  #
29
29
  # In all other cases the following rules apply:
30
- # * If the object is marshalable, it is passed as a deep copy through `Marshal.dump` and `Marshal.load` .
31
- # * An object which failed to marshal as a whole is tried to be dissected and values are sanitized recursively.
32
- # * If the object can't be marshaled or dissected, it is wrapped as {WrappedObject}.
30
+ # * If the object is marshalable, it is passed as a deep copy through +Marshal.dump+ and +Marshal.load+ .
31
+ # * An object which failed to marshal as a whole is tried to be dissected and values are sanitized separately and recursively.
32
+ # * If the object can't be marshaled or dissected, it is wrapped as {ExternalObject} when passed from external scope to event scope and wrapped as {WrappedObject} when passed from the event scope.
33
33
  # They are unwrapped when passed back to origin scope.
34
34
  # * Proc objects passed from event scope to external are wrapped as {WrappedObject}.
35
35
  # They are unwrapped when passed back to event scope.
@@ -46,24 +46,25 @@ class Eventbox
46
46
  # Separate the instance variables from the object
47
47
  ivns = arg.instance_variables
48
48
  ivvs = ivns.map do |ivn|
49
- arg.instance_variable_get(ivn)
50
- end
51
-
52
- # Temporary set all instance variables to nil
53
- ivns.each do |ivn|
49
+ ivv = arg.instance_variable_get(ivn)
50
+ # Temporary set all instance variables to nil
54
51
  arg.instance_variable_set(ivn, nil)
52
+ ivv
55
53
  end
56
54
 
57
55
  # Copy the object
58
56
  arg2 = yield(arg)
59
-
60
- # Restore the original object
57
+ rescue
58
+ # Restore the original object in case of Marshal.dump failure
61
59
  ivns.each_with_index do |ivn, ivni|
62
60
  arg.instance_variable_set(ivn, ivvs[ivni])
63
61
  end
64
-
65
- # sanitize instance variables independently and write them to the copied object
62
+ raise
63
+ else
66
64
  ivns.each_with_index do |ivn, ivni|
65
+ # Restore the original object
66
+ arg.instance_variable_set(ivn, ivvs[ivni])
67
+ # sanitize instance variables independently and write them to the copied object
67
68
  ivv = sanitize_value(ivvs[ivni], source_event_loop, target_event_loop, ivn)
68
69
  arg2.instance_variable_set(ivn, ivv)
69
70
  end
@@ -81,12 +82,15 @@ class Eventbox
81
82
  end
82
83
 
83
84
  arg2 = yield(arg)
84
-
85
+ rescue
86
+ # Restore the original object in case of Marshal.dump failure
85
87
  ms.each_with_index do |m, i|
86
88
  arg[m] = vs[i]
87
89
  end
88
-
90
+ raise
91
+ else
89
92
  ms.each_with_index do |m, i|
93
+ arg[m] = vs[i]
90
94
  v2 = sanitize_value(vs[i], source_event_loop, target_event_loop, m)
91
95
  arg2[m] = v2
92
96
  end
@@ -102,12 +106,15 @@ class Eventbox
102
106
  end
103
107
 
104
108
  arg2 = yield(arg)
105
-
109
+ rescue
110
+ # Restore the original object in case of Marshal.dump failure
106
111
  h.each do |k, v|
107
112
  arg[k] = v
108
113
  end
109
-
114
+ raise
115
+ else
110
116
  h.each do |k, v|
117
+ arg[k] = v
111
118
  arg2[k] = sanitize_value(v, source_event_loop, target_event_loop, k)
112
119
  end
113
120
 
@@ -122,12 +129,15 @@ class Eventbox
122
129
  end
123
130
 
124
131
  arg2 = yield(arg)
125
-
126
- vs.each_index do |i|
132
+ rescue
133
+ # Restore the original object in case of Marshal.dump failure
134
+ vs.each_with_index do |v, i|
127
135
  arg[i] = vs[i]
128
136
  end
129
-
137
+ raise
138
+ else
130
139
  vs.each_with_index do |v, i|
140
+ arg[i] = vs[i]
131
141
  v2 = sanitize_value(v, source_event_loop, target_event_loop, name)
132
142
  arg2[i] = v2
133
143
  end
@@ -158,9 +168,9 @@ class Eventbox
158
168
  unless mel == source_event_loop
159
169
  raise InvalidAccess, "object #{arg.inspect} #{"wrapped by #{name} " if name} was marked as shared_object in a different eventbox object than the calling eventbox"
160
170
  end
161
- WrappedObject.new(arg, mel, name)
171
+ wrap_object(arg, mel, target_event_loop, name)
162
172
  when ExternalSharedObject # External object marked as shared_object
163
- WrappedObject.new(arg, source_event_loop, name)
173
+ wrap_object(arg, source_event_loop, target_event_loop, name)
164
174
  else
165
175
  # Not tagged -> try to deep copy the object
166
176
  begin
@@ -219,16 +229,23 @@ class Eventbox
219
229
  args.map { |arg| sanitize_value(arg, source_event_loop, target_event_loop, name) }
220
230
  end
221
231
 
232
+ def sanitize_kwargs(args, source_event_loop, target_event_loop, name=nil)
233
+ args.transform_values { |arg| sanitize_value(arg, source_event_loop, target_event_loop, name) }
234
+ end
235
+
222
236
  def wrap_proc(arg, name, source_event_loop, target_event_loop)
223
237
  if target_event_loop&.event_scope?
224
- ExternalProc.new(arg, source_event_loop, name) do |*args, &block|
238
+ ExternalProc.new(arg, source_event_loop, name) do |*args, **kwargs, &block|
225
239
  if target_event_loop&.event_scope?
226
240
  # called in the event scope
241
+
227
242
  if block && !(WrappedProc === block)
228
243
  raise InvalidAccess, "calling #{arg.inspect} with block argument #{block.inspect} is not allowed - use async_proc, sync_proc, yield_proc or an external proc instead"
229
244
  end
230
- cbblock = args.last if Proc === args.last
231
- target_event_loop._external_proc_call(arg, name, args, block, cbblock, source_event_loop)
245
+
246
+ call_context = args.shift if CallContext === args.first
247
+ cbblock = args.pop if Proc === args.last
248
+ target_event_loop._external_object_call(arg, :call, name, args, kwargs, block, cbblock, source_event_loop, call_context)
232
249
  else
233
250
  # called externally
234
251
  raise InvalidAccess, "external proc #{arg.inspect} #{"wrapped by #{name} " if name} can not be called in a different eventbox instance"
@@ -238,6 +255,14 @@ class Eventbox
238
255
  WrappedObject.new(arg, source_event_loop, name)
239
256
  end
240
257
  end
258
+
259
+ def wrap_object(object, source_event_loop, target_event_loop, name)
260
+ if target_event_loop&.event_scope?
261
+ ExternalObject.new(object, source_event_loop, target_event_loop, name)
262
+ else
263
+ WrappedObject.new(object, source_event_loop, name)
264
+ end
265
+ end
241
266
  end
242
267
 
243
268
  # Generic wrapper for objects that are passed through a foreign scope as reference.
@@ -245,19 +270,87 @@ class Eventbox
245
270
  # Access to the object from a different scope is denied, but the wrapper object can be stored and passed back to the origin scope to unwrap it.
246
271
  class WrappedObject
247
272
  attr_reader :name
273
+ # @private
248
274
  def initialize(object, event_loop, name=nil)
249
- @object = object
250
- @event_loop = event_loop
251
- @name = name
275
+ @object = object # the object to be wrapped
276
+ @event_loop = event_loop # the event_loop @object originates from, nil if external scope
277
+ @name = name # some information to get more meaningful error messages
252
278
  @dont_marshal = ExternalSharedObject # protect self from being marshaled
253
279
  end
254
280
 
281
+ # @private
255
282
  def object_for(target_event_loop)
256
283
  @event_loop == target_event_loop ? @object : self
257
284
  end
258
285
 
259
286
  def inspect
260
- "#<#{self.class} @object=#{@object.inspect} @name=#{@name.inspect}>"
287
+ el = case @event_loop
288
+ when EventLoop then @event_loop.object_id.to_s(16)
289
+ else @event_loop.inspect
290
+ end
291
+ "#<#{self.class} @object=#{@object.inspect} @name=#{@name.inspect} @event_loop=#{el}>"
292
+ end
293
+ end
294
+
295
+ # Wrapper for objects created external or in the action scope of some Eventbox instance.
296
+ #
297
+ # External objects can be called from event scope by {ExternalObject#send}.
298
+ #
299
+ # External objects can also be passed to action or to external scope.
300
+ # In this case a {ExternalObject} is unwrapped back to the ordinary object.
301
+ #
302
+ # @see ExternalProc
303
+ class ExternalObject < WrappedObject
304
+ # @private
305
+ def initialize(object, event_loop, target_event_loop, name=nil)
306
+ super(object, event_loop, name)
307
+ @target_event_loop = target_event_loop
308
+ end
309
+
310
+ # Invoke the external objects within the event scope.
311
+ #
312
+ # It can be called within {Eventbox::Boxable#sync_call sync_call} and {Eventbox::Boxable#yield_call yield_call} methods and from {Eventbox#sync_proc} and {Eventbox#yield_proc} closures.
313
+ # The method then runs in the background on the thread that called the event scope method in execution.
314
+ #
315
+ # It's also possible to invoke the external object with an explicit {Eventbox::CallContext} instead of the implicit call context of a sync or yield call.
316
+ # The explicit {Eventbox::CallContext} must be given as the very first parameter and it is not passed to the object call.
317
+ # The object call is then done in the given context of an arbitrary event scope method or closure that didn't return yet.
318
+ # In this case the method runs in the background on the thread that is waiting for the call to return.
319
+ #
320
+ # If the call to the external object doesn't return immediately, it blocks the calling thread or the thread of the {Eventbox::CallContext}.
321
+ # If this is not desired, an {Eventbox::Boxable#action action} can be used instead, to invoke the method of the object on a dedicated thread.
322
+ # However in any case calling the external object doesn't block the Eventbox instance itself.
323
+ # It still keeps responsive to calls from other threads.
324
+ #
325
+ # Optionally a proc can be provided as the last argument which acts as a completion callback.
326
+ # This proc is invoked, when the call has finished, with the result value as argument.
327
+ #
328
+ # class Sender < Eventbox
329
+ # sync_call def init(€obj) # €-variables are passed as reference instead of copy
330
+ # # invoke the object given to Sender.new
331
+ # # and when completed, print the result of strip
332
+ # €obj.send :strip, ->(res){ p res }
333
+ # end
334
+ # end
335
+ # Sender.new(" a b c ") # Output: "a b c"
336
+ def send(method, *args, **kwargs, &block)
337
+ if @target_event_loop&.event_scope?
338
+ # called in the event scope
339
+ if CallContext === method
340
+ call_context = method
341
+ method = args.shift
342
+ end
343
+
344
+ if block && !(WrappedProc === block)
345
+ raise InvalidAccess, "calling `#{method}' with block argument #{block.inspect} is not allowed - use async_proc, sync_proc, yield_proc or an external proc instead"
346
+ end
347
+
348
+ cbblock = args.pop if Proc === args.last
349
+ @target_event_loop._external_object_call(@object, method, @name, args, kwargs, block, cbblock, @event_loop, call_context)
350
+ else
351
+ # called externally
352
+ raise InvalidAccess, "external object #{self.inspect} #{"wrapped by #{name} " if name} can not be called in a different eventbox instance"
353
+ end
261
354
  end
262
355
  end
263
356
 
@@ -283,9 +376,21 @@ class Eventbox
283
376
 
284
377
  WrappedException = Struct.new(:exc)
285
378
 
286
- # Proc object provided as the last argument of {Eventbox.yield_call} and {Eventbox#yield_proc}.
379
+ # Proc object provided as the last argument of {Eventbox::Boxable#yield_call yield_call} and {Eventbox#yield_proc}.
380
+ #
381
+ # Used to let the corresponding yield call return a value:
382
+ # class MyBox < Eventbox
383
+ # yield_call def number(result)
384
+ # result.yield 42
385
+ # end
386
+ # end
387
+ # MyBox.new.number # => 42
388
+ #
389
+ # Alternatively the yield call can respond with an exception by {CompletionProc#raise}.
287
390
  class CompletionProc < AsyncProc
288
- # Raise an exception in the context of the waiting {Eventbox.yield_call} or {Eventbox#yield_proc} method.
391
+ include CallContext
392
+
393
+ # Raise an exception in the context of the waiting {Eventbox::Boxable#yield_call yield_call} or {Eventbox#yield_proc} method.
289
394
  #
290
395
  # This allows to raise an exception to the calling scope from external or action scope:
291
396
  #
@@ -300,38 +405,49 @@ class Eventbox
300
405
  # end
301
406
  # MyBox.new # => raises RuntimeError (raise from action MyBox#process)
302
407
  #
303
- # In contrast to a direct call of `Kernel.raise`, calling this method doesn't abort the current context.
408
+ # In contrast to a direct call of +Kernel.raise+, calling this method doesn't abort the current context.
304
409
  # Instead when in the event scope, raising the exception is deferred until returning to the calling external or action scope.
305
410
  def raise(*args)
306
411
  self.call(WrappedException.new(args))
307
412
  end
308
413
  end
309
414
 
310
- # Wrapper for Proc objects created external of some Eventbox instance.
415
+ # Wrapper for Proc objects created external or in the action scope of some Eventbox instance.
416
+ #
417
+ # External Proc objects can be invoked from event scope by {ExternalProc#call}.
418
+ # It can be called within {Eventbox::Boxable#sync_call sync_call} and {Eventbox::Boxable#yield_call yield_call} methods and from {Eventbox#sync_proc} and {Eventbox#yield_proc} closures.
419
+ # The proc then runs in the background on the thread that called the event scope method in execution.
420
+ #
421
+ # It's also possible to invoke it within a {Eventbox::Boxable#async_call async_call} or {Eventbox#async_proc}, when the method or proc that brought the external proc into the event scope, is a yield call that didn't return yet.
422
+ # In this case the proc runs in the background on the thread that is waiting for the yield call to return.
311
423
  #
312
- # External Proc objects can be invoked from event scope through {Eventbox.sync_call} and {Eventbox.yield_call} methods.
313
424
  # Optionally a proc can be provided as the last argument which acts as a completion callback.
314
425
  # This proc is invoked, when the call has finished, with the result value as argument.
315
426
  #
316
427
  # class Callback < Eventbox
317
428
  # sync_call def init(&block)
318
- # block.call(5, proc do |res| # invoke the block given to Callback.new
319
- # p res # print the block result (5 + 1)
320
- # end)
429
+ # # invoke the block given to Callback.new
430
+ # # and when completed, print the result of the block
431
+ # block.call 5, ->(res){ p res }
321
432
  # end
322
433
  # end
323
434
  # Callback.new {|num| num + 1 } # Output: 6
324
435
  #
325
436
  # External Proc objects can also be passed to action or to external scope.
326
- # In this case a {ExternalProc} is unwrapped back to an ordinary Proc object.
437
+ # In this case a {ExternalProc} is unwrapped back to the ordinary Proc object.
438
+ #
439
+ # @see ExternalObject
327
440
  class ExternalProc < WrappedProc
441
+ # @private
328
442
  attr_reader :name
443
+ # @private
329
444
  def initialize(object, event_loop, name=nil)
330
445
  @object = object
331
446
  @event_loop = event_loop
332
447
  @name = name
333
448
  end
334
449
 
450
+ # @private
335
451
  def object_for(target_event_loop)
336
452
  @event_loop == target_event_loop ? @object : self
337
453
  end
@@ -68,6 +68,10 @@ class Eventbox
68
68
  end
69
69
  end
70
70
 
71
+ async_call def terminate
72
+ @action.abort
73
+ end
74
+
71
75
  # @private
72
76
  async_call def __start__(action, input)
73
77
  # Send the block to the start_pool_thread as result of next_job
@@ -120,6 +124,8 @@ class Eventbox
120
124
  Thread.handle_interrupt(Exception => :immediate) do
121
125
  sleep # Aborted by the exception
122
126
  end
127
+ rescue Eventbox::AbortAction
128
+ raise # The thread-pool was requested to shutdown
123
129
  rescue Exception
124
130
  end
125
131
  end
@@ -166,5 +172,9 @@ class Eventbox
166
172
  private async_call def gc_finished
167
173
  @run_gc_when_busy = true
168
174
  end
175
+
176
+ def inspect
177
+ "#<#{self.class}:#{self.object_id} @requests=#{@requests.length} @jobless=#{@jobless.length} @run_gc_when_busy=#{@run_gc_when_busy.inspect}>"
178
+ end
169
179
  end
170
180
  end
@@ -9,28 +9,36 @@ class Eventbox
9
9
  # include Eventbox::Timer
10
10
  #
11
11
  # async_call def init
12
- # super # make sure Timer#init is called
12
+ # super # Make sure Timer#init is called
13
13
  # timer_after(1) do
14
14
  # puts "one second elapsed"
15
15
  # end
16
16
  # end
17
17
  # end
18
18
  #
19
+ # MyBox.new # Schedule the alarm after 1 sec
20
+ # sleep 2 # Wait for the timer to be triggered
21
+ #
19
22
  # The main functions are timer_after and timer_every.
20
23
  # They schedule asynchronous calls to the given block:
21
- # timer_after(3) do
24
+ # timer_after(3.0) do
22
25
  # # executed once after 3 seconds
23
26
  # end
24
27
  #
25
- # timer_every(3) do
26
- # # executed repeatedly every 3 seconds
28
+ # timer_every(1.5) do
29
+ # # executed repeatedly every 1.5 seconds
27
30
  # end
28
31
  #
29
- # Both functions return an Alarm object which can be used to cancel the alarm through timer_cancel.
32
+ # Both functions return an {Alarm} object which can be used to cancel the alarm through {timer_cancel}.
30
33
  #
31
- # timer_after, timer_every and timer_cancel can be used within the event scope, in actions and from external scope.
34
+ # {Timer} always uses one {Eventbox::Boxable#action action} thread per Eventbox object, regardless of the number of scheduled timers.
35
+ # All alarms are called from this thread.
36
+ # {timer_after}, {timer_every} and {timer_cancel} can be used within the {file:README.md#event-scope event scope}, in actions and from external scope.
37
+ # Alarms defined within the event scope must be non-blocking, as any other code in the event scope.
38
+ # Alarms defined in action or external scope should also avoid blocking code, otherwise one alarm can delay the next alarm.
32
39
  #
33
- # {Timer} always uses one action thread per Eventbox object, regardless of the number of scheduled timers.
40
+ # *Note:* Each object that includes the {Timer} module must be explicit terminated by {Eventbox#shutdown!}.
41
+ # It is (currently) not freed by the garbarge collector.
34
42
  module Timer
35
43
  class Reload < RuntimeError
36
44
  end
@@ -124,6 +132,8 @@ class Eventbox
124
132
  end
125
133
 
126
134
  # Cancel an alarm previously scheduled per timer_after or timer_every
135
+ #
136
+ # The method does nothing, if the alarm is no longer active.
127
137
  async_call def timer_cancel(alarm)
128
138
  a = @timer_alarms.delete(alarm)
129
139
  if a
@@ -1,5 +1,5 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  class Eventbox
4
- VERSION = "0.1.0"
4
+ VERSION = "1.0.1"
5
5
  end
data/lib/eventbox.rb CHANGED
@@ -2,10 +2,11 @@
2
2
 
3
3
  require "weakref"
4
4
  require "eventbox/argument_wrapper"
5
- require "eventbox/sanitizer"
5
+ require "eventbox/call_context"
6
6
  require "eventbox/boxable"
7
7
  require "eventbox/event_loop"
8
8
  require "eventbox/object_registry"
9
+ require "eventbox/sanitizer"
9
10
 
10
11
  class Eventbox
11
12
  autoload :VERSION, "eventbox/version"
@@ -18,7 +19,7 @@ class Eventbox
18
19
  class MultipleResults < RuntimeError; end
19
20
  class AbortAction < RuntimeError; end
20
21
 
21
- if RUBY_ENGINE=='jruby' && RUBY_VERSION.split(".").map(&:to_i).pack("C*") < [9,2,1,0].pack("C*") ||
22
+ if RUBY_ENGINE=='jruby' && JRUBY_VERSION.split(".").map(&:to_i).pack("C*") < [9,2,1,0].pack("C*") ||
22
23
  RUBY_ENGINE=='truffleruby'
23
24
  # This is a workaround for bug https://github.com/jruby/jruby/issues/5314
24
25
  # which was fixed in JRuby-9.2.1.0.
@@ -90,7 +91,7 @@ class Eventbox
90
91
  # Create a new {Eventbox} instance.
91
92
  #
92
93
  # All arguments are passed to the init() method when defined.
93
- def initialize(*args, &block)
94
+ def initialize(*args, **kwargs, &block)
94
95
  options = self.class.eventbox_options
95
96
 
96
97
  # This instance variable is set to self here, but replaced by Boxable#action to a WeakRef
@@ -104,7 +105,7 @@ class Eventbox
104
105
  self.class.instance_variable_set(:@eventbox_methods_checked, true)
105
106
 
106
107
  obj = Object.new
107
- meths = methods - obj.methods - [:__getobj__, :shutdown!, :shared_object]
108
+ meths = methods - obj.methods - [:__getobj__, :shutdown!, :shared_object, :€]
108
109
  prmeths = private_methods - obj.private_methods
109
110
  prohib = meths.find do |name|
110
111
  !prmeths.include?(:"__#{name}__")
@@ -120,7 +121,7 @@ class Eventbox
120
121
  @__event_loop__ = EventLoop.new(options[:threadpool], options[:guard_time])
121
122
  ObjectSpace.define_finalizer(self, @__event_loop__.method(:send_shutdown))
122
123
 
123
- init(*args, &block)
124
+ init(*args, **kwargs, &block)
124
125
  end
125
126
 
126
127
  def self.method_added(name)
@@ -240,19 +241,70 @@ class Eventbox
240
241
  # Mark an object as to be shared instead of copied.
241
242
  #
242
243
  # A marked object is never passed as copy, but passed as reference.
243
- # The object is therefore wrapped as {WrappedObject} when used in an unsafe scope.
244
- # Wrapping as {WrappedObject} denies access from external/action scope to event scope objects and vice versa.
245
- # It also denies access to objects originated from a foreign event scope.
246
- # However the object can be passed as reference and is automatically unwrapped when passed back to the original scope.
244
+ # The object is therefore wrapped as {WrappedObject} or {ExternalObject} when used in an unsafe scope.
245
+ # Objects marked within the event scope are wrapped as {WrappedObject}, which denies access from external/action scope or the event scope of a different Eventbox instance.
246
+ # Objects marked in external/action scope are wrapped as {ExternalObject} which allows {External.send asynchronous calls} from event scope.
247
+ # In all cases the object can be passed as reference and is automatically unwrapped when passed back to the original scope.
247
248
  # It can therefore be used to modify the original object even after traversing the boundaries.
248
249
  #
249
- # Wrapping and unwrapping works even if the shared object is stored within another object as instance variable or within a collection class.
250
- #
251
250
  # The mark is stored for the lifetime of the object, so that it's enough to mark only once at object creation.
251
+ #
252
+ # Due to {Eventbox::Sanitizer Sanitizer dissection} of non-marshalable objects, wrapping and unwrapping works even if the shared object is stored within another object as instance variable or within a collection class.
253
+ # This is in contrast to €-variables which can only wrap the argument object as a whole when entering the event scope.
254
+ # See the difference here:
255
+ #
256
+ # A = Struct.new(:a)
257
+ # class Bo < Eventbox
258
+ # sync_call def go(struct, €struct)
259
+ # p struct # prints #<struct A a=#<Eventbox::ExternalObject @object="abc" @name=:a>>
260
+ # p €struct # prints #<Eventbox::ExternalObject @object=#<struct A a="abc"> @name=:€struct>
261
+ # [struct, €struct]
262
+ # end
263
+ # end
264
+ # e = Bo.new
265
+ # o = A.new(e.shared_object("abc"))
266
+ # e.go(o, o) # => [#<struct A a="abc">, #<struct A a="abc">]
252
267
  public def shared_object(object)
253
268
  @__event_loop__.shared_object(object)
254
269
  end
255
270
 
271
+ public def €(object)
272
+ @__event_loop__.€(object)
273
+ end
274
+
275
+ # Starts a new action dedicated to call external objects.
276
+ #
277
+ # It returns a {CallContext} which can be used with {Eventbox::ExternalObject#send} and {Eventbox::ExternalProc#call}.
278
+ #
279
+ # @returns [ActionCallContext] A new call context provided by a newly started action.
280
+ private def new_action_call_context
281
+ ActionCallContext.new(@__event_loop__)
282
+ end
283
+
284
+ # Get the context of the waiting external call within a yield or sync method or closure.
285
+ #
286
+ # Callable within the event scope only.
287
+ #
288
+ # @returns [BlockingExternalCallContext] The current call context.
289
+ # Returns +nil+ in async_call or async_proc context.
290
+ #
291
+ # Usable as first parameter to {ExternalProc.call} and {ExternalObject.send}.
292
+ private def call_context
293
+ if @__event_loop__.event_scope?
294
+ @__event_loop__._latest_call_context
295
+ else
296
+ raise InvalidAccess, "not in event scope"
297
+ end
298
+ end
299
+
300
+ private def with_call_context(ctx, &block)
301
+ if @__event_loop__.event_scope?
302
+ @__event_loop__.with_call_context(ctx, &block)
303
+ else
304
+ raise InvalidAccess, "not in event scope"
305
+ end
306
+ end
307
+
256
308
  # Force stop of all action threads spawned by this {Eventbox} instance.
257
309
  #
258
310
  # It is possible to enable automatic cleanup of action threads by the garbage collector through {Eventbox.with_options}.
data.tar.gz.sig CHANGED
Binary file