polyphony 0.64 → 0.68
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
- data/.github/workflows/test.yml +1 -1
- data/CHANGELOG.md +22 -0
- data/Gemfile.lock +1 -1
- data/TODO.md +10 -40
- data/bin/pdbg +112 -0
- data/examples/core/await.rb +9 -1
- data/ext/polyphony/backend_common.c +14 -1
- data/ext/polyphony/backend_common.h +3 -1
- data/ext/polyphony/backend_io_uring.c +85 -25
- data/ext/polyphony/backend_io_uring_context.c +42 -0
- data/ext/polyphony/backend_io_uring_context.h +6 -9
- data/ext/polyphony/backend_libev.c +85 -39
- data/ext/polyphony/fiber.c +20 -0
- data/ext/polyphony/polyphony.c +2 -0
- data/ext/polyphony/polyphony.h +5 -2
- data/ext/polyphony/queue.c +1 -1
- data/ext/polyphony/runqueue.c +7 -3
- data/ext/polyphony/runqueue.h +4 -3
- data/ext/polyphony/runqueue_ring_buffer.c +25 -14
- data/ext/polyphony/runqueue_ring_buffer.h +2 -0
- data/ext/polyphony/thread.c +2 -8
- data/lib/polyphony.rb +6 -0
- data/lib/polyphony/debugger.rb +225 -0
- data/lib/polyphony/extensions/debug.rb +1 -1
- data/lib/polyphony/extensions/fiber.rb +64 -71
- data/lib/polyphony/extensions/io.rb +4 -2
- data/lib/polyphony/extensions/openssl.rb +66 -0
- data/lib/polyphony/extensions/socket.rb +8 -2
- data/lib/polyphony/net.rb +1 -0
- data/lib/polyphony/version.rb +1 -1
- data/test/helper.rb +6 -5
- data/test/stress.rb +6 -2
- data/test/test_backend.rb +13 -4
- data/test/test_fiber.rb +35 -11
- data/test/test_global_api.rb +9 -4
- data/test/test_io.rb +2 -0
- data/test/test_socket.rb +14 -11
- data/test/test_supervise.rb +24 -24
- data/test/test_thread.rb +3 -0
- data/test/test_thread_pool.rb +1 -1
- data/test/test_throttler.rb +2 -2
- data/test/test_timer.rb +5 -3
- metadata +5 -3
| @@ -29,4 +29,6 @@ void runqueue_ring_buffer_push(runqueue_ring_buffer *buffer, VALUE fiber, VALUE | |
| 29 29 | 
             
            void runqueue_ring_buffer_delete(runqueue_ring_buffer *buffer, VALUE fiber);
         | 
| 30 30 | 
             
            int runqueue_ring_buffer_index_of(runqueue_ring_buffer *buffer, VALUE fiber);
         | 
| 31 31 |  | 
| 32 | 
            +
            void runqueue_ring_buffer_migrate(runqueue_ring_buffer *src, runqueue_ring_buffer *dest, VALUE fiber);
         | 
| 33 | 
            +
             | 
| 32 34 | 
             
            #endif /* RUNQUEUE_RING_BUFFER_H */
         | 
    
        data/ext/polyphony/thread.c
    CHANGED
    
    | @@ -22,14 +22,12 @@ VALUE Thread_fiber_unschedule(VALUE self, VALUE fiber) { | |
| 22 22 | 
             
              return self;
         | 
| 23 23 | 
             
            }
         | 
| 24 24 |  | 
| 25 | 
            -
             | 
| 25 | 
            +
            inline void Thread_schedule_fiber(VALUE self, VALUE fiber, VALUE value) {
         | 
| 26 26 | 
             
              schedule_fiber(self, fiber, value, 0);
         | 
| 27 | 
            -
              return self;
         | 
| 28 27 | 
             
            }
         | 
| 29 28 |  | 
| 30 | 
            -
             | 
| 29 | 
            +
            inline void Thread_schedule_fiber_with_priority(VALUE self, VALUE fiber, VALUE value) {
         | 
| 31 30 | 
             
              schedule_fiber(self, fiber, value, 1);
         | 
| 32 | 
            -
              return self;
         | 
| 33 31 | 
             
            }
         | 
| 34 32 |  | 
| 35 33 | 
             
            VALUE Thread_switch_fiber(VALUE self) {
         | 
| @@ -61,10 +59,6 @@ VALUE Thread_class_backend(VALUE _self) { | |
| 61 59 | 
             
            void Init_Thread() {
         | 
| 62 60 | 
             
              rb_define_method(rb_cThread, "setup_fiber_scheduling", Thread_setup_fiber_scheduling, 0);
         | 
| 63 61 | 
             
              rb_define_method(rb_cThread, "schedule_and_wakeup", Thread_fiber_schedule_and_wakeup, 2);
         | 
| 64 | 
            -
             | 
| 65 | 
            -
              rb_define_method(rb_cThread, "schedule_fiber", Thread_schedule_fiber, 2);
         | 
| 66 | 
            -
              rb_define_method(rb_cThread, "schedule_fiber_with_priority",
         | 
| 67 | 
            -
                Thread_schedule_fiber_with_priority, 2);
         | 
| 68 62 | 
             
              rb_define_method(rb_cThread, "switch_fiber", Thread_switch_fiber, 0);
         | 
| 69 63 | 
             
              rb_define_method(rb_cThread, "fiber_unschedule", Thread_fiber_unschedule, 1);
         | 
| 70 64 |  | 
    
        data/lib/polyphony.rb
    CHANGED
    
    | @@ -121,3 +121,9 @@ end | |
| 121 121 |  | 
| 122 122 | 
             
            Polyphony.install_terminating_signal_handlers
         | 
| 123 123 | 
             
            Polyphony.install_at_exit_handler
         | 
| 124 | 
            +
             | 
| 125 | 
            +
            if (debug_socket_path = ENV['POLYPHONY_DEBUG_SOCKET_PATH'])
         | 
| 126 | 
            +
              puts "Starting debug server on #{debug_socket_path}"
         | 
| 127 | 
            +
              require 'polyphony/debugger'
         | 
| 128 | 
            +
              Polyphony.start_debug_server(debug_socket_path)
         | 
| 129 | 
            +
            end
         | 
| @@ -0,0 +1,225 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'polyphony/extensions/debug'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Polyphony
         | 
| 6 | 
            +
              TP_EVENTS = [
         | 
| 7 | 
            +
                :line,
         | 
| 8 | 
            +
                :call,
         | 
| 9 | 
            +
                :return,
         | 
| 10 | 
            +
                :b_call,
         | 
| 11 | 
            +
                :b_return
         | 
| 12 | 
            +
              ]    
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              def self.start_debug_server(socket_path)
         | 
| 15 | 
            +
                server = DebugServer.new(socket_path)
         | 
| 16 | 
            +
                controller = DebugController.new(server)      
         | 
| 17 | 
            +
                trace = TracePoint.new(*TP_EVENTS) { |tp| controller.handle_tp(trace, tp) }
         | 
| 18 | 
            +
                trace.enable
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                at_exit do
         | 
| 21 | 
            +
                  Kernel.trace "program terminated"
         | 
| 22 | 
            +
                  trace.disable
         | 
| 23 | 
            +
                  server.stop
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
              end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
              class DebugController
         | 
| 28 | 
            +
                def initialize(server)
         | 
| 29 | 
            +
                  @server = server
         | 
| 30 | 
            +
                  @server.wait_for_client
         | 
| 31 | 
            +
                  @state = { fibers: {} }
         | 
| 32 | 
            +
                  @control_fiber = Fiber.new { |f| control_loop(f) }
         | 
| 33 | 
            +
                  @control_fiber.transfer Fiber.current
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                def control_loop(source_fiber)
         | 
| 37 | 
            +
                  @peer = source_fiber
         | 
| 38 | 
            +
                  cmd = { cmd: :initial }
         | 
| 39 | 
            +
                  loop do
         | 
| 40 | 
            +
                    cmd = send(:"cmd_#{cmd[:cmd]}", cmd)
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                POLYPHONY_LIB_DIR = File.expand_path('..', __dir__)
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                def get_next_trace_event
         | 
| 47 | 
            +
                  @peer.transfer.tap { |e| update_state(e) }
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                def update_state(event)
         | 
| 51 | 
            +
                  trace update_state: event
         | 
| 52 | 
            +
                  @state[:fiber] = event[:fiber]
         | 
| 53 | 
            +
                  @state[:path] = event[:path]
         | 
| 54 | 
            +
                  @state[:lineno] = event[:lineno]
         | 
| 55 | 
            +
                  update_fiber_state(event)
         | 
| 56 | 
            +
                end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                def update_fiber_state(event)
         | 
| 59 | 
            +
                  fiber_state = @state[:fibers][event[:fiber]] ||= { stack: [] }
         | 
| 60 | 
            +
                  case event[:kind]
         | 
| 61 | 
            +
                  when :call, :c_call, :b_call
         | 
| 62 | 
            +
                    fiber_state[:stack] << event
         | 
| 63 | 
            +
                  when :return, :c_return, :b_return
         | 
| 64 | 
            +
                    fiber_state[:stack].pop
         | 
| 65 | 
            +
                  end
         | 
| 66 | 
            +
                  fiber_state[:binding] = event[:binding]
         | 
| 67 | 
            +
                  fiber_state[:path] = event[:path]
         | 
| 68 | 
            +
                  fiber_state[:lineno] = event[:lineno]
         | 
| 69 | 
            +
                end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                def state_presentation(state)
         | 
| 72 | 
            +
                  {
         | 
| 73 | 
            +
                    fiber:  fiber_id(state[:fiber]),
         | 
| 74 | 
            +
                    path:   state[:path],
         | 
| 75 | 
            +
                    lineno: state[:lineno]
         | 
| 76 | 
            +
                  }
         | 
| 77 | 
            +
                end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                def fiber_id(fiber)
         | 
| 80 | 
            +
                  {
         | 
| 81 | 
            +
                    object_id: fiber.object_id,
         | 
| 82 | 
            +
                    tag: fiber.tag
         | 
| 83 | 
            +
                  }
         | 
| 84 | 
            +
                end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                def fiber_representation(fiber)
         | 
| 87 | 
            +
                  {
         | 
| 88 | 
            +
                    object_id: fiber.object_id,
         | 
| 89 | 
            +
                    tag: fiber.tag,
         | 
| 90 | 
            +
                    parent: fiber.parent && fiber_id(fiber.parent),
         | 
| 91 | 
            +
                    children: fiber.children.map { |c| fiber_id(c) }
         | 
| 92 | 
            +
                  }
         | 
| 93 | 
            +
                end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                def get_next_command(info)
         | 
| 96 | 
            +
                  @server.get_command(info)
         | 
| 97 | 
            +
                end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                def cmd_initial(cmd)
         | 
| 100 | 
            +
                  get_next_command(nil)
         | 
| 101 | 
            +
                end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                def info_listing(state)
         | 
| 104 | 
            +
                  {
         | 
| 105 | 
            +
                    kind: :listing,
         | 
| 106 | 
            +
                    fiber: fiber_id(state[:fiber]),
         | 
| 107 | 
            +
                    path: state[:path],
         | 
| 108 | 
            +
                    lineno: state[:lineno]
         | 
| 109 | 
            +
                  }
         | 
| 110 | 
            +
                end
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                def info_state(state)
         | 
| 113 | 
            +
                  info_listing(state).merge(
         | 
| 114 | 
            +
                    kind: :state,
         | 
| 115 | 
            +
                    fibers: info_fiber_states(state[:fibers])
         | 
| 116 | 
            +
                  )
         | 
| 117 | 
            +
                end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                def info_fiber_states(fiber_states)
         | 
| 120 | 
            +
                  fiber_states.inject({}) do |h, (f, s)|
         | 
| 121 | 
            +
                    h[fiber_id(f)] = {
         | 
| 122 | 
            +
                      stack: s[:stack].map { |e| { path: e[:path], lineno: e[:lineno] } }
         | 
| 123 | 
            +
                    }
         | 
| 124 | 
            +
                    h
         | 
| 125 | 
            +
                  end
         | 
| 126 | 
            +
                end
         | 
| 127 | 
            +
                
         | 
| 128 | 
            +
                def cmd_step(cmd)
         | 
| 129 | 
            +
                  tp = nil
         | 
| 130 | 
            +
                  fiber = nil
         | 
| 131 | 
            +
                  while true
         | 
| 132 | 
            +
                    event = get_next_trace_event
         | 
| 133 | 
            +
                    @peer = event[:fiber]
         | 
| 134 | 
            +
                    if event[:kind] == :line && event[:path] !~ /#{POLYPHONY_LIB_DIR}/
         | 
| 135 | 
            +
                      return get_next_command(info_listing(@state))
         | 
| 136 | 
            +
                    end
         | 
| 137 | 
            +
                  end
         | 
| 138 | 
            +
                rescue => e
         | 
| 139 | 
            +
                  trace "Uncaught error: #{e.inspect}"
         | 
| 140 | 
            +
                  @trace&.disable
         | 
| 141 | 
            +
                end
         | 
| 142 | 
            +
             | 
| 143 | 
            +
                def cmd_help(cmd)
         | 
| 144 | 
            +
                  get_next_command(kind: :help)
         | 
| 145 | 
            +
                end
         | 
| 146 | 
            +
             | 
| 147 | 
            +
                def cmd_list(cmd)
         | 
| 148 | 
            +
                  get_next_command(info_listing(@state))
         | 
| 149 | 
            +
                end
         | 
| 150 | 
            +
             | 
| 151 | 
            +
                def cmd_state(cmd)
         | 
| 152 | 
            +
                  get_next_command(info_state(@state))
         | 
| 153 | 
            +
                end
         | 
| 154 | 
            +
             | 
| 155 | 
            +
                def handle_tp(trace, tp)
         | 
| 156 | 
            +
                  return if Thread.current == @server.thread
         | 
| 157 | 
            +
                  return if Fiber.current == @control_fiber
         | 
| 158 | 
            +
             | 
| 159 | 
            +
                  kind = tp.event
         | 
| 160 | 
            +
                  event = {
         | 
| 161 | 
            +
                    fiber: Fiber.current,
         | 
| 162 | 
            +
                    kind: kind,
         | 
| 163 | 
            +
                    path: tp.path,
         | 
| 164 | 
            +
                    lineno: tp.lineno,
         | 
| 165 | 
            +
                    binding: tp.binding
         | 
| 166 | 
            +
                  }
         | 
| 167 | 
            +
                  case kind
         | 
| 168 | 
            +
                  when :call, :c_call, :b_call
         | 
| 169 | 
            +
                    event[:method_id] = tp.method_id
         | 
| 170 | 
            +
                    event[:parameters] = tp.parameters
         | 
| 171 | 
            +
                  when :return, :c_return, :b_return
         | 
| 172 | 
            +
                    event[:method_id] = tp.method_id
         | 
| 173 | 
            +
                    event[:return_value] = tp.return_value
         | 
| 174 | 
            +
                  end
         | 
| 175 | 
            +
                  @control_fiber.transfer(event)
         | 
| 176 | 
            +
                end
         | 
| 177 | 
            +
              end
         | 
| 178 | 
            +
             | 
| 179 | 
            +
              class DebugServer
         | 
| 180 | 
            +
                attr_reader :thread
         | 
| 181 | 
            +
             | 
| 182 | 
            +
                def initialize(socket_path)
         | 
| 183 | 
            +
                  @socket_path = socket_path
         | 
| 184 | 
            +
                  @fiber = Fiber.current
         | 
| 185 | 
            +
                  start_server_thread
         | 
| 186 | 
            +
                end
         | 
| 187 | 
            +
             | 
| 188 | 
            +
                def start_server_thread
         | 
| 189 | 
            +
                  @thread = Thread.new do
         | 
| 190 | 
            +
                    puts("Listening on #{@socket_path}")
         | 
| 191 | 
            +
                    FileUtils.rm(@socket_path) if File.exists?(@socket_path)
         | 
| 192 | 
            +
                    socket = UNIXServer.new(@socket_path)
         | 
| 193 | 
            +
                    loop do
         | 
| 194 | 
            +
                      @client = socket.accept
         | 
| 195 | 
            +
                    end
         | 
| 196 | 
            +
                  end
         | 
| 197 | 
            +
                end
         | 
| 198 | 
            +
             | 
| 199 | 
            +
                def stop
         | 
| 200 | 
            +
                  @thread.kill
         | 
| 201 | 
            +
                end
         | 
| 202 | 
            +
             | 
| 203 | 
            +
                def handle_client(client)
         | 
| 204 | 
            +
                  @client = client
         | 
| 205 | 
            +
                end
         | 
| 206 | 
            +
             | 
| 207 | 
            +
                def wait_for_client
         | 
| 208 | 
            +
                  sleep 0.1 until @client
         | 
| 209 | 
            +
                  msg = @client.gets
         | 
| 210 | 
            +
                  @client.puts msg
         | 
| 211 | 
            +
                end
         | 
| 212 | 
            +
             | 
| 213 | 
            +
                def get_command(info)
         | 
| 214 | 
            +
                  @client&.orig_write "#{info.inspect}\n"
         | 
| 215 | 
            +
                  cmd = @client&.orig_gets&.chomp
         | 
| 216 | 
            +
                  eval(cmd)
         | 
| 217 | 
            +
                rescue SystemCallError
         | 
| 218 | 
            +
                  nil
         | 
| 219 | 
            +
                rescue => e
         | 
| 220 | 
            +
                  trace "Error in interact_with_client: #{e.inspect}"
         | 
| 221 | 
            +
                  e.backtrace[0..3].each { |l| trace l }
         | 
| 222 | 
            +
                  @client = nil
         | 
| 223 | 
            +
                end
         | 
| 224 | 
            +
              end
         | 
| 225 | 
            +
            end
         | 
| @@ -7,6 +7,10 @@ require_relative '../core/exceptions' | |
| 7 7 | 
             
            module Polyphony
         | 
| 8 8 | 
             
              # Fiber control API
         | 
| 9 9 | 
             
              module FiberControl
         | 
| 10 | 
            +
                def monitor_mailbox
         | 
| 11 | 
            +
                  @monitor_mailbox ||= Polyphony::Queue.new
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 10 14 | 
             
                def interrupt(value = nil)
         | 
| 11 15 | 
             
                  return if @running == false
         | 
| 12 16 |  | 
| @@ -75,47 +79,26 @@ module Polyphony | |
| 75 79 |  | 
| 76 80 | 
             
              # Fiber supervision
         | 
| 77 81 | 
             
              module FiberSupervision
         | 
| 78 | 
            -
                def supervise(opts  | 
| 79 | 
            -
                   | 
| 80 | 
            -
             | 
| 81 | 
            -
             | 
| 82 | 
            -
                  end
         | 
| 83 | 
            -
                  while true
         | 
| 84 | 
            -
                    supervise_perform(opts)
         | 
| 85 | 
            -
                  end
         | 
| 86 | 
            -
              rescue Polyphony::MoveOn
         | 
| 87 | 
            -
                  # generated in #supervise_perform to stop supervisor
         | 
| 88 | 
            -
                ensure
         | 
| 89 | 
            -
                  @on_child_done = nil
         | 
| 90 | 
            -
                end
         | 
| 82 | 
            +
                def supervise(*fibers, **opts, &block)
         | 
| 83 | 
            +
                  block ||= opts[:on_done] ||
         | 
| 84 | 
            +
                            (opts[:on_error] && supervise_on_error_proc(opts[:on_error]))
         | 
| 85 | 
            +
                  raise "No block given" unless block
         | 
| 91 86 |  | 
| 92 | 
            -
             | 
| 93 | 
            -
             | 
| 94 | 
            -
             | 
| 95 | 
            -
                    restart_fiber(fiber, opts)
         | 
| 96 | 
            -
                  elsif Fiber.current.children.empty?
         | 
| 97 | 
            -
                    Fiber.current.stop
         | 
| 87 | 
            +
                  fibers.each do |f|
         | 
| 88 | 
            +
                    f.attach_to(self) unless f.parent == self
         | 
| 89 | 
            +
                    f.monitor(self)
         | 
| 98 90 | 
             
                  end
         | 
| 99 | 
            -
                rescue Polyphony::Restart
         | 
| 100 | 
            -
                  restart_all_children
         | 
| 101 | 
            -
                rescue Exception => e
         | 
| 102 | 
            -
                  Kernel.raise e if e.source_fiber.nil? || e.source_fiber == self
         | 
| 103 91 |  | 
| 104 | 
            -
                   | 
| 105 | 
            -
             | 
| 106 | 
            -
                   | 
| 107 | 
            -
                     | 
| 92 | 
            +
                  mailbox = monitor_mailbox
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                  while true
         | 
| 95 | 
            +
                    (fiber, result) = mailbox.shift
         | 
| 96 | 
            +
                    block&.call(fiber, result)
         | 
| 108 97 | 
             
                  end
         | 
| 109 98 | 
             
                end
         | 
| 110 99 |  | 
| 111 | 
            -
                def  | 
| 112 | 
            -
                  opts[: | 
| 113 | 
            -
                  case opts[:restart]
         | 
| 114 | 
            -
                  when true
         | 
| 115 | 
            -
                    fiber.restart
         | 
| 116 | 
            -
                  when :one_for_all
         | 
| 117 | 
            -
                    @children.keys.each(&:restart)
         | 
| 118 | 
            -
                  end
         | 
| 100 | 
            +
                def supervise_on_error_proc(on_error)
         | 
| 101 | 
            +
                  ->(f, r) { opts[:on_error].(f, r) if r.is_a?(Exception) }
         | 
| 119 102 | 
             
                end
         | 
| 120 103 | 
             
              end
         | 
| 121 104 |  | 
| @@ -124,23 +107,24 @@ module Polyphony | |
| 124 107 | 
             
                def await(*fibers)
         | 
| 125 108 | 
             
                  return [] if fibers.empty?
         | 
| 126 109 |  | 
| 127 | 
            -
                   | 
| 110 | 
            +
                  current_fiber = self.current
         | 
| 111 | 
            +
                  mailbox = current_fiber.monitor_mailbox
         | 
| 128 112 | 
             
                  results = {}
         | 
| 129 113 | 
             
                  fibers.each do |f|
         | 
| 130 114 | 
             
                    results[f] = nil
         | 
| 131 115 | 
             
                    if f.dead?
         | 
| 132 116 | 
             
                      # fiber already terminated, so queue message
         | 
| 133 | 
            -
                       | 
| 117 | 
            +
                      mailbox << [f, f.result]
         | 
| 134 118 | 
             
                    else
         | 
| 135 | 
            -
                      f.monitor
         | 
| 119 | 
            +
                      f.monitor(current_fiber)
         | 
| 136 120 | 
             
                    end
         | 
| 137 121 | 
             
                  end
         | 
| 138 122 | 
             
                  exception = nil
         | 
| 139 123 | 
             
                  while !fibers.empty?
         | 
| 140 | 
            -
                    (fiber, result) =  | 
| 124 | 
            +
                    (fiber, result) = mailbox.shift
         | 
| 141 125 | 
             
                    next unless fibers.include?(fiber)
         | 
| 142 | 
            -
             | 
| 143 126 | 
             
                    fibers.delete(fiber)
         | 
| 127 | 
            +
                    current_fiber.remove_child(fiber) if fiber.parent == current_fiber
         | 
| 144 128 | 
             
                    if result.is_a?(Exception)
         | 
| 145 129 | 
             
                      exception ||= result
         | 
| 146 130 | 
             
                      fibers.each { |f| f.terminate }
         | 
| @@ -148,16 +132,16 @@ module Polyphony | |
| 148 132 | 
             
                      results[fiber] = result
         | 
| 149 133 | 
             
                    end
         | 
| 150 134 | 
             
                  end
         | 
| 151 | 
            -
                  results.values
         | 
| 152 | 
            -
                ensure
         | 
| 153 | 
            -
                  Fiber.current.message_on_child_termination = false
         | 
| 154 135 | 
             
                  raise exception if exception
         | 
| 136 | 
            +
                  results.values
         | 
| 155 137 | 
             
                end
         | 
| 156 138 | 
             
                alias_method :join, :await
         | 
| 157 139 |  | 
| 158 140 | 
             
                def select(*fibers)
         | 
| 159 141 | 
             
                  return nil if fibers.empty?
         | 
| 160 142 |  | 
| 143 | 
            +
                  current_fiber = self.current
         | 
| 144 | 
            +
                  mailbox = current_fiber.monitor_mailbox
         | 
| 161 145 | 
             
                  fibers.each do |f|
         | 
| 162 146 | 
             
                    if f.dead?
         | 
| 163 147 | 
             
                      result = f.result
         | 
| @@ -165,21 +149,18 @@ module Polyphony | |
| 165 149 | 
             
                    end
         | 
| 166 150 | 
             
                  end
         | 
| 167 151 |  | 
| 168 | 
            -
                   | 
| 169 | 
            -
                  fibers.each { |f| f.monitor }
         | 
| 152 | 
            +
                  fibers.each { |f| f.monitor(current_fiber) }
         | 
| 170 153 | 
             
                  while true
         | 
| 171 | 
            -
                    (fiber, result) =  | 
| 154 | 
            +
                    (fiber, result) = mailbox.shift
         | 
| 172 155 | 
             
                    next unless fibers.include?(fiber)
         | 
| 173 156 |  | 
| 174 | 
            -
                    fibers.each { |f| f.unmonitor }
         | 
| 157 | 
            +
                    fibers.each { |f| f.unmonitor(current_fiber) }
         | 
| 175 158 | 
             
                    if result.is_a?(Exception)
         | 
| 176 159 | 
             
                      raise result
         | 
| 177 160 | 
             
                    else
         | 
| 178 161 | 
             
                      return [fiber, result]
         | 
| 179 162 | 
             
                    end
         | 
| 180 163 | 
             
                  end
         | 
| 181 | 
            -
                ensure
         | 
| 182 | 
            -
                  Fiber.current.message_on_child_termination = false
         | 
| 183 164 | 
             
                end
         | 
| 184 165 |  | 
| 185 166 | 
             
                # Creates and schedules with priority an out-of-band fiber that runs the
         | 
| @@ -219,14 +200,6 @@ module Polyphony | |
| 219 200 | 
             
                  f
         | 
| 220 201 | 
             
                end
         | 
| 221 202 |  | 
| 222 | 
            -
                def child_done(child_fiber, result)
         | 
| 223 | 
            -
                  @children.delete(child_fiber)
         | 
| 224 | 
            -
             | 
| 225 | 
            -
                  if result.is_a?(Exception) && !@message_on_child_termination
         | 
| 226 | 
            -
                    schedule_with_priority(result)
         | 
| 227 | 
            -
                  end
         | 
| 228 | 
            -
                end
         | 
| 229 | 
            -
             | 
| 230 203 | 
             
                def terminate_all_children(graceful = false)
         | 
| 231 204 | 
             
                  return unless @children
         | 
| 232 205 |  | 
| @@ -240,16 +213,25 @@ module Polyphony | |
| 240 213 | 
             
                def await_all_children
         | 
| 241 214 | 
             
                  return unless @children && !@children.empty?
         | 
| 242 215 |  | 
| 243 | 
            -
                  Fiber.await(*@children.keys)
         | 
| 216 | 
            +
                  Fiber.await(*@children.keys.reject { |c| c.dead? })
         | 
| 244 217 | 
             
                end
         | 
| 245 218 |  | 
| 246 219 | 
             
                def shutdown_all_children(graceful = false)
         | 
| 247 220 | 
             
                  return unless @children
         | 
| 248 221 |  | 
| 249 222 | 
             
                  @children.keys.each do |c|
         | 
| 223 | 
            +
                    next if c.dead?
         | 
| 224 | 
            +
             | 
| 250 225 | 
             
                    c.terminate(graceful)
         | 
| 251 226 | 
             
                    c.await
         | 
| 252 227 | 
             
                  end
         | 
| 228 | 
            +
                  reap_dead_children
         | 
| 229 | 
            +
                end
         | 
| 230 | 
            +
             | 
| 231 | 
            +
                def reap_dead_children
         | 
| 232 | 
            +
                  return unless @children
         | 
| 233 | 
            +
             | 
| 234 | 
            +
                  @children.reject! { |f| f.dead? }
         | 
| 253 235 | 
             
                end
         | 
| 254 236 |  | 
| 255 237 | 
             
                def detach
         | 
| @@ -258,10 +240,17 @@ module Polyphony | |
| 258 240 | 
             
                  @parent.add_child(self)
         | 
| 259 241 | 
             
                end
         | 
| 260 242 |  | 
| 261 | 
            -
                def  | 
| 243 | 
            +
                def attach_to(fiber)
         | 
| 262 244 | 
             
                  @parent.remove_child(self)
         | 
| 263 | 
            -
                  @parent =  | 
| 264 | 
            -
                   | 
| 245 | 
            +
                  @parent = fiber
         | 
| 246 | 
            +
                  fiber.add_child(self)
         | 
| 247 | 
            +
                end
         | 
| 248 | 
            +
             | 
| 249 | 
            +
                def attach_and_monitor(fiber)
         | 
| 250 | 
            +
                  @parent.remove_child(self)
         | 
| 251 | 
            +
                  @parent = fiber
         | 
| 252 | 
            +
                  fiber.add_child(self)
         | 
| 253 | 
            +
                  monitor(fiber)
         | 
| 265 254 | 
             
                end
         | 
| 266 255 | 
             
              end
         | 
| 267 256 |  | 
| @@ -323,7 +312,7 @@ module Polyphony | |
| 323 312 | 
             
                  Thread.backend.trace(:fiber_terminate, self, result)
         | 
| 324 313 | 
             
                  @result = result
         | 
| 325 314 |  | 
| 326 | 
            -
                   | 
| 315 | 
            +
                  inform_monitors(result, uncaught_exception)
         | 
| 327 316 | 
             
                  @running = false
         | 
| 328 317 | 
             
                ensure
         | 
| 329 318 | 
             
                  # Prevent fiber from being resumed after terminating
         | 
| @@ -341,24 +330,28 @@ module Polyphony | |
| 341 330 | 
             
                  [e, true]
         | 
| 342 331 | 
             
                end
         | 
| 343 332 |  | 
| 344 | 
            -
                def  | 
| 333 | 
            +
                def inform_monitors(result, uncaught_exception)
         | 
| 345 334 | 
             
                  if @monitors
         | 
| 346 335 | 
             
                    msg = [self, result]
         | 
| 347 | 
            -
                    @monitors. | 
| 336 | 
            +
                    @monitors.each_key { |f| f.monitor_mailbox << msg }
         | 
| 348 337 | 
             
                  end
         | 
| 349 338 |  | 
| 350 | 
            -
                  @parent | 
| 339 | 
            +
                  if uncaught_exception && @parent
         | 
| 340 | 
            +
                    parent_is_monitor = @monitors&.has_key?(@parent)
         | 
| 341 | 
            +
                    @parent.schedule_with_priority(result) unless parent_is_monitor
         | 
| 342 | 
            +
                  end
         | 
| 351 343 | 
             
                end
         | 
| 352 344 |  | 
| 353 | 
            -
                 | 
| 345 | 
            +
                def monitor(fiber)
         | 
| 346 | 
            +
                  (@monitors ||= {})[fiber] = true
         | 
| 347 | 
            +
                end
         | 
| 354 348 |  | 
| 355 | 
            -
                def  | 
| 356 | 
            -
                  @monitors ||= []
         | 
| 357 | 
            -
                  @monitors << Fiber.current
         | 
| 349 | 
            +
                def unmonitor(fiber)
         | 
| 350 | 
            +
                  (@monitors ||= []).delete(fiber)
         | 
| 358 351 | 
             
                end
         | 
| 359 352 |  | 
| 360 | 
            -
                def  | 
| 361 | 
            -
                  @monitors | 
| 353 | 
            +
                def monitors
         | 
| 354 | 
            +
                  @monitors&.keys || []
         | 
| 362 355 | 
             
                end
         | 
| 363 356 |  | 
| 364 357 | 
             
                def dead?
         |