polyphony 0.77 → 0.80
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 +18 -0
- data/Gemfile.lock +2 -1
- data/examples/core/pingpong.rb +7 -4
- data/examples/core/zlib_stream.rb +15 -0
- data/ext/polyphony/backend_common.c +16 -8
- data/ext/polyphony/backend_common.h +8 -3
- data/ext/polyphony/backend_io_uring.c +19 -3
- data/ext/polyphony/backend_libev.c +33 -17
- data/ext/polyphony/fiber.c +28 -28
- data/ext/polyphony/polyphony.c +1 -8
- data/ext/polyphony/polyphony.h +11 -8
- data/ext/polyphony/queue.c +82 -6
- data/ext/polyphony/thread.c +6 -2
- 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 +203 -0
- 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 +244 -61
- 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 +59 -0
- 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_io.rb +7 -7
- data/test/test_queue.rb +103 -1
- data/test/test_resource_pool.rb +1 -1
- data/test/test_signal.rb +15 -15
- data/test/test_supervise.rb +27 -0
- data/test/test_thread.rb +1 -1
- data/test/test_throttler.rb +0 -6
- data/test/test_trace.rb +189 -24
- metadata +9 -8
- data/lib/polyphony/core/channel.rb +0 -15
| @@ -5,8 +5,11 @@ require_relative '../core/exceptions' | |
| 5 5 | 
             
            # Thread extensions
         | 
| 6 6 | 
             
            class ::Thread
         | 
| 7 7 | 
             
              attr_reader :main_fiber, :result
         | 
| 8 | 
            +
              attr_accessor :backend
         | 
| 8 9 |  | 
| 9 10 | 
             
              alias_method :orig_initialize, :initialize
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              # Initializes the thread.
         | 
| 10 13 | 
             
              def initialize(*args, &block)
         | 
| 11 14 | 
             
                @join_wait_queue = []
         | 
| 12 15 | 
             
                @finalization_mutex = Mutex.new
         | 
| @@ -15,53 +18,30 @@ class ::Thread | |
| 15 18 | 
             
                orig_initialize { execute }
         | 
| 16 19 | 
             
              end
         | 
| 17 20 |  | 
| 18 | 
            -
               | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 21 | 
            -
                raise_error = false
         | 
| 22 | 
            -
                begin
         | 
| 23 | 
            -
                  @backend = Polyphony::Backend.new
         | 
| 24 | 
            -
                rescue Exception => e
         | 
| 25 | 
            -
                  raise_error = true
         | 
| 26 | 
            -
                  raise e
         | 
| 27 | 
            -
                end
         | 
| 28 | 
            -
                setup
         | 
| 29 | 
            -
                @ready = true
         | 
| 30 | 
            -
                result = @block.(*@args)
         | 
| 31 | 
            -
              rescue Polyphony::MoveOn, Polyphony::Terminate => e
         | 
| 32 | 
            -
                result = e.value
         | 
| 33 | 
            -
              rescue Exception => e
         | 
| 34 | 
            -
                raise_error ? (raise e) : (result = e)
         | 
| 35 | 
            -
              ensure
         | 
| 36 | 
            -
                @ready = true
         | 
| 37 | 
            -
                finalize(result)
         | 
| 38 | 
            -
              end
         | 
| 39 | 
            -
             | 
| 40 | 
            -
              attr_accessor :backend
         | 
| 41 | 
            -
             | 
| 21 | 
            +
              # Sets up the thread and its main fiber.
         | 
| 22 | 
            +
              #
         | 
| 23 | 
            +
              # @return [void]
         | 
| 42 24 | 
             
              def setup
         | 
| 43 25 | 
             
                @main_fiber = Fiber.current
         | 
| 44 26 | 
             
                @main_fiber.setup_main_fiber
         | 
| 45 27 | 
             
                setup_fiber_scheduling
         | 
| 46 28 | 
             
              end
         | 
| 47 29 |  | 
| 48 | 
            -
              def finalize(result)
         | 
| 49 | 
            -
                unless Fiber.current.children.empty?
         | 
| 50 | 
            -
                  Fiber.current.shutdown_all_children
         | 
| 51 | 
            -
                end
         | 
| 52 | 
            -
                @finalization_mutex.synchronize do
         | 
| 53 | 
            -
                  @terminated = true
         | 
| 54 | 
            -
                  @result = result
         | 
| 55 | 
            -
                  signal_waiters(result)
         | 
| 56 | 
            -
                end
         | 
| 57 | 
            -
                @backend&.finalize
         | 
| 58 | 
            -
              end
         | 
| 59 | 
            -
             | 
| 60 | 
            -
              def signal_waiters(result)
         | 
| 61 | 
            -
                @join_wait_queue.each { |w| w.signal(result) }
         | 
| 62 | 
            -
              end
         | 
| 63 | 
            -
             | 
| 64 30 | 
             
              alias_method :orig_join, :join
         | 
| 31 | 
            +
             | 
| 32 | 
            +
              # call-seq:
         | 
| 33 | 
            +
              #   thread.join -> result
         | 
| 34 | 
            +
              #   thread.join(timeout) -> result
         | 
| 35 | 
            +
              #   thread.await -> result
         | 
| 36 | 
            +
              #   thread.await(timeout) -> result
         | 
| 37 | 
            +
              # 
         | 
| 38 | 
            +
              # Waits for the thread to terminate and returns its return value. If the
         | 
| 39 | 
            +
              # thread terminated with an uncaught exception, it is propagated to the
         | 
| 40 | 
            +
              # waiting fiber. If a timeout interval is specified, the thread will be
         | 
| 41 | 
            +
              # terminated without propagating the timeout exception.
         | 
| 42 | 
            +
              #
         | 
| 43 | 
            +
              # @param timeout [Number] timeout interval
         | 
| 44 | 
            +
              # @return [any] thread's return value
         | 
| 65 45 | 
             
              def join(timeout = nil)
         | 
| 66 46 | 
             
                watcher = Fiber.current.auto_watcher
         | 
| 67 47 |  | 
| @@ -77,6 +57,16 @@ class ::Thread | |
| 77 57 | 
             
              alias_method :await, :join
         | 
| 78 58 |  | 
| 79 59 | 
             
              alias_method :orig_raise, :raise
         | 
| 60 | 
            +
             | 
| 61 | 
            +
              # call-seq:
         | 
| 62 | 
            +
              #   thread.raise
         | 
| 63 | 
            +
              #   thread.raise(exception_class)
         | 
| 64 | 
            +
              #   thread.raise(exception_instance)
         | 
| 65 | 
            +
              # 
         | 
| 66 | 
            +
              # Raises an exception in the context of the thread. If no exception is given,
         | 
| 67 | 
            +
              # a `RuntimeError` is raised.
         | 
| 68 | 
            +
              #
         | 
| 69 | 
            +
              # @param error [Exception, Class, nil] exception spec
         | 
| 80 70 | 
             
              def raise(error = nil)
         | 
| 81 71 | 
             
                Thread.pass until @main_fiber
         | 
| 82 72 | 
             
                error = RuntimeError.new if error.nil?
         | 
| @@ -88,13 +78,22 @@ class ::Thread | |
| 88 78 | 
             
              end
         | 
| 89 79 |  | 
| 90 80 | 
             
              alias_method :orig_kill, :kill
         | 
| 81 | 
            +
             | 
| 82 | 
            +
              # Terminates the thread.
         | 
| 83 | 
            +
              #
         | 
| 84 | 
            +
              # @return [Thread] self
         | 
| 91 85 | 
             
              def kill
         | 
| 92 | 
            -
                return if @terminated
         | 
| 86 | 
            +
                return self if @terminated
         | 
| 93 87 |  | 
| 94 88 | 
             
                raise Polyphony::Terminate
         | 
| 89 | 
            +
                self
         | 
| 95 90 | 
             
              end
         | 
| 96 91 |  | 
| 97 92 | 
             
              alias_method :orig_inspect, :inspect
         | 
| 93 | 
            +
             | 
| 94 | 
            +
              # Returns a string representation of the thread for debugging purposes.
         | 
| 95 | 
            +
              #
         | 
| 96 | 
            +
              # @return [String] string representation
         | 
| 98 97 | 
             
              def inspect
         | 
| 99 98 | 
             
                return orig_inspect if self == Thread.main
         | 
| 100 99 |  | 
| @@ -103,20 +102,86 @@ class ::Thread | |
| 103 102 | 
             
              end
         | 
| 104 103 | 
             
              alias_method :to_s, :inspect
         | 
| 105 104 |  | 
| 105 | 
            +
              # Returns the source location of the thread's block.
         | 
| 106 | 
            +
              #
         | 
| 107 | 
            +
              # @return [String] source location
         | 
| 106 108 | 
             
              def location
         | 
| 107 109 | 
             
                @block.source_location.join(':')
         | 
| 108 110 | 
             
              end
         | 
| 109 111 |  | 
| 110 | 
            -
               | 
| 111 | 
            -
             | 
| 112 | 
            +
              # Sends a message to the thread's main fiber.
         | 
| 113 | 
            +
              #
         | 
| 114 | 
            +
              # @param msg [any] message
         | 
| 115 | 
            +
              # @return [Fiber] main fiber
         | 
| 116 | 
            +
              def <<(msg)
         | 
| 117 | 
            +
                main_fiber << msg
         | 
| 112 118 | 
             
              end
         | 
| 113 119 | 
             
              alias_method :send, :<<
         | 
| 114 120 |  | 
| 121 | 
            +
              # Sets the idle GC period for the thread's backend.
         | 
| 122 | 
            +
              #
         | 
| 123 | 
            +
              # @param period [Number] GC period in seconds
         | 
| 124 | 
            +
              # @return [Number] GC period
         | 
| 115 125 | 
             
              def idle_gc_period=(period)
         | 
| 116 126 | 
             
                backend.idle_gc_period = period
         | 
| 117 127 | 
             
              end
         | 
| 118 128 |  | 
| 129 | 
            +
              # Sets the idle handler for the thread's backend.
         | 
| 130 | 
            +
              #
         | 
| 131 | 
            +
              # @param &block [Proc] idle handler
         | 
| 132 | 
            +
              # @return [Proc] idle handler
         | 
| 119 133 | 
             
              def on_idle(&block)
         | 
| 120 134 | 
             
                backend.idle_proc = block
         | 
| 121 135 | 
             
              end
         | 
| 136 | 
            +
             | 
| 137 | 
            +
              private
         | 
| 138 | 
            +
             | 
| 139 | 
            +
              # Runs the thread's block, handling any uncaught exceptions.
         | 
| 140 | 
            +
              #
         | 
| 141 | 
            +
              # @return [void]
         | 
| 142 | 
            +
              def execute
         | 
| 143 | 
            +
                # backend must be created in the context of the new thread, therefore it
         | 
| 144 | 
            +
                # cannot be created in Thread#initialize
         | 
| 145 | 
            +
                raise_error = false
         | 
| 146 | 
            +
                begin
         | 
| 147 | 
            +
                  @backend = Polyphony::Backend.new
         | 
| 148 | 
            +
                rescue Exception => e
         | 
| 149 | 
            +
                  raise_error = true
         | 
| 150 | 
            +
                  raise e
         | 
| 151 | 
            +
                end
         | 
| 152 | 
            +
                setup
         | 
| 153 | 
            +
                @ready = true
         | 
| 154 | 
            +
                result = @block.(*@args)
         | 
| 155 | 
            +
              rescue Polyphony::MoveOn, Polyphony::Terminate => e
         | 
| 156 | 
            +
                result = e.value
         | 
| 157 | 
            +
              rescue Exception => e
         | 
| 158 | 
            +
                raise_error ? (raise e) : (result = e)
         | 
| 159 | 
            +
              ensure
         | 
| 160 | 
            +
                @ready = true
         | 
| 161 | 
            +
                finalize(result)
         | 
| 162 | 
            +
              end
         | 
| 163 | 
            +
             | 
| 164 | 
            +
              # Finalizes the thread.
         | 
| 165 | 
            +
              #
         | 
| 166 | 
            +
              # @param result [any] thread's return value
         | 
| 167 | 
            +
              # @return [void]
         | 
| 168 | 
            +
              def finalize(result)
         | 
| 169 | 
            +
                unless Fiber.current.children.empty?
         | 
| 170 | 
            +
                  Fiber.current.shutdown_all_children
         | 
| 171 | 
            +
                end
         | 
| 172 | 
            +
                @finalization_mutex.synchronize do
         | 
| 173 | 
            +
                  @terminated = true
         | 
| 174 | 
            +
                  @result = result
         | 
| 175 | 
            +
                  signal_waiters(result)
         | 
| 176 | 
            +
                end
         | 
| 177 | 
            +
                @backend&.finalize
         | 
| 178 | 
            +
              end
         | 
| 179 | 
            +
             | 
| 180 | 
            +
              # Signals all fibers waiting for the thread to terminate.
         | 
| 181 | 
            +
              #
         | 
| 182 | 
            +
              # @param result [any] thread's return value
         | 
| 183 | 
            +
              # @return [void]
         | 
| 184 | 
            +
              def signal_waiters(result)
         | 
| 185 | 
            +
                @join_wait_queue.each { |w| w.signal(result) }
         | 
| 186 | 
            +
              end
         | 
| 122 187 | 
             
            end
         | 
| @@ -2,8 +2,19 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            require 'timeout'
         | 
| 4 4 |  | 
| 5 | 
            -
            #  | 
| 5 | 
            +
            # Timeout extensions
         | 
| 6 6 | 
             
            module ::Timeout
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              # Sets a timeout for the given block. This method provides an equivalent API
         | 
| 9 | 
            +
              # to the stock Timeout API provided by Ruby. In case of a timeout, the block
         | 
| 10 | 
            +
              # will be interrupted and an exception will be raised according to the given
         | 
| 11 | 
            +
              # arguments.
         | 
| 12 | 
            +
              #
         | 
| 13 | 
            +
              # @param sec [Number] timeout period in seconds
         | 
| 14 | 
            +
              # @param klass [Class] exception class
         | 
| 15 | 
            +
              # @param message [String] exception message
         | 
| 16 | 
            +
              # @param &block [Proc] code to run
         | 
| 17 | 
            +
              # @return [any] block's return value
         | 
| 7 18 | 
             
              def self.timeout(sec, klass = Timeout::Error, message = 'execution expired', &block)
         | 
| 8 19 | 
             
                cancel_after(sec, with_exception: [klass, message], &block)
         | 
| 9 20 | 
             
              end
         | 
    
        data/lib/polyphony/extensions.rb
    CHANGED
    
    | @@ -3,6 +3,7 @@ | |
| 3 3 | 
             
            require_relative './extensions/exception'
         | 
| 4 4 | 
             
            require_relative './extensions/fiber'
         | 
| 5 5 | 
             
            require_relative './extensions/io'
         | 
| 6 | 
            +
            require_relative './extensions/object'
         | 
| 6 7 | 
             
            require_relative './extensions/kernel'
         | 
| 7 8 | 
             
            require_relative './extensions/process'
         | 
| 8 9 | 
             
            require_relative './extensions/thread'
         | 
    
        data/lib/polyphony/net.rb
    CHANGED
    
    | @@ -4,9 +4,25 @@ require_relative './extensions/socket' | |
| 4 4 | 
             
            require_relative './extensions/openssl'
         | 
| 5 5 |  | 
| 6 6 | 
             
            module Polyphony
         | 
| 7 | 
            +
              
         | 
| 7 8 | 
             
              # A more elegant networking API
         | 
| 8 9 | 
             
              module Net
         | 
| 9 10 | 
             
                class << self
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  # call-seq:
         | 
| 13 | 
            +
                  #   Polyphony::Net.tcp_connect(host, port) -> TCPSocket
         | 
| 14 | 
            +
                  #   Polyphony::Net.tcp_connect(host, port, secure: true) -> SSLSocket
         | 
| 15 | 
            +
                  #   Polyphony::Net.tcp_connect(host, port, secure_context: ctx) -> SSLSocket
         | 
| 16 | 
            +
                  #
         | 
| 17 | 
            +
                  # Create a TCP connection to the given host and port, returning the new
         | 
| 18 | 
            +
                  # socket. If `opts[:secure]` is true, or if an SSL context is given in
         | 
| 19 | 
            +
                  # `opts[:secure_context]`, a TLS handshake is performed, and an SSLSocket
         | 
| 20 | 
            +
                  # is returned.
         | 
| 21 | 
            +
                  #
         | 
| 22 | 
            +
                  # @param host [String] hostname
         | 
| 23 | 
            +
                  # @param port [Integer] port number
         | 
| 24 | 
            +
                  # @param opts [Hash] connection options
         | 
| 25 | 
            +
                  # @return [TCPSocket, SSLSocket] connected socket
         | 
| 10 26 | 
             
                  def tcp_connect(host, port, opts = {})
         | 
| 11 27 | 
             
                    socket = TCPSocket.new(host, port)
         | 
| 12 28 | 
             
                    if opts[:secure_context] || opts[:secure]
         | 
| @@ -16,6 +32,15 @@ module Polyphony | |
| 16 32 | 
             
                    end
         | 
| 17 33 | 
             
                  end
         | 
| 18 34 |  | 
| 35 | 
            +
                  # Creates a server socket for accepting incoming connection on the given
         | 
| 36 | 
            +
                  # host and port. If `opts[:secure]` is true, or if an SSL context is given
         | 
| 37 | 
            +
                  # in `opts[:secure_context]`, a TLS handshake is performed, and an
         | 
| 38 | 
            +
                  # SSLSocket is returned.
         | 
| 39 | 
            +
                  #
         | 
| 40 | 
            +
                  # @param host [String] hostname
         | 
| 41 | 
            +
                  # @param port [Integer] port number
         | 
| 42 | 
            +
                  # @param opts [Hash] connection options
         | 
| 43 | 
            +
                  # @return [TCPServer, SSLServer] listening socket
         | 
| 19 44 | 
             
                  def tcp_listen(host = nil, port = nil, opts = {})
         | 
| 20 45 | 
             
                    host ||= '0.0.0.0'
         | 
| 21 46 | 
             
                    raise 'Port number not specified' unless port
         | 
| @@ -28,6 +53,14 @@ module Polyphony | |
| 28 53 | 
             
                    end
         | 
| 29 54 | 
             
                  end
         | 
| 30 55 |  | 
| 56 | 
            +
                  private
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                  # Creates a listening `Socket` instance.
         | 
| 59 | 
            +
                  #
         | 
| 60 | 
            +
                  # @param host [String] hostname
         | 
| 61 | 
            +
                  # @param port [Integer] port number
         | 
| 62 | 
            +
                  # @param opts [Hash] connection options
         | 
| 63 | 
            +
                  # @return [Socket] listening socket
         | 
| 31 64 | 
             
                  def listening_socket_from_options(host, port, opts)
         | 
| 32 65 | 
             
                    ::Socket.new(:INET, :STREAM).tap do |s|
         | 
| 33 66 | 
             
                      s.reuse_addr if opts[:reuse_addr]
         | 
| @@ -39,6 +72,12 @@ module Polyphony | |
| 39 72 | 
             
                    end
         | 
| 40 73 | 
             
                  end
         | 
| 41 74 |  | 
| 75 | 
            +
                  # Wraps the given socket with a SSLSocket and performs a TLS handshake.
         | 
| 76 | 
            +
                  #
         | 
| 77 | 
            +
                  # @param socket [Socket] plain socket
         | 
| 78 | 
            +
                  # @param context [SSLContext, nil] SSL context
         | 
| 79 | 
            +
                  # @param opts [Hash] connection options
         | 
| 80 | 
            +
                  # @return [SSLSocket] SSL socket
         | 
| 42 81 | 
             
                  def secure_socket(socket, context, opts)
         | 
| 43 82 | 
             
                    context ||= OpenSSL::SSL::SSLContext.new
         | 
| 44 83 | 
             
                    setup_alpn(context, opts[:alpn_protocols]) if opts[:alpn_protocols]
         | 
| @@ -51,6 +90,11 @@ module Polyphony | |
| 51 90 | 
             
                    end
         | 
| 52 91 | 
             
                  end
         | 
| 53 92 |  | 
| 93 | 
            +
                  # Wraps the given socket with an SSLSocket.
         | 
| 94 | 
            +
                  #
         | 
| 95 | 
            +
                  # @param socket [Socket] plain socket
         | 
| 96 | 
            +
                  # @param context [SSLContext] SSL context
         | 
| 97 | 
            +
                  # @return [SSLSocket] SSL socket
         | 
| 54 98 | 
             
                  def secure_socket_wrapper(socket, context)
         | 
| 55 99 | 
             
                    if context
         | 
| 56 100 | 
             
                      OpenSSL::SSL::SSLSocket.new(socket, context)
         | 
| @@ -59,11 +103,26 @@ module Polyphony | |
| 59 103 | 
             
                    end
         | 
| 60 104 | 
             
                  end
         | 
| 61 105 |  | 
| 106 | 
            +
                  # Wraps the given socket with an SSLServer, setting up ALPN from the given
         | 
| 107 | 
            +
                  # options.
         | 
| 108 | 
            +
                  #
         | 
| 109 | 
            +
                  # @param socket [Socket] plain socket
         | 
| 110 | 
            +
                  # @param context [SSLContext] SSL context
         | 
| 111 | 
            +
                  # @param opts [Hash] options
         | 
| 112 | 
            +
                  # @return [SSLServer] SSL socket
         | 
| 62 113 | 
             
                  def secure_server(socket, context, opts)
         | 
| 63 114 | 
             
                    setup_alpn(context, opts[:alpn_protocols]) if opts[:alpn_protocols]
         | 
| 64 115 | 
             
                    OpenSSL::SSL::SSLServer.new(socket, context)
         | 
| 65 116 | 
             
                  end
         | 
| 66 117 |  | 
| 118 | 
            +
                  # Sets up ALPN negotiation for the given context. The ALPN handler for the
         | 
| 119 | 
            +
                  # context will select the first protocol from the list given by the client
         | 
| 120 | 
            +
                  # that appears in the list of given protocols, according to the specified
         | 
| 121 | 
            +
                  # order.
         | 
| 122 | 
            +
                  # 
         | 
| 123 | 
            +
                  # @param context [SSLContext] SSL context
         | 
| 124 | 
            +
                  # @param protocols [Array] array of supported protocols
         | 
| 125 | 
            +
                  # @return [void]
         | 
| 67 126 | 
             
                  def setup_alpn(context, protocols)
         | 
| 68 127 | 
             
                    context.alpn_protocols = protocols
         | 
| 69 128 | 
             
                    context.alpn_select_cb = lambda do |peer_protocols|
         | 
    
        data/lib/polyphony/version.rb
    CHANGED
    
    
    
        data/lib/polyphony.rb
    CHANGED
    
    | @@ -9,7 +9,6 @@ Thread.current.backend = Polyphony::Backend.new | |
| 9 9 |  | 
| 10 10 | 
             
            require_relative './polyphony/extensions'
         | 
| 11 11 | 
             
            require_relative './polyphony/core/exceptions'
         | 
| 12 | 
            -
            require_relative './polyphony/core/global_api'
         | 
| 13 12 | 
             
            require_relative './polyphony/core/resource_pool'
         | 
| 14 13 | 
             
            require_relative './polyphony/core/sync'
         | 
| 15 14 | 
             
            require_relative './polyphony/core/timer'
         | 
| @@ -53,7 +52,6 @@ module Polyphony | |
| 53 52 |  | 
| 54 53 | 
             
                def run_forked_block(&block)
         | 
| 55 54 | 
             
                  Thread.current.setup
         | 
| 56 | 
            -
                  Fiber.current.setup_main_fiber
         | 
| 57 55 | 
             
                  Thread.current.backend.post_fork
         | 
| 58 56 |  | 
| 59 57 | 
             
                  install_terminating_signal_handlers
         | 
    
        data/test/test_backend.rb
    CHANGED
    
    | @@ -191,7 +191,9 @@ class BackendTest < MiniTest::Test | |
| 191 191 | 
             
              Net = Polyphony::Net
         | 
| 192 192 |  | 
| 193 193 | 
             
              def test_accept
         | 
| 194 | 
            -
                server = Net. | 
| 194 | 
            +
                server = Net.send(
         | 
| 195 | 
            +
                  :listening_socket_from_options, '127.0.0.1', 1234, reuse_addr: true
         | 
| 196 | 
            +
                )
         | 
| 195 197 |  | 
| 196 198 | 
             
                clients = []
         | 
| 197 199 | 
             
                server_fiber = spin_loop do
         | 
| @@ -218,7 +220,9 @@ class BackendTest < MiniTest::Test | |
| 218 220 | 
             
              end
         | 
| 219 221 |  | 
| 220 222 | 
             
              def test_accept_loop
         | 
| 221 | 
            -
                server = Net. | 
| 223 | 
            +
                server = Net.send(
         | 
| 224 | 
            +
                  :listening_socket_from_options, '127.0.0.1', 1235, reuse_addr: true
         | 
| 225 | 
            +
                )
         | 
| 222 226 |  | 
| 223 227 | 
             
                clients = []
         | 
| 224 228 | 
             
                server_fiber = spin do
         | 
    
        data/test/test_global_api.rb
    CHANGED
    
    | @@ -140,17 +140,6 @@ class MoveOnAfterTest < MiniTest::Test | |
| 140 140 | 
             
                assert_in_range 0.014..0.02, t1 - t0 if IS_LINUX
         | 
| 141 141 | 
             
              end
         | 
| 142 142 |  | 
| 143 | 
            -
              def test_move_on_after_without_block
         | 
| 144 | 
            -
                t0 = Time.now
         | 
| 145 | 
            -
                f = move_on_after(0.01, with_value: 'foo')
         | 
| 146 | 
            -
                assert_kind_of Fiber, f
         | 
| 147 | 
            -
                assert_equal Fiber.current, f.parent
         | 
| 148 | 
            -
                v = sleep 1
         | 
| 149 | 
            -
                t1 = Time.now
         | 
| 150 | 
            -
                assert t1 - t0 < 0.1
         | 
| 151 | 
            -
                assert_equal 'foo', v
         | 
| 152 | 
            -
              end
         | 
| 153 | 
            -
             | 
| 154 143 | 
             
              def test_nested_move_on_after
         | 
| 155 144 | 
             
                skip unless IS_LINUX
         | 
| 156 145 |  | 
| @@ -190,18 +179,6 @@ class CancelAfterTest < MiniTest::Test | |
| 190 179 | 
             
                assert t1 - t0 < 0.1
         | 
| 191 180 | 
             
              end
         | 
| 192 181 |  | 
| 193 | 
            -
              def test_cancel_after_without_block
         | 
| 194 | 
            -
                t0 = Time.now
         | 
| 195 | 
            -
                f = cancel_after(0.01)
         | 
| 196 | 
            -
                assert_kind_of Fiber, f
         | 
| 197 | 
            -
                assert_equal Fiber.current, f.parent
         | 
| 198 | 
            -
                assert_raises Polyphony::Cancel do
         | 
| 199 | 
            -
                  sleep 1
         | 
| 200 | 
            -
                end
         | 
| 201 | 
            -
                t1 = Time.now
         | 
| 202 | 
            -
                assert t1 - t0 < 0.1
         | 
| 203 | 
            -
              end
         | 
| 204 | 
            -
             | 
| 205 182 | 
             
              def test_cancel_after_with_reset
         | 
| 206 183 | 
             
                t0 = Time.now
         | 
| 207 184 | 
             
                cancel_after(0.01) do |f|
         | 
    
        data/test/test_io.rb
    CHANGED
    
    | @@ -309,13 +309,13 @@ class IOClassMethodsTest < MiniTest::Test | |
| 309 309 | 
             
                assert_equal BIN_DATA, s
         | 
| 310 310 | 
             
              end
         | 
| 311 311 |  | 
| 312 | 
            -
              def test_foreach
         | 
| 313 | 
            -
             | 
| 314 | 
            -
             | 
| 315 | 
            -
             | 
| 316 | 
            -
             | 
| 317 | 
            -
             | 
| 318 | 
            -
              end
         | 
| 312 | 
            +
              # def test_foreach
         | 
| 313 | 
            +
              #   skip 'IO.foreach is not yet implemented'
         | 
| 314 | 
            +
              #   lines = []
         | 
| 315 | 
            +
              #   IO.foreach(__FILE__) { |l| lines << l }
         | 
| 316 | 
            +
              #   assert_equal "# frozen_string_literal: true\n", lines[0]
         | 
| 317 | 
            +
              #   assert_equal "end\n", lines[-1]
         | 
| 318 | 
            +
              # end
         | 
| 319 319 |  | 
| 320 320 | 
             
              def test_read_class_method
         | 
| 321 321 | 
             
                s = IO.read(__FILE__)
         | 
    
        data/test/test_queue.rb
    CHANGED
    
    | @@ -21,6 +21,44 @@ class QueueTest < MiniTest::Test | |
| 21 21 | 
             
                assert_equal [1, 2, 3, 4], buf
         | 
| 22 22 | 
             
              end
         | 
| 23 23 |  | 
| 24 | 
            +
              def test_chained_push
         | 
| 25 | 
            +
                @queue << 5 << 6 << 7
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                buf = []
         | 
| 28 | 
            +
                3.times { buf << @queue.shift }
         | 
| 29 | 
            +
                assert_equal [5, 6, 7], buf
         | 
| 30 | 
            +
              end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
              def test_push_aliases
         | 
| 33 | 
            +
                @queue.push 1
         | 
| 34 | 
            +
                @queue << 2
         | 
| 35 | 
            +
                @queue.enq 3
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                buf = []
         | 
| 38 | 
            +
                3.times { buf << @queue.shift }
         | 
| 39 | 
            +
                assert_equal [1, 2, 3], buf
         | 
| 40 | 
            +
              end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
              def test_pop_aliases
         | 
| 43 | 
            +
                @queue << 1 << 2 << 3
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                assert_equal 1, @queue.pop
         | 
| 46 | 
            +
                assert_equal 2, @queue.deq
         | 
| 47 | 
            +
                assert_equal 3, @queue.shift
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                @queue << 1 << 2 << 3
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                assert_equal 1, @queue.pop(false)
         | 
| 52 | 
            +
                assert_equal 2, @queue.deq(false)
         | 
| 53 | 
            +
                assert_equal 3, @queue.shift(false)
         | 
| 54 | 
            +
              end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
              def test_nonblocking_pop
         | 
| 57 | 
            +
                assert_raises(ThreadError) { @queue.pop(true) }
         | 
| 58 | 
            +
                assert_raises(ThreadError) { @queue.deq(true) }
         | 
| 59 | 
            +
                assert_raises(ThreadError) { @queue.shift(true) }
         | 
| 60 | 
            +
              end
         | 
| 61 | 
            +
             | 
| 24 62 | 
             
              def test_unshift
         | 
| 25 63 | 
             
                @queue.push 1
         | 
| 26 64 | 
             
                @queue.push 2
         | 
| @@ -112,22 +150,86 @@ class QueueTest < MiniTest::Test | |
| 112 150 |  | 
| 113 151 | 
             
              def test_queue_size
         | 
| 114 152 | 
             
                assert_equal 0, @queue.size
         | 
| 153 | 
            +
                assert_equal 0, @queue.length
         | 
| 115 154 |  | 
| 116 155 | 
             
                @queue.push 1
         | 
| 117 156 |  | 
| 118 157 | 
             
                assert_equal 1, @queue.size
         | 
| 158 | 
            +
                assert_equal 1, @queue.length
         | 
| 119 159 |  | 
| 120 160 | 
             
                @queue.push 2
         | 
| 121 161 |  | 
| 122 162 | 
             
                assert_equal 2, @queue.size
         | 
| 163 | 
            +
                assert_equal 2, @queue.length
         | 
| 123 164 |  | 
| 124 165 | 
             
                @queue.shift
         | 
| 125 166 |  | 
| 126 167 | 
             
                assert_equal 1, @queue.size
         | 
| 168 | 
            +
                assert_equal 1, @queue.length
         | 
| 127 169 |  | 
| 128 170 | 
             
                @queue.shift
         | 
| 129 171 |  | 
| 130 172 | 
             
                assert_equal 0, @queue.size
         | 
| 173 | 
            +
                assert_equal 0, @queue.length
         | 
| 174 | 
            +
              end
         | 
| 175 | 
            +
             | 
| 176 | 
            +
              def test_pending?
         | 
| 177 | 
            +
                assert_equal false, @queue.pending?
         | 
| 178 | 
            +
             | 
| 179 | 
            +
                buf = []
         | 
| 180 | 
            +
                f = spin { buf << @queue.shift }
         | 
| 181 | 
            +
                snooze
         | 
| 182 | 
            +
                assert_equal true, @queue.pending?
         | 
| 183 | 
            +
             | 
| 184 | 
            +
                @queue << 42
         | 
| 185 | 
            +
                f.await
         | 
| 186 | 
            +
                assert_equal [42], buf
         | 
| 187 | 
            +
                assert_equal false, @queue.pending?
         | 
| 188 | 
            +
              end
         | 
| 189 | 
            +
             | 
| 190 | 
            +
              def test_num_waiting
         | 
| 191 | 
            +
                assert_equal 0, @queue.num_waiting
         | 
| 192 | 
            +
             | 
| 193 | 
            +
                f1 = spin { @queue.shift }
         | 
| 194 | 
            +
                snooze # allow fiber to start
         | 
| 195 | 
            +
                assert_equal 1, @queue.num_waiting
         | 
| 196 | 
            +
             | 
| 197 | 
            +
                f2 = spin { @queue.shift }
         | 
| 198 | 
            +
                snooze # allow fiber to start
         | 
| 199 | 
            +
                assert_equal 2, @queue.num_waiting
         | 
| 200 | 
            +
             | 
| 201 | 
            +
                @queue << 1
         | 
| 202 | 
            +
                f1.await
         | 
| 203 | 
            +
                assert_equal 1, @queue.num_waiting
         | 
| 204 | 
            +
             | 
| 205 | 
            +
                @queue << 2
         | 
| 206 | 
            +
                f2.await
         | 
| 207 | 
            +
                assert_equal 0, @queue.num_waiting
         | 
| 208 | 
            +
              end
         | 
| 209 | 
            +
             | 
| 210 | 
            +
              def test_closed_queue
         | 
| 211 | 
            +
                assert_equal false, @queue.closed?
         | 
| 212 | 
            +
             | 
| 213 | 
            +
                buf = []
         | 
| 214 | 
            +
                f = spin { buf << @queue.shift }
         | 
| 215 | 
            +
                snooze # allow fiber to start
         | 
| 216 | 
            +
             | 
| 217 | 
            +
                @queue.close
         | 
| 218 | 
            +
                assert_equal true, @queue.closed?
         | 
| 219 | 
            +
                cancel_after(1) { f.await }
         | 
| 220 | 
            +
                assert_equal [nil], buf
         | 
| 221 | 
            +
             | 
| 222 | 
            +
                assert_raises(ClosedQueueError) { @queue << 1 }
         | 
| 223 | 
            +
                assert_raises(ClosedQueueError) { @queue.deq }
         | 
| 224 | 
            +
                assert_raises(ThreadError) { @queue.pop(true) }
         | 
| 225 | 
            +
             | 
| 226 | 
            +
                # test deq on closed non-empty queue
         | 
| 227 | 
            +
                @queue = Polyphony::Queue.new
         | 
| 228 | 
            +
                @queue << 42 << 43
         | 
| 229 | 
            +
                @queue.close
         | 
| 230 | 
            +
             | 
| 231 | 
            +
                assert_equal 42, @queue.deq(false)
         | 
| 232 | 
            +
                assert_equal 43, @queue.deq(true)
         | 
| 131 233 | 
             
              end
         | 
| 132 234 | 
             
            end
         | 
| 133 235 |  | 
| @@ -246,4 +348,4 @@ class CappedQueueTest < MiniTest::Test | |
| 246 348 | 
             
                a.join
         | 
| 247 349 | 
             
                assert_equal [1, 2, 3, :d5, 4, :d8, 5], buffer
         | 
| 248 350 | 
             
              end
         | 
| 249 | 
            -
            end
         | 
| 351 | 
            +
            end
         | 
    
        data/test/test_resource_pool.rb
    CHANGED
    
    
    
        data/test/test_signal.rb
    CHANGED
    
    | @@ -33,24 +33,24 @@ class SignalTrapTest < Minitest::Test | |
| 33 33 | 
             
                Fiber.current.tag = :main
         | 
| 34 34 |  | 
| 35 35 | 
             
                expected = [
         | 
| 36 | 
            -
                  [: | 
| 37 | 
            -
                  [: | 
| 38 | 
            -
                  [: | 
| 39 | 
            -
                  [: | 
| 40 | 
            -
                  [: | 
| 41 | 
            -
                  [: | 
| 42 | 
            -
                  [: | 
| 43 | 
            -
                  [: | 
| 44 | 
            -
                  [: | 
| 45 | 
            -
                  [: | 
| 46 | 
            -
                  [: | 
| 47 | 
            -
                  [: | 
| 36 | 
            +
                  [:block, :main],
         | 
| 37 | 
            +
                  [:enter_poll, :main],
         | 
| 38 | 
            +
                  [:spin, :oob],
         | 
| 39 | 
            +
                  [:schedule, :oob],
         | 
| 40 | 
            +
                  [:leave_poll, :main],
         | 
| 41 | 
            +
                  [:unblock, :oob],
         | 
| 42 | 
            +
                  [:terminate, :oob],
         | 
| 43 | 
            +
                  [:block, :oob],
         | 
| 44 | 
            +
                  [:enter_poll, :oob],
         | 
| 45 | 
            +
                  [:schedule, :main],
         | 
| 46 | 
            +
                  [:leave_poll, :oob],
         | 
| 47 | 
            +
                  [:unblock, :main]
         | 
| 48 48 | 
             
                ]
         | 
| 49 49 | 
             
                if Thread.backend.kind == :libev
         | 
| 50 50 | 
             
                  expected += [
         | 
| 51 | 
            -
                    [: | 
| 52 | 
            -
                    [: | 
| 53 | 
            -
                    [: | 
| 51 | 
            +
                    [:schedule, :main],
         | 
| 52 | 
            +
                    [:block, :main],
         | 
| 53 | 
            +
                    [:unblock, :main]
         | 
| 54 54 | 
             
                  ]
         | 
| 55 55 | 
             
                end
         | 
| 56 56 |  | 
    
        data/test/test_supervise.rb
    CHANGED
    
    | @@ -269,4 +269,31 @@ class SuperviseTest < MiniTest::Test | |
| 269 269 | 
             
                snooze
         | 
| 270 270 | 
             
                assert_equal [[f1, :foo], [f2, :bar]], buffer
         | 
| 271 271 | 
             
              end
         | 
| 272 | 
            +
             | 
| 273 | 
            +
              def test_detached_supervisor
         | 
| 274 | 
            +
                buffer = []
         | 
| 275 | 
            +
             | 
| 276 | 
            +
                s = nil
         | 
| 277 | 
            +
                f = spin {
         | 
| 278 | 
            +
                  foo = spin do
         | 
| 279 | 
            +
                    sleep 0.1
         | 
| 280 | 
            +
                  ensure
         | 
| 281 | 
            +
                    buffer << :foo
         | 
| 282 | 
            +
                  end
         | 
| 283 | 
            +
                  bar = spin do
         | 
| 284 | 
            +
                    sleep 0.2
         | 
| 285 | 
            +
                  ensure
         | 
| 286 | 
            +
                    buffer << :bar
         | 
| 287 | 
            +
                  end
         | 
| 288 | 
            +
             | 
| 289 | 
            +
                  s = spin { supervise }.detach
         | 
| 290 | 
            +
                  Fiber.current.attach_all_children_to(s)
         | 
| 291 | 
            +
             | 
| 292 | 
            +
                  s.terminate(true)
         | 
| 293 | 
            +
                }
         | 
| 294 | 
            +
             | 
| 295 | 
            +
                f.await
         | 
| 296 | 
            +
                s.await
         | 
| 297 | 
            +
                assert_equal [:foo, :bar], buffer
         | 
| 298 | 
            +
              end
         | 
| 272 299 | 
             
            end
         | 
    
        data/test/test_thread.rb
    CHANGED
    
    | @@ -132,7 +132,7 @@ class ThreadTest < MiniTest::Test | |
| 132 132 | 
             
                Thread.backend.trace_proc = proc {|*r| records << r }
         | 
| 133 133 | 
             
                suspend
         | 
| 134 134 | 
             
                assert_equal [
         | 
| 135 | 
            -
                  [: | 
| 135 | 
            +
                  [:block, Fiber.current, ["#{__FILE__}:#{__LINE__ - 2}:in `test_that_suspend_returns_immediately_if_no_watchers'"] + caller]
         | 
| 136 136 | 
             
                ], records
         | 
| 137 137 | 
             
              ensure
         | 
| 138 138 | 
             
                Thread.backend.trace_proc = nil
         |