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
         |