polyphony 0.79 → 0.81.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/Gemfile.lock +2 -1
- data/examples/core/raw_buffer_test.rb +8 -0
- data/examples/core/zlib_stream.rb +16 -0
- data/ext/polyphony/backend_common.c +2 -1
- data/ext/polyphony/backend_common.h +7 -2
- data/ext/polyphony/backend_io_uring.c +69 -30
- data/ext/polyphony/polyphony.c +8 -0
- data/ext/polyphony/polyphony.h +11 -0
- data/lib/polyphony/adapters/fs.rb +4 -0
- data/lib/polyphony/adapters/process.rb +14 -1
- data/lib/polyphony/adapters/redis.rb +28 -0
- data/lib/polyphony/adapters/sequel.rb +19 -1
- data/lib/polyphony/core/debug.rb +129 -72
- data/lib/polyphony/core/exceptions.rb +21 -6
- data/lib/polyphony/core/global_api.rb +228 -73
- data/lib/polyphony/core/resource_pool.rb +65 -20
- data/lib/polyphony/core/sync.rb +57 -12
- data/lib/polyphony/core/thread_pool.rb +42 -5
- data/lib/polyphony/core/throttler.rb +21 -5
- data/lib/polyphony/core/timer.rb +125 -1
- data/lib/polyphony/extensions/exception.rb +36 -6
- data/lib/polyphony/extensions/fiber.rb +238 -57
- data/lib/polyphony/extensions/io.rb +4 -2
- data/lib/polyphony/extensions/kernel.rb +9 -4
- data/lib/polyphony/extensions/object.rb +8 -0
- data/lib/polyphony/extensions/openssl.rb +3 -1
- data/lib/polyphony/extensions/socket.rb +458 -39
- data/lib/polyphony/extensions/thread.rb +108 -43
- data/lib/polyphony/extensions/timeout.rb +12 -1
- data/lib/polyphony/extensions.rb +1 -0
- data/lib/polyphony/net.rb +66 -7
- data/lib/polyphony/version.rb +1 -1
- data/lib/polyphony.rb +0 -2
- data/test/test_backend.rb +6 -2
- data/test/test_global_api.rb +0 -23
- data/test/test_resource_pool.rb +1 -1
- data/test/test_throttler.rb +0 -6
- data/test/test_trace.rb +87 -0
- metadata +10 -8
- data/lib/polyphony/core/channel.rb +0 -15
    
        data/lib/polyphony/core/debug.rb
    CHANGED
    
    | @@ -1,8 +1,15 @@ | |
| 1 | 
            +
            # Kernel extensions
         | 
| 1 2 | 
             
            module ::Kernel
         | 
| 3 | 
            +
              # Prints a trace message to `STDOUT`, bypassing the Polyphony backend.
         | 
| 4 | 
            +
              #
         | 
| 5 | 
            +
              # @return [void]
         | 
| 2 6 | 
             
              def trace(*args)
         | 
| 3 7 | 
             
                STDOUT.orig_write(format_trace(args))
         | 
| 4 8 | 
             
              end
         | 
| 5 9 |  | 
| 10 | 
            +
              # Formats a trace message.
         | 
| 11 | 
            +
              #
         | 
| 12 | 
            +
              # @return [String] trace message
         | 
| 6 13 | 
             
              def format_trace(args)
         | 
| 7 14 | 
             
                if args.size > 1 && args.first.is_a?(String)
         | 
| 8 15 | 
             
                  format("%s: %p\n", args.shift, args.size == 1 ? args.first : args)
         | 
| @@ -15,14 +22,29 @@ module ::Kernel | |
| 15 22 | 
             
            end
         | 
| 16 23 |  | 
| 17 24 | 
             
            module Polyphony
         | 
| 25 | 
            +
             | 
| 26 | 
            +
              # Trace provides tools for tracing the activity of the current thread's
         | 
| 27 | 
            +
              # backend.
         | 
| 18 28 | 
             
              module Trace
         | 
| 19 29 | 
             
                class << self
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  # Starts tracing, emitting events converted to hashes to the given block.
         | 
| 32 | 
            +
                  # If an IO instance is given, events are dumped to it instead.
         | 
| 33 | 
            +
                  #
         | 
| 34 | 
            +
                  # @param io [IO, nil] IO instance
         | 
| 35 | 
            +
                  # @param &block [Proc] event handler block
         | 
| 36 | 
            +
                  # @return [void]
         | 
| 20 37 | 
             
                  def start_event_firehose(io = nil, &block)
         | 
| 21 38 | 
             
                    Thread.backend.trace_proc = firehose_proc(io, block)
         | 
| 22 39 | 
             
                  end
         | 
| 23 40 |  | 
| 24 41 | 
             
                  private
         | 
| 25 42 |  | 
| 43 | 
            +
                  # Returns a firehose proc for the given io and block.
         | 
| 44 | 
            +
                  #
         | 
| 45 | 
            +
                  # @param io [IO, nil] IO instance
         | 
| 46 | 
            +
                  # @param block [Proc] event handler block
         | 
| 47 | 
            +
                  # @return [Proc] firehose proc
         | 
| 26 48 | 
             
                  def firehose_proc(io, block)
         | 
| 27 49 | 
             
                    if io
         | 
| 28 50 | 
             
                      ->(*e) { io.orig_write("#{trace_event_info(e).inspect}\n") }
         | 
| @@ -33,53 +55,50 @@ module Polyphony | |
| 33 55 | 
             
                    end
         | 
| 34 56 | 
             
                  end
         | 
| 35 57 |  | 
| 58 | 
            +
                  # Converts an event (expressed as an array) to a hash.
         | 
| 59 | 
            +
                  #
         | 
| 60 | 
            +
                  # @param e [Array] event as emitted by the backend
         | 
| 61 | 
            +
                  # @return [Hash] event hash
         | 
| 36 62 | 
             
                  def trace_event_info(e)
         | 
| 37 63 | 
             
                    {
         | 
| 38 | 
            -
                      stamp:  | 
| 64 | 
            +
                      stamp: Time.now,
         | 
| 39 65 | 
             
                      event: e[0]
         | 
| 40 66 | 
             
                    }.merge(
         | 
| 41 67 | 
             
                      send(:"event_props_#{e[0]}", e)
         | 
| 42 68 | 
             
                    )
         | 
| 43 69 | 
             
                  end
         | 
| 44 | 
            -
                  
         | 
| 45 | 
            -
                  def format_trace_event_message(e)
         | 
| 46 | 
            -
                    props = send(:"event_props_#{e[0]}", e).merge(
         | 
| 47 | 
            -
                      timestamp: format_current_time,
         | 
| 48 | 
            -
                      event: e[0]
         | 
| 49 | 
            -
                    )
         | 
| 50 | 
            -
                    # templ = send(:"event_format_#{e[0]}", e)
         | 
| 51 | 
            -
             | 
| 52 | 
            -
                    # msg = format("%<timestamp>s #{templ}\n", **props)
         | 
| 53 | 
            -
                  end
         | 
| 54 | 
            -
             | 
| 55 | 
            -
                  def format_current_time
         | 
| 56 | 
            -
                    Time.now.strftime('%Y-%m-%d %H:%M:%S')
         | 
| 57 | 
            -
                  end
         | 
| 58 70 |  | 
| 59 | 
            -
                   | 
| 60 | 
            -
             | 
| 61 | 
            -
                   | 
| 62 | 
            -
             | 
| 63 | 
            -
                  def  | 
| 64 | 
            -
                     | 
| 71 | 
            +
                  # Returns an event hash for a `:block` event.
         | 
| 72 | 
            +
                  #
         | 
| 73 | 
            +
                  # @param e [Array] event array
         | 
| 74 | 
            +
                  # @return [Hash] event hash
         | 
| 75 | 
            +
                  def event_props_block(e)
         | 
| 76 | 
            +
                    {
         | 
| 77 | 
            +
                      fiber: e[1],
         | 
| 78 | 
            +
                      caller: e[2]
         | 
| 79 | 
            +
                    }
         | 
| 65 80 | 
             
                  end
         | 
| 66 81 |  | 
| 82 | 
            +
                  # Returns an event hash for a `:enter_poll` event.
         | 
| 83 | 
            +
                  #
         | 
| 84 | 
            +
                  # @param e [Array] event array
         | 
| 85 | 
            +
                  # @return [Hash] event hash
         | 
| 67 86 | 
             
                  def event_props_enter_poll(e)
         | 
| 68 87 | 
             
                    {}
         | 
| 69 88 | 
             
                  end
         | 
| 70 89 |  | 
| 71 | 
            -
                   | 
| 72 | 
            -
             | 
| 73 | 
            -
                   | 
| 74 | 
            -
             | 
| 90 | 
            +
                  # Returns an event hash for a `:leave_poll` event.
         | 
| 91 | 
            +
                  #
         | 
| 92 | 
            +
                  # @param e [Array] event array
         | 
| 93 | 
            +
                  # @return [Hash] event hash
         | 
| 75 94 | 
             
                  def event_props_leave_poll(e)
         | 
| 76 95 | 
             
                    {}
         | 
| 77 96 | 
             
                  end
         | 
| 78 97 |  | 
| 79 | 
            -
                   | 
| 80 | 
            -
             | 
| 81 | 
            -
                   | 
| 82 | 
            -
             | 
| 98 | 
            +
                  # Returns an event hash for a `:schedule` event.
         | 
| 99 | 
            +
                  #
         | 
| 100 | 
            +
                  # @param e [Array] event array
         | 
| 101 | 
            +
                  # @return [Hash] event hash
         | 
| 83 102 | 
             
                  def event_props_schedule(e)
         | 
| 84 103 | 
             
                    {
         | 
| 85 104 | 
             
                      fiber: e[1],
         | 
| @@ -89,22 +108,22 @@ module Polyphony | |
| 89 108 | 
             
                    }
         | 
| 90 109 | 
             
                  end
         | 
| 91 110 |  | 
| 92 | 
            -
                   | 
| 93 | 
            -
             | 
| 94 | 
            -
                   | 
| 95 | 
            -
             | 
| 96 | 
            -
                  def  | 
| 111 | 
            +
                  # Returns an event hash for a `:spin` event.
         | 
| 112 | 
            +
                  #
         | 
| 113 | 
            +
                  # @param e [Array] event array
         | 
| 114 | 
            +
                  # @return [Hash] event hash
         | 
| 115 | 
            +
                  def event_props_spin(e)
         | 
| 97 116 | 
             
                    {
         | 
| 98 117 | 
             
                      fiber: e[1],
         | 
| 99 | 
            -
                       | 
| 100 | 
            -
                       | 
| 118 | 
            +
                      caller: e[2],
         | 
| 119 | 
            +
                      source_fiber: Fiber.current
         | 
| 101 120 | 
             
                    }
         | 
| 102 121 | 
             
                  end
         | 
| 103 122 |  | 
| 104 | 
            -
                   | 
| 105 | 
            -
             | 
| 106 | 
            -
                   | 
| 107 | 
            -
             | 
| 123 | 
            +
                  # Returns an event hash for a `:terminate` event.
         | 
| 124 | 
            +
                  #
         | 
| 125 | 
            +
                  # @param e [Array] event array
         | 
| 126 | 
            +
                  # @return [Hash] event hash
         | 
| 108 127 | 
             
                  def event_props_terminate(e)
         | 
| 109 128 | 
             
                    {
         | 
| 110 129 | 
             
                      fiber: e[1],
         | 
| @@ -112,48 +131,86 @@ module Polyphony | |
| 112 131 | 
             
                    }
         | 
| 113 132 | 
             
                  end
         | 
| 114 133 |  | 
| 115 | 
            -
                   | 
| 116 | 
            -
             | 
| 117 | 
            -
                   | 
| 118 | 
            -
             | 
| 119 | 
            -
                  def  | 
| 134 | 
            +
                  # Returns an event hash for a `:unblock` event.
         | 
| 135 | 
            +
                  #
         | 
| 136 | 
            +
                  # @param e [Array] event array
         | 
| 137 | 
            +
                  # @return [Hash] event hash
         | 
| 138 | 
            +
                  def event_props_unblock(e)
         | 
| 120 139 | 
             
                    {
         | 
| 121 140 | 
             
                      fiber: e[1],
         | 
| 122 | 
            -
                       | 
| 141 | 
            +
                      value: e[2],
         | 
| 142 | 
            +
                      caller: e[3],
         | 
| 123 143 | 
             
                    }
         | 
| 124 144 | 
             
                  end
         | 
| 125 145 |  | 
| 126 | 
            -
                   | 
| 127 | 
            -
             | 
| 128 | 
            -
                   | 
| 146 | 
            +
                  # TODO: work on text formatting of events
         | 
| 147 | 
            +
                  # def format_trace_event_message(e)
         | 
| 148 | 
            +
                  #   props = send(:"event_props_#{e[0]}", e).merge(
         | 
| 149 | 
            +
                  #     timestamp: format_current_time,
         | 
| 150 | 
            +
                  #     event: e[0]
         | 
| 151 | 
            +
                  #   )
         | 
| 152 | 
            +
                  #   templ = send(:"event_format_#{e[0]}", e)
         | 
| 153 | 
            +
                  #   msg = format("%<timestamp>s #{templ}\n", **props)
         | 
| 154 | 
            +
                  # end
         | 
| 129 155 |  | 
| 130 | 
            -
                  def  | 
| 131 | 
            -
             | 
| 132 | 
            -
             | 
| 133 | 
            -
                      caller: e[2],
         | 
| 134 | 
            -
                      source_fiber: Fiber.current
         | 
| 135 | 
            -
                    }
         | 
| 136 | 
            -
                  end
         | 
| 156 | 
            +
                  # def format_current_time
         | 
| 157 | 
            +
                  #   Time.now.strftime('%Y-%m-%d %H:%M:%S')
         | 
| 158 | 
            +
                  # end
         | 
| 137 159 |  | 
| 138 | 
            -
                  def  | 
| 139 | 
            -
             | 
| 140 | 
            -
                  end
         | 
| 160 | 
            +
                  # def generic_event_format
         | 
| 161 | 
            +
                  #   '%<event>-12.12s'
         | 
| 162 | 
            +
                  # end
         | 
| 141 163 |  | 
| 142 | 
            -
                  def  | 
| 143 | 
            -
             | 
| 144 | 
            -
                  end
         | 
| 164 | 
            +
                  # def fiber_event_format
         | 
| 165 | 
            +
                  #   "#{generic_event_format} %<fiber>-44.44s"
         | 
| 166 | 
            +
                  # end
         | 
| 145 167 |  | 
| 146 | 
            -
                  def  | 
| 147 | 
            -
             | 
| 148 | 
            -
             | 
| 149 | 
            -
                    else
         | 
| 150 | 
            -
                      format("%-6x %-.10s", fiber.object_id, "(#{fiber.state})")
         | 
| 151 | 
            -
                    end
         | 
| 152 | 
            -
                  end
         | 
| 168 | 
            +
                  # def event_format_enter_poll(e)
         | 
| 169 | 
            +
                  #   generic_event_format
         | 
| 170 | 
            +
                  # end
         | 
| 153 171 |  | 
| 154 | 
            -
                  def  | 
| 155 | 
            -
             | 
| 156 | 
            -
                  end
         | 
| 172 | 
            +
                  # def event_format_leave_poll(e)
         | 
| 173 | 
            +
                  #   generic_event_format
         | 
| 174 | 
            +
                  # end
         | 
| 175 | 
            +
             | 
| 176 | 
            +
             | 
| 177 | 
            +
                  # def event_format_schedule(e)
         | 
| 178 | 
            +
                  #   "#{fiber_event_format} %<value>-24.24p %<caller>-120.120s <= %<origin_fiber>s"
         | 
| 179 | 
            +
                  # end
         | 
| 180 | 
            +
             | 
| 181 | 
            +
             | 
| 182 | 
            +
                  # def event_format_unblock(e)
         | 
| 183 | 
            +
                  #   "#{fiber_event_format} %<value>-24.24p %<caller>-120.120s"
         | 
| 184 | 
            +
                  # end
         | 
| 185 | 
            +
             | 
| 186 | 
            +
                  # def event_format_terminate(e)
         | 
| 187 | 
            +
                  #   "#{fiber_event_format} %<value>-24.24p"
         | 
| 188 | 
            +
                  # end
         | 
| 189 | 
            +
             | 
| 190 | 
            +
                  # def event_format_block(e)
         | 
| 191 | 
            +
                  #   "#{fiber_event_format} #{' ' * 24} %<caller>-120.120s"
         | 
| 192 | 
            +
                  # end
         | 
| 193 | 
            +
             | 
| 194 | 
            +
             | 
| 195 | 
            +
                  # def event_format_spin(e)
         | 
| 196 | 
            +
                  #   "#{fiber_event_format} #{' ' * 24} %<caller>-120.120s <= %<origin_fiber>s"
         | 
| 197 | 
            +
                  # end
         | 
| 198 | 
            +
             | 
| 199 | 
            +
                  # def fibe_repr(fiber)
         | 
| 200 | 
            +
                  #   format("%-6x %-20.20s %-10.10s", fiber.object_id, fiber.tag, "(#{fiber.state})")
         | 
| 201 | 
            +
                  # end
         | 
| 202 | 
            +
             | 
| 203 | 
            +
                  # def fiber_compact_repr(fiber)
         | 
| 204 | 
            +
                  #   if fiber.tag
         | 
| 205 | 
            +
                  #     format("%-6x %-.20s %-.10s", fiber.object_id, fiber.tag, "(#{fiber.state})")
         | 
| 206 | 
            +
                  #   else
         | 
| 207 | 
            +
                  #     format("%-6x %-.10s", fiber.object_id, "(#{fiber.state})")
         | 
| 208 | 
            +
                  #   end
         | 
| 209 | 
            +
                  # end
         | 
| 210 | 
            +
             | 
| 211 | 
            +
                  # def caller_repr(c)
         | 
| 212 | 
            +
                  #  c.map { |i| i.gsub('/home/sharon/repo/polyphony/lib/polyphony', '') }.join('  ')
         | 
| 213 | 
            +
                  # end
         | 
| 157 214 | 
             
                end
         | 
| 158 215 | 
             
              end
         | 
| 159 216 | 
             
            end
         | 
| @@ -1,15 +1,22 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 3 | 
             
            module Polyphony
         | 
| 4 | 
            -
             | 
| 5 | 
            -
              #  | 
| 6 | 
            -
              #  | 
| 7 | 
            -
              #  | 
| 8 | 
            -
              #  | 
| 9 | 
            -
              # through nested | 
| 4 | 
            +
             | 
| 5 | 
            +
              # Base exception class for interrupting fibers. These exceptions allow control
         | 
| 6 | 
            +
              # of fibers. BaseException exceptions can encapsulate a value and thus provide
         | 
| 7 | 
            +
              # a way to interrupt long-running blocking operations while still passing a
         | 
| 8 | 
            +
              # value back to the call site. BaseException exceptions can also references a
         | 
| 9 | 
            +
              # cancel scope in order to allow correct bubbling of exceptions through nested
         | 
| 10 | 
            +
              # cancel scopes.
         | 
| 10 11 | 
             
              class BaseException < ::Exception
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                # Exception value, used mainly for `MoveOn` exceptions.
         | 
| 11 14 | 
             
                attr_reader :value
         | 
| 12 15 |  | 
| 16 | 
            +
                # Initializes the exception, setting the caller and the value.
         | 
| 17 | 
            +
                #
         | 
| 18 | 
            +
                # @param value [any] Exception value
         | 
| 19 | 
            +
                # @return [void]
         | 
| 13 20 | 
             
                def initialize(value = nil)
         | 
| 14 21 | 
             
                  @caller_backtrace = caller
         | 
| 15 22 | 
             
                  @value = value
         | 
| @@ -33,10 +40,18 @@ module Polyphony | |
| 33 40 |  | 
| 34 41 | 
             
              # Interjection is used to run arbitrary code on arbitrary fibers at any point
         | 
| 35 42 | 
             
              class Interjection < BaseException
         | 
| 43 | 
            +
                
         | 
| 44 | 
            +
                # Initializes an Interjection with the given proc.
         | 
| 45 | 
            +
                #
         | 
| 46 | 
            +
                # @param proc [Proc] interjection proc
         | 
| 47 | 
            +
                # @return [void]
         | 
| 36 48 | 
             
                def initialize(proc)
         | 
| 37 49 | 
             
                  @proc = proc
         | 
| 38 50 | 
             
                end
         | 
| 39 51 |  | 
| 52 | 
            +
                # Invokes the exception by calling the associated proc.
         | 
| 53 | 
            +
                #
         | 
| 54 | 
            +
                # @return [void]
         | 
| 40 55 | 
             
                def invoke
         | 
| 41 56 | 
             
                  @proc.call
         | 
| 42 57 | 
             
                end
         | 
| @@ -3,8 +3,16 @@ | |
| 3 3 | 
             
            require_relative './throttler'
         | 
| 4 4 |  | 
| 5 5 | 
             
            module Polyphony
         | 
| 6 | 
            -
               | 
| 6 | 
            +
              
         | 
| 7 | 
            +
              # Global API methods to be included in `::Object`
         | 
| 7 8 | 
             
              module GlobalAPI
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                # Spins up a fiber that will run the given block after sleeping for the
         | 
| 11 | 
            +
                # given delay.
         | 
| 12 | 
            +
                #
         | 
| 13 | 
            +
                # @param delay [Number] delay in seconds before running the given block
         | 
| 14 | 
            +
                # @param &block [Proc] block to run
         | 
| 15 | 
            +
                # @return [Fiber] spun fiber
         | 
| 8 16 | 
             
                def after(interval, &block)
         | 
| 9 17 | 
             
                  spin do
         | 
| 10 18 | 
             
                    sleep interval
         | 
| @@ -12,64 +20,88 @@ module Polyphony | |
| 12 20 | 
             
                  end
         | 
| 13 21 | 
             
                end
         | 
| 14 22 |  | 
| 23 | 
            +
                # call-seq:
         | 
| 24 | 
            +
                #   cancel_after(interval) { ... }
         | 
| 25 | 
            +
                #   cancel_after(interval, with_exception: exception) { ... }
         | 
| 26 | 
            +
                #   cancel_after(interval, with_exception: [klass, message]) { ... }
         | 
| 27 | 
            +
                #   cancel_after(interval) { |timeout| ... }
         | 
| 28 | 
            +
                #   cancel_after(interval, with_exception: exception) { |timeout| ... }
         | 
| 29 | 
            +
                #   cancel_after(interval, with_exception: [klass, message]) { |timeout| ... }
         | 
| 30 | 
            +
                #
         | 
| 31 | 
            +
                # Runs the given block after setting up a cancellation timer for
         | 
| 32 | 
            +
                # cancellation. If the cancellation timer elapses, the execution will be
         | 
| 33 | 
            +
                # interrupted with an exception defaulting to `Polyphony::Cancel`.
         | 
| 34 | 
            +
                #
         | 
| 35 | 
            +
                # This method should be used when a timeout should cause an exception to be
         | 
| 36 | 
            +
                # propagated down the call stack or up the fiber tree.
         | 
| 37 | 
            +
                #
         | 
| 38 | 
            +
                # Example of normal use:
         | 
| 39 | 
            +
                #
         | 
| 40 | 
            +
                #   def read_from_io_with_timeout(io)
         | 
| 41 | 
            +
                #     cancel_after(10) { io.read }
         | 
| 42 | 
            +
                #   rescue Polyphony::Cancel
         | 
| 43 | 
            +
                #     nil
         | 
| 44 | 
            +
                #   end
         | 
| 45 | 
            +
                #
         | 
| 46 | 
            +
                # The timeout period can be reset by passing a block that takes a single
         | 
| 47 | 
            +
                # argument. The block will be provided with the canceller fiber. To reset
         | 
| 48 | 
            +
                # the timeout, use `Fiber#reset`, as shown in the following example:
         | 
| 49 | 
            +
                #
         | 
| 50 | 
            +
                #   cancel_after(10) do |timeout|
         | 
| 51 | 
            +
                #     loop do
         | 
| 52 | 
            +
                #       msg = socket.gets
         | 
| 53 | 
            +
                #       timeout.reset
         | 
| 54 | 
            +
                #       handle_msg(msg)
         | 
| 55 | 
            +
                #     end
         | 
| 56 | 
            +
                #   end
         | 
| 57 | 
            +
                #
         | 
| 58 | 
            +
                # @param interval [Number] timout in seconds
         | 
| 59 | 
            +
                # @param with_exception: [Class, Exception] exception or exception class
         | 
| 60 | 
            +
                # @param &block [Proc] block to execute
         | 
| 61 | 
            +
                # @return [any] block's return value
         | 
| 15 62 | 
             
                def cancel_after(interval, with_exception: Polyphony::Cancel, &block)
         | 
| 16 | 
            -
                  if  | 
| 17 | 
            -
                     | 
| 18 | 
            -
                  elsif block.arity > 0
         | 
| 19 | 
            -
                    cancel_after_with_block(Fiber.current, interval, with_exception, &block)
         | 
| 63 | 
            +
                  if block.arity > 0
         | 
| 64 | 
            +
                    cancel_after_with_optional_reset(interval, with_exception, &block)
         | 
| 20 65 | 
             
                  else
         | 
| 21 66 | 
             
                    Polyphony.backend_timeout(interval, with_exception, &block)
         | 
| 22 67 | 
             
                  end
         | 
| 23 68 | 
             
                end
         | 
| 24 69 |  | 
| 25 | 
            -
                 | 
| 26 | 
            -
             | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 30 | 
            -
                    fiber.schedule exception
         | 
| 31 | 
            -
                  end
         | 
| 32 | 
            -
                end
         | 
| 33 | 
            -
             | 
| 34 | 
            -
                def cancel_after_with_block(fiber, interval, with_exception, &block)
         | 
| 35 | 
            -
                  canceller = cancel_after_blockless_canceller(fiber, interval, with_exception)
         | 
| 36 | 
            -
                  block.call(canceller)
         | 
| 37 | 
            -
                ensure
         | 
| 38 | 
            -
                  canceller.stop
         | 
| 39 | 
            -
                end
         | 
| 40 | 
            -
             | 
| 41 | 
            -
                def cancel_exception(exception)
         | 
| 42 | 
            -
                  case exception
         | 
| 43 | 
            -
                  when Class then exception.new
         | 
| 44 | 
            -
                  when Array then exception[0].new(exception[1])
         | 
| 45 | 
            -
                  else RuntimeError.new(exception)
         | 
| 46 | 
            -
                  end
         | 
| 47 | 
            -
                end
         | 
| 48 | 
            -
             | 
| 70 | 
            +
                # Spins up a new fiber.
         | 
| 71 | 
            +
                #
         | 
| 72 | 
            +
                # @param tag [any] optional tag for the new fiber
         | 
| 73 | 
            +
                # @param &block [Proc] fiber block
         | 
| 74 | 
            +
                # @return [Fiber] new fiber
         | 
| 49 75 | 
             
                def spin(tag = nil, &block)
         | 
| 50 76 | 
             
                  Fiber.current.spin(tag, caller, &block)
         | 
| 51 77 | 
             
                end
         | 
| 52 78 |  | 
| 79 | 
            +
                # Spins up a new fiber, running the given block inside an infinite loop. If
         | 
| 80 | 
            +
                # `rate:` or `interval:` parameters are given, the loop is throttled
         | 
| 81 | 
            +
                # accordingly.
         | 
| 82 | 
            +
                #
         | 
| 83 | 
            +
                # @param tag [any] optional tag for the new fiber
         | 
| 84 | 
            +
                # @param rate: [Number, nil] loop rate (times per second)
         | 
| 85 | 
            +
                # @param interval: [Number, nil] interval between consecutive iterations in seconds
         | 
| 86 | 
            +
                # @param &block [Proc] code to run
         | 
| 87 | 
            +
                # @return [Fiber] new fiber
         | 
| 53 88 | 
             
                def spin_loop(tag = nil, rate: nil, interval: nil, &block)
         | 
| 54 89 | 
             
                  if rate || interval
         | 
| 55 90 | 
             
                    Fiber.current.spin(tag, caller) do
         | 
| 56 91 | 
             
                      throttled_loop(rate: rate, interval: interval, &block)
         | 
| 57 92 | 
             
                    end
         | 
| 58 93 | 
             
                  else
         | 
| 59 | 
            -
                     | 
| 60 | 
            -
                  end
         | 
| 61 | 
            -
                end
         | 
| 62 | 
            -
             | 
| 63 | 
            -
                def spin_looped_block(tag, caller, block)
         | 
| 64 | 
            -
                  Fiber.current.spin(tag, caller) do
         | 
| 65 | 
            -
                    block.call while true
         | 
| 66 | 
            -
                  rescue LocalJumpError, StopIteration
         | 
| 67 | 
            -
                    # break called or StopIteration raised
         | 
| 94 | 
            +
                    spin_loop_without_throttling(tag, caller, block)
         | 
| 68 95 | 
             
                  end
         | 
| 69 96 | 
             
                end
         | 
| 70 97 |  | 
| 71 | 
            -
                 | 
| 72 | 
            -
             | 
| 98 | 
            +
                # Runs the given code, then waits for any child fibers of the current fibers
         | 
| 99 | 
            +
                # to terminate.
         | 
| 100 | 
            +
                #
         | 
| 101 | 
            +
                # @param &block [Proc] code to run
         | 
| 102 | 
            +
                # @return [any] given block's return value
         | 
| 103 | 
            +
                def spin_scope(&block)
         | 
| 104 | 
            +
                  raise unless block
         | 
| 73 105 |  | 
| 74 106 | 
             
                  spin do
         | 
| 75 107 | 
             
                    result = yield
         | 
| @@ -78,61 +110,121 @@ module Polyphony | |
| 78 110 | 
             
                  end.await
         | 
| 79 111 | 
             
                end
         | 
| 80 112 |  | 
| 113 | 
            +
                # Runs the given block in an infinite loop with a regular interval between
         | 
| 114 | 
            +
                # consecutive iterations.
         | 
| 115 | 
            +
                #
         | 
| 116 | 
            +
                # @param interval [Number] interval between consecutive iterations in seconds
         | 
| 117 | 
            +
                # @param &block [Proc] block to run
         | 
| 118 | 
            +
                # @return [void]
         | 
| 81 119 | 
             
                def every(interval, &block)
         | 
| 82 120 | 
             
                  Polyphony.backend_timer_loop(interval, &block)
         | 
| 83 121 | 
             
                end
         | 
| 84 122 |  | 
| 123 | 
            +
                # call-seq:
         | 
| 124 | 
            +
                #   move_on_after(interval) { ... }
         | 
| 125 | 
            +
                #   move_on_after(interval, with_value: value) { ... }
         | 
| 126 | 
            +
                #   move_on_after(interval) { |canceller| ... }
         | 
| 127 | 
            +
                #   move_on_after(interval, with_value: value) { |canceller| ... }
         | 
| 128 | 
            +
                #
         | 
| 129 | 
            +
                # Runs the given block after setting up a cancellation timer for
         | 
| 130 | 
            +
                # cancellation. If the cancellation timer elapses, the execution will be
         | 
| 131 | 
            +
                # interrupted with a `Polyphony::MoveOn` exception, which will be rescued,
         | 
| 132 | 
            +
                # and with cause the operation to return the given value.
         | 
| 133 | 
            +
                #
         | 
| 134 | 
            +
                # This method should be used when a timeout is to be handled locally,
         | 
| 135 | 
            +
                # without generating an exception that is to propagated down the call stack
         | 
| 136 | 
            +
                # or up the fiber tree.
         | 
| 137 | 
            +
                #
         | 
| 138 | 
            +
                # Example of normal use:
         | 
| 139 | 
            +
                #
         | 
| 140 | 
            +
                #   move_on_after(10) {
         | 
| 141 | 
            +
                #     sleep 60
         | 
| 142 | 
            +
                #     42
         | 
| 143 | 
            +
                #   } #=> nil
         | 
| 144 | 
            +
                #
         | 
| 145 | 
            +
                #   move_on_after(10, with_value: :oops) {
         | 
| 146 | 
            +
                #     sleep 60
         | 
| 147 | 
            +
                #     42
         | 
| 148 | 
            +
                #   } #=> :oops
         | 
| 149 | 
            +
                #
         | 
| 150 | 
            +
                # The timeout period can be reset by passing a block that takes a single
         | 
| 151 | 
            +
                # argument. The block will be provided with the canceller fiber. To reset
         | 
| 152 | 
            +
                # the timeout, use `Fiber#reset`, as shown in the following example:
         | 
| 153 | 
            +
                #
         | 
| 154 | 
            +
                #   move_on_after(10) do |timeout|
         | 
| 155 | 
            +
                #     loop do
         | 
| 156 | 
            +
                #       msg = socket.gets
         | 
| 157 | 
            +
                #       timeout.reset
         | 
| 158 | 
            +
                #       handle_msg(msg)
         | 
| 159 | 
            +
                #     end
         | 
| 160 | 
            +
                #   end
         | 
| 161 | 
            +
                #
         | 
| 162 | 
            +
                # @param interval [Number] timout in seconds
         | 
| 163 | 
            +
                # @param with_value: [any] return value in case of timeout
         | 
| 164 | 
            +
                # @param &block [Proc] block to execute
         | 
| 165 | 
            +
                # @return [any] block's return value
         | 
| 85 166 | 
             
                def move_on_after(interval, with_value: nil, &block)
         | 
| 86 | 
            -
                  if  | 
| 87 | 
            -
                     | 
| 88 | 
            -
                  elsif block.arity > 0
         | 
| 89 | 
            -
                    move_on_after_with_block(Fiber.current, interval, with_value, &block)
         | 
| 167 | 
            +
                  if block.arity > 0
         | 
| 168 | 
            +
                    move_on_after_with_optional_reset(interval, with_value, &block)
         | 
| 90 169 | 
             
                  else
         | 
| 91 170 | 
             
                    Polyphony.backend_timeout(interval, nil, with_value, &block)
         | 
| 92 171 | 
             
                  end
         | 
| 93 172 | 
             
                end
         | 
| 94 173 |  | 
| 95 | 
            -
                 | 
| 96 | 
            -
             | 
| 97 | 
            -
             | 
| 98 | 
            -
             | 
| 99 | 
            -
                  end
         | 
| 100 | 
            -
                end
         | 
| 101 | 
            -
             | 
| 102 | 
            -
                def move_on_after_with_block(fiber, interval, with_value, &block)
         | 
| 103 | 
            -
                  canceller = spin do
         | 
| 104 | 
            -
                    sleep interval
         | 
| 105 | 
            -
                    fiber.schedule Polyphony::MoveOn.new(with_value)
         | 
| 106 | 
            -
                  end
         | 
| 107 | 
            -
                  block.call(canceller)
         | 
| 108 | 
            -
                rescue Polyphony::MoveOn => e
         | 
| 109 | 
            -
                  e.value
         | 
| 110 | 
            -
                ensure
         | 
| 111 | 
            -
                  canceller.stop
         | 
| 112 | 
            -
                end
         | 
| 113 | 
            -
             | 
| 174 | 
            +
                # Returns the first message from the current fiber's mailbox. If the mailbox
         | 
| 175 | 
            +
                # is empty, blocks until a message is available.
         | 
| 176 | 
            +
                #
         | 
| 177 | 
            +
                # @return [any] received message
         | 
| 114 178 | 
             
                def receive
         | 
| 115 179 | 
             
                  Fiber.current.receive
         | 
| 116 180 | 
             
                end
         | 
| 117 181 |  | 
| 182 | 
            +
                # Returns all messages currently pending on the current fiber's mailbox.
         | 
| 183 | 
            +
                #
         | 
| 184 | 
            +
                # @return [Array] array of received messages
         | 
| 118 185 | 
             
                def receive_all_pending
         | 
| 119 186 | 
             
                  Fiber.current.receive_all_pending
         | 
| 120 187 | 
             
                end
         | 
| 121 188 |  | 
| 189 | 
            +
                # Supervises the current fiber's children. See `Fiber#supervise` for
         | 
| 190 | 
            +
                # options.
         | 
| 191 | 
            +
                #
         | 
| 192 | 
            +
                # @param *args [Array] positional parameters
         | 
| 193 | 
            +
                # @param **opts [Hash] named parameters
         | 
| 194 | 
            +
                # @param &block [Proc] given block
         | 
| 195 | 
            +
                # @return [void]
         | 
| 122 196 | 
             
                def supervise(*args, **opts, &block)
         | 
| 123 197 | 
             
                  Fiber.current.supervise(*args, **opts, &block)
         | 
| 124 198 | 
             
                end
         | 
| 125 199 |  | 
| 200 | 
            +
                # Sleeps for the given duration. If the duration is `nil`, sleeps
         | 
| 201 | 
            +
                # indefinitely.
         | 
| 202 | 
            +
                #
         | 
| 203 | 
            +
                # @param duration [Number, nil] duration
         | 
| 204 | 
            +
                # @return [void]
         | 
| 126 205 | 
             
                def sleep(duration = nil)
         | 
| 127 | 
            -
                   | 
| 128 | 
            -
             | 
| 129 | 
            -
                  Polyphony.backend_sleep duration
         | 
| 130 | 
            -
                end
         | 
| 131 | 
            -
             | 
| 132 | 
            -
                def sleep_forever
         | 
| 133 | 
            -
                  Polyphony.backend_wait_event(true)
         | 
| 206 | 
            +
                  duration ?
         | 
| 207 | 
            +
                    Polyphony.backend_sleep(duration) : Polyphony.backend_wait_event(true)
         | 
| 134 208 | 
             
                end
         | 
| 135 209 |  | 
| 210 | 
            +
                # call-seq:
         | 
| 211 | 
            +
                #   throttled_loop(rate) { ... }
         | 
| 212 | 
            +
                #   throttled_loop(interval: value) { ... }
         | 
| 213 | 
            +
                #   throttled_loop(rate: value) { ... }
         | 
| 214 | 
            +
                #   throttled_loop(rate, count: value) { ... }
         | 
| 215 | 
            +
                #
         | 
| 216 | 
            +
                # Starts a throttled loop with the given rate. If `count:` is given, the
         | 
| 217 | 
            +
                # loop is run for the given number of times. Otherwise, the loop is
         | 
| 218 | 
            +
                # infinite. The loop rate (times per second) can be given as the rate
         | 
| 219 | 
            +
                # parameter. The throttling can also be controlled by providing an
         | 
| 220 | 
            +
                # `interval:` or `rate:` named parameter.
         | 
| 221 | 
            +
                #
         | 
| 222 | 
            +
                # @param rate [Number, nil] loop rate (times per second)
         | 
| 223 | 
            +
                # @param rate: [Number] loop rate (times per second)
         | 
| 224 | 
            +
                # @param interval: [Number] loop interval in seconds
         | 
| 225 | 
            +
                # @param count: [Number, nil] number of iterations (nil for infinite)
         | 
| 226 | 
            +
                # @param &block [Proc] code to run
         | 
| 227 | 
            +
                # @return [void]
         | 
| 136 228 | 
             
                def throttled_loop(rate = nil, **opts, &block)
         | 
| 137 229 | 
             
                  throttler = Polyphony::Throttler.new(rate || opts)
         | 
| 138 230 | 
             
                  if opts[:count]
         | 
| @@ -144,10 +236,73 @@ module Polyphony | |
| 144 236 | 
             
                  end
         | 
| 145 237 | 
             
                rescue LocalJumpError, StopIteration
         | 
| 146 238 | 
             
                  # break called or StopIteration raised
         | 
| 239 | 
            +
                end
         | 
| 240 | 
            +
             | 
| 241 | 
            +
                private
         | 
| 242 | 
            +
             | 
| 243 | 
            +
                # Helper method for performing a `cancel_after` with optional reset.
         | 
| 244 | 
            +
                #
         | 
| 245 | 
            +
                # @param interval [Number] timeout interval in seconds
         | 
| 246 | 
            +
                # @param exception [Exception, Class, Array<class, message>] exception spec
         | 
| 247 | 
            +
                # @param &block [Proc] block to run
         | 
| 248 | 
            +
                # @return [any] block's return value
         | 
| 249 | 
            +
                def cancel_after_with_optional_reset(interval, exception, &block)
         | 
| 250 | 
            +
                  canceller = spin do
         | 
| 251 | 
            +
                    sleep interval
         | 
| 252 | 
            +
                    exception = cancel_exception(exception)
         | 
| 253 | 
            +
                    exception.raising_fiber = Fiber.current
         | 
| 254 | 
            +
                    fiber.cancel(exception).await
         | 
| 255 | 
            +
                  end
         | 
| 256 | 
            +
                  block.call(canceller)
         | 
| 147 257 | 
             
                ensure
         | 
| 148 | 
            -
                   | 
| 258 | 
            +
                  canceller.stop
         | 
| 259 | 
            +
                end
         | 
| 260 | 
            +
             | 
| 261 | 
            +
                # Converts the given exception spec to an exception instance.
         | 
| 262 | 
            +
                #
         | 
| 263 | 
            +
                # @param exception [Exception, Class, Array<class, message>] exception spec
         | 
| 264 | 
            +
                # @return [Exception] exception instance
         | 
| 265 | 
            +
                def cancel_exception(exception)
         | 
| 266 | 
            +
                  case exception
         | 
| 267 | 
            +
                  when Class then exception.new
         | 
| 268 | 
            +
                  when Array then exception[0].new(exception[1])
         | 
| 269 | 
            +
                  else RuntimeError.new(exception)
         | 
| 270 | 
            +
                  end
         | 
| 149 271 | 
             
                end
         | 
| 272 | 
            +
             | 
| 273 | 
            +
                # Helper method for performing `#spin_loop` without throttling. Spins up a
         | 
| 274 | 
            +
                # new fiber in which to run the loop.
         | 
| 275 | 
            +
                #
         | 
| 276 | 
            +
                # @param tag [any] new fiber's tag
         | 
| 277 | 
            +
                # @param caller [Array<String>] caller info
         | 
| 278 | 
            +
                # @param block [Proc] code to run
         | 
| 279 | 
            +
                # @return [void]
         | 
| 280 | 
            +
                def spin_loop_without_throttling(tag, caller, block)
         | 
| 281 | 
            +
                  Fiber.current.spin(tag, caller) do
         | 
| 282 | 
            +
                    block.call while true
         | 
| 283 | 
            +
                  rescue LocalJumpError, StopIteration
         | 
| 284 | 
            +
                    # break called or StopIteration raised
         | 
| 285 | 
            +
                  end
         | 
| 286 | 
            +
                end
         | 
| 287 | 
            +
             | 
| 288 | 
            +
                # Helper method for performing `#move_on_after` with optional reset.
         | 
| 289 | 
            +
                #
         | 
| 290 | 
            +
                # @param interval [Number] timeout interval in seconds
         | 
| 291 | 
            +
                # @param value [any] return value in case of timeout
         | 
| 292 | 
            +
                # @param &block [Proc] code to run
         | 
| 293 | 
            +
                # @return [any] return value of given block or timeout value
         | 
| 294 | 
            +
                def move_on_after_with_optional_reset(interval, value, &block)
         | 
| 295 | 
            +
                  fiber = Fiber.current
         | 
| 296 | 
            +
                  canceller = spin do
         | 
| 297 | 
            +
                    sleep interval
         | 
| 298 | 
            +
                    fiber.move_on(value).await
         | 
| 299 | 
            +
                  end
         | 
| 300 | 
            +
                  block.call(canceller)
         | 
| 301 | 
            +
                rescue Polyphony::MoveOn => e
         | 
| 302 | 
            +
                  e.value
         | 
| 303 | 
            +
                ensure
         | 
| 304 | 
            +
                  canceller.stop
         | 
| 305 | 
            +
                end
         | 
| 306 | 
            +
             | 
| 150 307 | 
             
              end
         | 
| 151 308 | 
             
            end
         | 
| 152 | 
            -
             | 
| 153 | 
            -
            Object.include Polyphony::GlobalAPI
         |