Module: Eventbox::Boxable
Overview
Instance Method Summary collapse
-
#action(name, &block) ⇒ Object
private
Define a private method for asynchronous execution.
-
#async_call(name, &block) ⇒ Object
private
Define a threadsafe method for asynchronous (fire-and-forget) calls.
-
#attr_accessor(*names) ⇒ Object
private
Threadsafe read and write access to instance variables.
-
#attr_reader(*names) ⇒ Object
private
Threadsafe read access to instance variables.
-
#attr_writer(*names) ⇒ Object
private
Threadsafe write access to instance variables.
-
#sync_call(name, &block) ⇒ Object
private
Define a method for synchronous calls.
-
#yield_call(name, &block) ⇒ Object
private
Define a method for calls with deferred result.
Instance Method Details
#action(name, &block) ⇒ Object (private)
Define a private method for asynchronous execution.
The call to the action method returns immediately after starting a new action. It returns an Action object. By default each call to an action method spawns a new thread which executes the code of the action definition. Alternatively a threadpool can be assigned by Eventbox.with_options.
All method arguments are passed through the Sanitizer.
Actions can return state changes or objects to the event loop by calls to methods created by #async_call, #sync_call or #yield_call or through calling Eventbox#async_proc, Eventbox#sync_proc or Eventbox#yield_proc objects. To avoid unsafe shared objects, an action has it’s own set of local variables or instance variables. It doesn’t have access to variables defined by other methods.
The Action object can be used to interrupt the program execution by an exception. See Action for further information. If the action method accepts one more argument than given to the action call, it is set to corresponding Action instance:
async_call def init
do_something("value1")
end
action def do_something(str, action)
str # => "value1"
action.current? # => true
# `action' can be passed to event scope or external scope,
# in order to send a signal per Action#raise
end
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 |
# File 'lib/eventbox/boxable.rb', line 202 def action(name, &block) unbound_method = self.instance_method(name) with_block_or_def(name, block) do |*args, &cb| raise InvalidAccess, "action must not be called with a block" if cb gc_actions = self.class.[:gc_actions] sandbox = self.class.allocate sandbox.instance_variable_set(:@__event_loop__, @__event_loop__) sandbox.instance_variable_set(:@__eventbox__, gc_actions ? WeakRef.new(self) : self) meth = unbound_method.bind(sandbox) if @__event_loop__.event_scope? args = Sanitizer.sanitize_values(args, @__event_loop__, nil) end # Start a new action thread and return an Action instance @__event_loop__.start_action(meth, name, args) end private name name end |
#async_call(name, &block) ⇒ Object (private)
Define a threadsafe method for asynchronous (fire-and-forget) calls.
The created method can be safely called from any thread. All method arguments are passed through the Sanitizer. Arguments prefixed by a € sign are automatically passed as ExternalObject.
The method itself might not do any blocking calls or expensive computations - this would impair responsiveness of the Eventbox instance. Instead use #action in these cases.
In contrast to #sync_call it’s not possible to call external blocks or proc objects from #async_call methods.
The method always returns self
to the caller.
47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/eventbox/boxable.rb', line 47 def async_call(name, &block) unbound_method = self.instance_method(name) wrapper = ArgumentWrapper.build(unbound_method, name) with_block_or_def(name, block) do |*args, **kwargs, &cb| if @__event_loop__.event_scope? # Use the correct method within the class hierarchy, instead of just self.send(*args). # Otherwise super() would start an infinite recursion. unbound_method.bind(eventbox).call(*args, **kwargs, &cb) else @__event_loop__.async_call(eventbox, name, args, kwargs, cb, wrapper) end self end end |
#attr_accessor(*names) ⇒ Object (private)
Threadsafe read and write access to instance variables.
Attention: Be careful with read-modify-write operations like “+=” - they are not atomic but are executed as two independent operations.
This will lose counter increments, since counter
is incremented in a non-atomic manner:
attr_accessor :counter
async_call def start
10.times { do_something }
end
action def do_something
self.counter += 1
end
Instead don’t use accessors but do increments within one method call like so:
async_call def start
10.times { do_something }
end
action def do_something
increment 1
end
async_call def increment(by)
@counter += by
end
168 169 170 171 172 173 174 |
# File 'lib/eventbox/boxable.rb', line 168 def attr_accessor(*names) super names.each do |name| async_call(:"#{name}=") sync_call(:"#{name}") end end |
#attr_reader(*names) ⇒ Object (private)
Threadsafe read access to instance variables.
138 139 140 141 142 143 |
# File 'lib/eventbox/boxable.rb', line 138 def attr_reader(*names) super names.each do |name| sync_call(:"#{name}") end end |
#attr_writer(*names) ⇒ Object (private)
Threadsafe write access to instance variables.
130 131 132 133 134 135 |
# File 'lib/eventbox/boxable.rb', line 130 def attr_writer(*names) super names.each do |name| async_call(:"#{name}=") end end |
#sync_call(name, &block) ⇒ Object (private)
Define a method for synchronous calls.
The created method can be safely called from any thread. It is simular to #async_call, but the method waits until the method body is executed and returns its return value. Since all processing within the event scope of an Eventbox instance must not involve blocking operations, sync calls can only return immediate values. For deferred results use #yield_call instead.
It’s possible to call external blocks or proc objects from #sync_call methods. Blocks are executed by the same thread that calls the #sync_call method to that time.
All method arguments as well as the result value are passed through the Sanitizer. Arguments prefixed by a € sign are automatically passed as ExternalObject.
The method itself might not do any blocking calls or expensive computations - this would impair responsiveness of the Eventbox instance. Instead use #action in these cases.
77 78 79 80 81 82 83 84 85 86 87 88 89 |
# File 'lib/eventbox/boxable.rb', line 77 def sync_call(name, &block) unbound_method = self.instance_method(name) wrapper = ArgumentWrapper.build(unbound_method, name) with_block_or_def(name, block) do |*args, **kwargs, &cb| if @__event_loop__.event_scope? unbound_method.bind(eventbox).call(*args, **kwargs, &cb) else answer_queue = Queue.new sel = @__event_loop__.sync_call(eventbox, name, args, kwargs, cb, answer_queue, wrapper) @__event_loop__.callback_loop(answer_queue, sel, name) end end end |
#yield_call(name, &block) ⇒ Object (private)
Define a method for calls with deferred result.
This call type is simular to #sync_call, however it’s not the result of the method that is returned. Instead the method is called with one additional argument in the event scope, which is used to yield a result value or raise an exception. In contrast to a return
statement, the execution of the method continues after yielding a result.
The result value can be yielded within the called method, but it can also be stored and called by any other event scope or external method, leading to a deferred method return. The external thread calling this method is suspended until a result is yielded. However the Eventbox object keeps responsive to calls from other threads.
The created method can be safely called from any thread. If yield methods are called in the event scope, they must get a Proc object as the last argument. It is called when a result was yielded.
It’s possible to call external blocks or proc objects from #yield_call methods up to the point when the result was yielded. Blocks are executed by the same thread that calls the #yield_call method to that time.
All method arguments as well as the result value are passed through the Sanitizer. Arguments prefixed by a € sign are automatically passed as ExternalObject.
The method itself as well as the Proc object might not do any blocking calls or expensive computations - this would impair responsiveness of the Eventbox instance. Instead use #action in these cases.
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/eventbox/boxable.rb', line 113 def yield_call(name, &block) unbound_method = self.instance_method(name) wrapper = ArgumentWrapper.build(unbound_method, name) with_block_or_def(name, block) do |*args, **kwargs, &cb| if @__event_loop__.event_scope? @__event_loop__.internal_yield_result(args, name) unbound_method.bind(eventbox).call(*args, **kwargs, &cb) self else answer_queue = Queue.new sel = @__event_loop__.yield_call(eventbox, name, args, kwargs, cb, answer_queue, wrapper) @__event_loop__.callback_loop(answer_queue, sel, name) end end end |