polyphony 0.45.5 → 0.47.2
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 +2 -0
 - data/.gitmodules +0 -0
 - data/CHANGELOG.md +23 -0
 - data/Gemfile.lock +1 -1
 - data/README.md +3 -3
 - data/Rakefile +1 -1
 - data/TODO.md +21 -22
 - data/bin/test +4 -0
 - data/examples/core/enumerable.rb +64 -0
 - data/examples/performance/fiber_resume.rb +43 -0
 - data/examples/performance/fiber_transfer.rb +13 -4
 - data/examples/performance/thread-vs-fiber/compare.rb +59 -0
 - data/examples/performance/thread-vs-fiber/em_server.rb +33 -0
 - data/examples/performance/thread-vs-fiber/polyphony_server.rb +10 -21
 - data/examples/performance/thread-vs-fiber/threaded_server.rb +22 -15
 - data/examples/performance/thread_switch.rb +44 -0
 - data/ext/liburing/liburing.h +585 -0
 - data/ext/liburing/liburing/README.md +4 -0
 - data/ext/liburing/liburing/barrier.h +73 -0
 - data/ext/liburing/liburing/compat.h +15 -0
 - data/ext/liburing/liburing/io_uring.h +343 -0
 - data/ext/liburing/queue.c +333 -0
 - data/ext/liburing/register.c +187 -0
 - data/ext/liburing/setup.c +210 -0
 - data/ext/liburing/syscall.c +54 -0
 - data/ext/liburing/syscall.h +18 -0
 - data/ext/polyphony/backend.h +0 -14
 - data/ext/polyphony/backend_common.h +129 -0
 - data/ext/polyphony/backend_io_uring.c +995 -0
 - data/ext/polyphony/backend_io_uring_context.c +74 -0
 - data/ext/polyphony/backend_io_uring_context.h +53 -0
 - data/ext/polyphony/{libev_backend.c → backend_libev.c} +304 -294
 - data/ext/polyphony/event.c +1 -1
 - data/ext/polyphony/extconf.rb +31 -13
 - data/ext/polyphony/fiber.c +35 -24
 - data/ext/polyphony/libev.c +4 -0
 - data/ext/polyphony/libev.h +8 -2
 - data/ext/polyphony/liburing.c +8 -0
 - data/ext/polyphony/playground.c +51 -0
 - data/ext/polyphony/polyphony.c +8 -5
 - data/ext/polyphony/polyphony.h +23 -19
 - data/ext/polyphony/polyphony_ext.c +10 -4
 - data/ext/polyphony/queue.c +100 -35
 - data/ext/polyphony/thread.c +10 -10
 - data/lib/polyphony/adapters/trace.rb +2 -2
 - data/lib/polyphony/core/exceptions.rb +0 -4
 - data/lib/polyphony/core/global_api.rb +45 -21
 - data/lib/polyphony/core/resource_pool.rb +12 -1
 - data/lib/polyphony/extensions/core.rb +9 -15
 - data/lib/polyphony/extensions/debug.rb +13 -0
 - data/lib/polyphony/extensions/fiber.rb +8 -4
 - data/lib/polyphony/extensions/openssl.rb +6 -0
 - data/lib/polyphony/extensions/socket.rb +73 -10
 - data/lib/polyphony/version.rb +1 -1
 - data/test/helper.rb +36 -4
 - data/test/io_uring_test.rb +55 -0
 - data/test/stress.rb +4 -1
 - data/test/test_backend.rb +63 -6
 - data/test/test_ext.rb +1 -2
 - data/test/test_fiber.rb +55 -20
 - data/test/test_global_api.rb +107 -35
 - data/test/test_queue.rb +117 -0
 - data/test/test_resource_pool.rb +21 -0
 - data/test/test_socket.rb +2 -2
 - data/test/test_throttler.rb +3 -6
 - data/test/test_trace.rb +7 -5
 - metadata +28 -3
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 8f9e7ffec31a2009f8000e7b88473e21581de37640ee2d5c6357550883d7bc7c
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: d3f8262b8373e7a337dacc0c8e04b1b25ddf0254445b146787a5aa82520dee30
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: 0203bd8d66851f3eeed446f7537183e5b66a422f5f5c68b79a1b99f5c3762e243d1027cd77ccbe255012b9450a56b336c9da2e78e91b867adee8f3353351db7c
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 74ef01b9828d4ba0c6a67cd8a402551484c935234fb7d259146d6cfd5033149f7376aa98068dc8f30168c0ec783015208326455795926d02b385e1bda27d1f37
         
     | 
    
        data/.github/workflows/test.yml
    CHANGED
    
    
    
        data/.gitmodules
    ADDED
    
    | 
         
            File without changes
         
     | 
    
        data/CHANGELOG.md
    CHANGED
    
    | 
         @@ -1,3 +1,26 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            ## 0.47.2
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            * Fix API compatibility between TCPSocket and IO
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            ## 0.47.0
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            * Implement `#spin_scope` used for creating blocking fiber scopes 
         
     | 
| 
      
 8 
     | 
    
         
            +
            * Reimplement `move_on_after`, `cancel_after`, `Timeout.timeout` using
         
     | 
| 
      
 9 
     | 
    
         
            +
              `Backend#timeout` (avoids creating canceller fiber for most common use case)
         
     | 
| 
      
 10 
     | 
    
         
            +
            * Implement `Backend#timeout` API
         
     | 
| 
      
 11 
     | 
    
         
            +
            * Implemented capped queues
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
            ## 0.46.1
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
            * Add `TCPServer#accept_loop`, `OpenSSL::SSL::SSLSocket#accept_loop` method
         
     | 
| 
      
 16 
     | 
    
         
            +
            * Fix compilation error on MacOS (#43)
         
     | 
| 
      
 17 
     | 
    
         
            +
            * Fix backtrace for `Timeout.timeout`
         
     | 
| 
      
 18 
     | 
    
         
            +
            * Add `Backend#timer_loop`
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
            ## 0.46.0
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
            * Implement [io_uring backend](https://github.com/digital-fabric/polyphony/pull/44)
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
       1 
24 
     | 
    
         
             
            ## 0.45.5
         
     | 
| 
       2 
25 
     | 
    
         | 
| 
       3 
26 
     | 
    
         
             
            * Fix compilation error (#43)
         
     | 
    
        data/Gemfile.lock
    CHANGED
    
    
    
        data/README.md
    CHANGED
    
    | 
         @@ -35,9 +35,9 @@ 
     | 
|
| 
       35 
35 
     | 
    
         
             
            Polyphony is a library for building concurrent applications in Ruby. Polyphony
         
     | 
| 
       36 
36 
     | 
    
         
             
            harnesses the power of [Ruby fibers](https://ruby-doc.org/core-2.5.1/Fiber.html)
         
     | 
| 
       37 
37 
     | 
    
         
             
            to provide a cooperative, sequential coroutine-based concurrency model. Under
         
     | 
| 
       38 
     | 
    
         
            -
            the hood, Polyphony uses 
     | 
| 
       39 
     | 
    
         
            -
             
     | 
| 
       40 
     | 
    
         
            -
             
     | 
| 
      
 38 
     | 
    
         
            +
            the hood, Polyphony uses
         
     | 
| 
      
 39 
     | 
    
         
            +
            [io_uring](https://unixism.net/loti/what_is_io_uring.html) or
         
     | 
| 
      
 40 
     | 
    
         
            +
            [libev](https://github.com/enki/libev) to maximize I/O performance.
         
     | 
| 
       41 
41 
     | 
    
         | 
| 
       42 
42 
     | 
    
         
             
            ## Features
         
     | 
| 
       43 
43 
     | 
    
         | 
    
        data/Rakefile
    CHANGED
    
    | 
         @@ -23,4 +23,4 @@ task :docs do 
     | 
|
| 
       23 
23 
     | 
    
         
             
              exec 'RUBYOPT=-W0 jekyll serve -s docs -H ec2-18-156-117-172.eu-central-1.compute.amazonaws.com'
         
     | 
| 
       24 
24 
     | 
    
         
             
            end
         
     | 
| 
       25 
25 
     | 
    
         | 
| 
       26 
     | 
    
         
            -
            CLEAN.include "**/*.o", "**/*.so", "**/*.bundle", "**/*.jar", "pkg", "tmp"
         
     | 
| 
      
 26 
     | 
    
         
            +
            CLEAN.include "**/*.o", "**/*.so", "**/*.so.*", "**/*.a", "**/*.bundle", "**/*.jar", "pkg", "tmp"
         
     | 
    
        data/TODO.md
    CHANGED
    
    | 
         @@ -1,16 +1,24 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
             
     | 
| 
       2 
     | 
    
         
            -
             
     | 
| 
       3 
     | 
    
         
            -
            0.46
         
     | 
| 
      
 1 
     | 
    
         
            +
            ## Roadmap for Polyphony 1.0
         
     | 
| 
       4 
2 
     | 
    
         | 
| 
       5 
     | 
    
         
            -
            -  
     | 
| 
       6 
     | 
    
         
            -
            -  
     | 
| 
      
 3 
     | 
    
         
            +
            - Check why worker-thread example doesn't work.
         
     | 
| 
      
 4 
     | 
    
         
            +
            - Add test that mimics the original design for Monocrono:
         
     | 
| 
      
 5 
     | 
    
         
            +
              - 256 fibers each waiting for a message
         
     | 
| 
      
 6 
     | 
    
         
            +
              - When message received do some blocking work using a `ThreadPool`
         
     | 
| 
      
 7 
     | 
    
         
            +
              - Send messages, collect responses, check for correctness
         
     | 
| 
       7 
8 
     | 
    
         
             
            - Improve `#supervise`. It does not work as advertised, and seems to exhibit an
         
     | 
| 
       8 
9 
     | 
    
         
             
              inconsistent behaviour (see supervisor example).
         
     | 
| 
       9 
     | 
    
         
            -
            - Fix backtrace for `Timeout.timeout` API (see timeout example).
         
     | 
| 
       10 
     | 
    
         
            -
            - Check why worker-thread example doesn't work.
         
     | 
| 
       11 
10 
     | 
    
         | 
| 
       12 
     | 
    
         
            -
             
     | 
| 
      
 11 
     | 
    
         
            +
            - io_uring
         
     | 
| 
      
 12 
     | 
    
         
            +
              - Use playground.c to find out why we when submitting and waiting for
         
     | 
| 
      
 13 
     | 
    
         
            +
                completion in single syscall signals seem to be blocked until the syscall
         
     | 
| 
      
 14 
     | 
    
         
            +
                returns. Is this a bug in io_uring/liburing?
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
            -----------------------------------------------------
         
     | 
| 
       13 
17 
     | 
    
         | 
| 
      
 18 
     | 
    
         
            +
            - Add `Backend#splice(in, out, nbytes)` API
         
     | 
| 
      
 19 
     | 
    
         
            +
            - Adapter for io/console (what does `IO#raw` do?)
         
     | 
| 
      
 20 
     | 
    
         
            +
            - Adapter for Pry and IRB (Which fixes #5 and #6)
         
     | 
| 
      
 21 
     | 
    
         
            +
            - allow backend selection at runtime
         
     | 
| 
       14 
22 
     | 
    
         
             
            - Debugging
         
     | 
| 
       15 
23 
     | 
    
         
             
              - Eat your own dogfood: need a good tool to check what's going on when some
         
     | 
| 
       16 
24 
     | 
    
         
             
                test fails
         
     | 
| 
         @@ -148,6 +156,11 @@ 
     | 
|
| 
       148 
156 
     | 
    
         
             
              - `IO.foreach`
         
     | 
| 
       149 
157 
     | 
    
         
             
              - `Process.waitpid`
         
     | 
| 
       150 
158 
     | 
    
         | 
| 
      
 159 
     | 
    
         
            +
            ### Quic / HTTP/3
         
     | 
| 
      
 160 
     | 
    
         
            +
             
     | 
| 
      
 161 
     | 
    
         
            +
            - Python impl: https://github.com/aiortc/aioquic/
         
     | 
| 
      
 162 
     | 
    
         
            +
            - Go impl: https://github.com/lucas-clemente/quic-go
         
     | 
| 
      
 163 
     | 
    
         
            +
             
     | 
| 
       151 
164 
     | 
    
         
             
            ### DNS client
         
     | 
| 
       152 
165 
     | 
    
         | 
| 
       153 
166 
     | 
    
         
             
            ```ruby
         
     | 
| 
         @@ -179,17 +192,3 @@ Prior art: 
     | 
|
| 
       179 
192 
     | 
    
         | 
| 
       180 
193 
     | 
    
         
             
            - https://github.com/socketry/async-dns
         
     | 
| 
       181 
194 
     | 
    
         | 
| 
       182 
     | 
    
         
            -
            ## Work on API
         
     | 
| 
       183 
     | 
    
         
            -
             
     | 
| 
       184 
     | 
    
         
            -
            - Add option for setting the exception raised on cancelling using `#cancel_after`:
         
     | 
| 
       185 
     | 
    
         
            -
             
     | 
| 
       186 
     | 
    
         
            -
            ```ruby
         
     | 
| 
       187 
     | 
    
         
            -
            cancel_after(3, with_error: MyErrorClass) do
         
     | 
| 
       188 
     | 
    
         
            -
              do_my_thing
         
     | 
| 
       189 
     | 
    
         
            -
            end
         
     | 
| 
       190 
     | 
    
         
            -
            # or a RuntimeError with message
         
     | 
| 
       191 
     | 
    
         
            -
            cancel_after(3, with_error: 'Cancelled due to timeout') do
         
     | 
| 
       192 
     | 
    
         
            -
              do_my_thing
         
     | 
| 
       193 
     | 
    
         
            -
            end
         
     | 
| 
       194 
     | 
    
         
            -
            ```
         
     | 
| 
       195 
     | 
    
         
            -
             
     | 
    
        data/bin/test
    ADDED
    
    
| 
         @@ -0,0 +1,64 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require 'bundler/setup'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'polyphony'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            Exception.__disable_sanitized_backtrace__ = true
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            module Enumerable
         
     | 
| 
      
 9 
     | 
    
         
            +
              def map_concurrently(&block)
         
     | 
| 
      
 10 
     | 
    
         
            +
                spin do
         
     | 
| 
      
 11 
     | 
    
         
            +
                  results = []
         
     | 
| 
      
 12 
     | 
    
         
            +
                  each_with_index do |i, idx|
         
     | 
| 
      
 13 
     | 
    
         
            +
                    spin { results[idx] = block.(i) }
         
     | 
| 
      
 14 
     | 
    
         
            +
                  end
         
     | 
| 
      
 15 
     | 
    
         
            +
                  Fiber.current.await_all_children
         
     | 
| 
      
 16 
     | 
    
         
            +
                  results
         
     | 
| 
      
 17 
     | 
    
         
            +
                end.await
         
     | 
| 
      
 18 
     | 
    
         
            +
              end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
              def each_concurrently(max_fibers: nil, &block)
         
     | 
| 
      
 21 
     | 
    
         
            +
                return each_concurrently_with_fiber_pool(max_fibers, &block) if max_fibers
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                spin do
         
     | 
| 
      
 24 
     | 
    
         
            +
                  results = []
         
     | 
| 
      
 25 
     | 
    
         
            +
                  each do |i|
         
     | 
| 
      
 26 
     | 
    
         
            +
                    spin(&block).schedule(i)
         
     | 
| 
      
 27 
     | 
    
         
            +
                  end
         
     | 
| 
      
 28 
     | 
    
         
            +
                  Fiber.current.await_all_children
         
     | 
| 
      
 29 
     | 
    
         
            +
                end.await
         
     | 
| 
      
 30 
     | 
    
         
            +
                self
         
     | 
| 
      
 31 
     | 
    
         
            +
              end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
              def each_concurrently_with_fiber_pool(max_fibers, &block)
         
     | 
| 
      
 34 
     | 
    
         
            +
                spin do
         
     | 
| 
      
 35 
     | 
    
         
            +
                  fiber_count = 0
         
     | 
| 
      
 36 
     | 
    
         
            +
                  workers = []
         
     | 
| 
      
 37 
     | 
    
         
            +
                  each do |i|
         
     | 
| 
      
 38 
     | 
    
         
            +
                    if fiber_count < max_fibers
         
     | 
| 
      
 39 
     | 
    
         
            +
                      workers << spin do
         
     | 
| 
      
 40 
     | 
    
         
            +
                        loop do
         
     | 
| 
      
 41 
     | 
    
         
            +
                          item = receive
         
     | 
| 
      
 42 
     | 
    
         
            +
                          break if item == :__stop__
         
     | 
| 
      
 43 
     | 
    
         
            +
                          block.(item)
         
     | 
| 
      
 44 
     | 
    
         
            +
                        end
         
     | 
| 
      
 45 
     | 
    
         
            +
                      end
         
     | 
| 
      
 46 
     | 
    
         
            +
                    end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                    fiber = workers.shift
         
     | 
| 
      
 49 
     | 
    
         
            +
                    fiber << i
         
     | 
| 
      
 50 
     | 
    
         
            +
                    workers << fiber
         
     | 
| 
      
 51 
     | 
    
         
            +
                  end
         
     | 
| 
      
 52 
     | 
    
         
            +
                  workers.each { |f| f << :__stop__ }
         
     | 
| 
      
 53 
     | 
    
         
            +
                  Fiber.current.await_all_children
         
     | 
| 
      
 54 
     | 
    
         
            +
                end.await
         
     | 
| 
      
 55 
     | 
    
         
            +
                self
         
     | 
| 
      
 56 
     | 
    
         
            +
              end
         
     | 
| 
      
 57 
     | 
    
         
            +
            end
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
            o = 1..3
         
     | 
| 
      
 60 
     | 
    
         
            +
            o.each_concurrently(max_fibers: 2) do |i|
         
     | 
| 
      
 61 
     | 
    
         
            +
              puts "#{Fiber.current} sleep #{i}"
         
     | 
| 
      
 62 
     | 
    
         
            +
              sleep(i)
         
     | 
| 
      
 63 
     | 
    
         
            +
              puts "wakeup #{i}"
         
     | 
| 
      
 64 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,43 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require 'fiber'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            class Fiber
         
     | 
| 
      
 6 
     | 
    
         
            +
              attr_accessor :next
         
     | 
| 
      
 7 
     | 
    
         
            +
            end
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            # This program shows how the performance of Fiber.transfer degrades as the fiber
         
     | 
| 
      
 10 
     | 
    
         
            +
            # count increases
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            def run(num_fibers)
         
     | 
| 
      
 13 
     | 
    
         
            +
              count = 0
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
              GC.start
         
     | 
| 
      
 16 
     | 
    
         
            +
              GC.disable
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
              fibers = []
         
     | 
| 
      
 19 
     | 
    
         
            +
              num_fibers.times do
         
     | 
| 
      
 20 
     | 
    
         
            +
                fibers << Fiber.new { loop { Fiber.yield } }
         
     | 
| 
      
 21 
     | 
    
         
            +
              end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
              t0 = Time.now
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
              while count < 1000000
         
     | 
| 
      
 26 
     | 
    
         
            +
                fibers.each do |f|
         
     | 
| 
      
 27 
     | 
    
         
            +
                  count += 1
         
     | 
| 
      
 28 
     | 
    
         
            +
                  f.resume
         
     | 
| 
      
 29 
     | 
    
         
            +
                end
         
     | 
| 
      
 30 
     | 
    
         
            +
              end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
              elapsed = Time.now - t0
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
              puts "fibers: #{num_fibers} count: #{count} rate: #{count / elapsed}"
         
     | 
| 
      
 35 
     | 
    
         
            +
            rescue Exception => e
         
     | 
| 
      
 36 
     | 
    
         
            +
              puts "Stopped at #{count} fibers"
         
     | 
| 
      
 37 
     | 
    
         
            +
              p e
         
     | 
| 
      
 38 
     | 
    
         
            +
            end
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
            run(100)
         
     | 
| 
      
 41 
     | 
    
         
            +
            run(1000)
         
     | 
| 
      
 42 
     | 
    
         
            +
            run(10000)
         
     | 
| 
      
 43 
     | 
    
         
            +
            run(100000)
         
     | 
| 
         @@ -12,6 +12,7 @@ end 
     | 
|
| 
       12 
12 
     | 
    
         
             
            def run(num_fibers)
         
     | 
| 
       13 
13 
     | 
    
         
             
              count = 0
         
     | 
| 
       14 
14 
     | 
    
         | 
| 
      
 15 
     | 
    
         
            +
              GC.start
         
     | 
| 
       15 
16 
     | 
    
         
             
              GC.disable
         
     | 
| 
       16 
17 
     | 
    
         | 
| 
       17 
18 
     | 
    
         
             
              first = nil
         
     | 
| 
         @@ -36,13 +37,21 @@ def run(num_fibers) 
     | 
|
| 
       36 
37 
     | 
    
         
             
              last.next = first
         
     | 
| 
       37 
38 
     | 
    
         | 
| 
       38 
39 
     | 
    
         
             
              t0 = Time.now
         
     | 
| 
      
 40 
     | 
    
         
            +
              puts "start transfer..."
         
     | 
| 
       39 
41 
     | 
    
         
             
              first.transfer
         
     | 
| 
       40 
42 
     | 
    
         
             
              elapsed = Time.now - t0
         
     | 
| 
       41 
43 
     | 
    
         | 
| 
       42 
     | 
    
         
            -
               
     | 
| 
       43 
     | 
    
         
            -
             
     | 
| 
      
 44 
     | 
    
         
            +
              rss = `ps -o rss= -p #{Process.pid}`.to_i
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
              puts "fibers: #{num_fibers} rss: #{rss} count: #{count} rate: #{count / elapsed}"
         
     | 
| 
      
 47 
     | 
    
         
            +
            rescue Exception => e
         
     | 
| 
      
 48 
     | 
    
         
            +
              puts "Stopped at #{count} fibers"
         
     | 
| 
      
 49 
     | 
    
         
            +
              p e
         
     | 
| 
       44 
50 
     | 
    
         
             
            end
         
     | 
| 
       45 
51 
     | 
    
         | 
| 
      
 52 
     | 
    
         
            +
            puts "pid: #{Process.pid}"
         
     | 
| 
       46 
53 
     | 
    
         
             
            run(100)
         
     | 
| 
       47 
     | 
    
         
            -
            run(1000)
         
     | 
| 
       48 
     | 
    
         
            -
            run(10000)
         
     | 
| 
      
 54 
     | 
    
         
            +
            # run(1000)
         
     | 
| 
      
 55 
     | 
    
         
            +
            # run(10000)
         
     | 
| 
      
 56 
     | 
    
         
            +
            # run(100000)
         
     | 
| 
      
 57 
     | 
    
         
            +
            # run(400000)
         
     | 
| 
         @@ -0,0 +1,59 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            SERVERS = {
         
     | 
| 
      
 2 
     | 
    
         
            +
              polyphony: {
         
     | 
| 
      
 3 
     | 
    
         
            +
                port: 1234,
         
     | 
| 
      
 4 
     | 
    
         
            +
                cmd: 'ruby examples/performance/thread-vs-fiber/polyphony_server.rb'
         
     | 
| 
      
 5 
     | 
    
         
            +
              },
         
     | 
| 
      
 6 
     | 
    
         
            +
              threaded: {
         
     | 
| 
      
 7 
     | 
    
         
            +
                port: 1235,
         
     | 
| 
      
 8 
     | 
    
         
            +
                cmd: 'ruby examples/performance/thread-vs-fiber/threaded_server.rb'
         
     | 
| 
      
 9 
     | 
    
         
            +
              },
         
     | 
| 
      
 10 
     | 
    
         
            +
              em: {
         
     | 
| 
      
 11 
     | 
    
         
            +
                port: 1236,
         
     | 
| 
      
 12 
     | 
    
         
            +
                cmd: 'ruby examples/performance/thread-vs-fiber/em_server.rb'
         
     | 
| 
      
 13 
     | 
    
         
            +
              }
         
     | 
| 
      
 14 
     | 
    
         
            +
            }
         
     | 
| 
      
 15 
     | 
    
         
            +
            SETTINGS = [
         
     | 
| 
      
 16 
     | 
    
         
            +
              '-t1 -c1',
         
     | 
| 
      
 17 
     | 
    
         
            +
              '-t4 -c8',
         
     | 
| 
      
 18 
     | 
    
         
            +
              '-t8 -c64',
         
     | 
| 
      
 19 
     | 
    
         
            +
              '-t16 -c512',
         
     | 
| 
      
 20 
     | 
    
         
            +
              '-t32 -c4096',
         
     | 
| 
      
 21 
     | 
    
         
            +
              '-t64 -c8192',
         
     | 
| 
      
 22 
     | 
    
         
            +
              '-t128 -c16384',
         
     | 
| 
      
 23 
     | 
    
         
            +
              '-t256 -c32768'
         
     | 
| 
      
 24 
     | 
    
         
            +
            ]
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
            def run_test(name, port, cmd, setting)
         
     | 
| 
      
 27 
     | 
    
         
            +
              puts "*" * 80
         
     | 
| 
      
 28 
     | 
    
         
            +
              puts "Run #{name} (#{port}): #{setting}"
         
     | 
| 
      
 29 
     | 
    
         
            +
              puts "*" * 80
         
     | 
| 
      
 30 
     | 
    
         
            +
              
         
     | 
| 
      
 31 
     | 
    
         
            +
              pid = spawn("#{cmd} > /dev/null 2>&1")
         
     | 
| 
      
 32 
     | 
    
         
            +
              sleep 1
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
              output = `wrk -d60 #{setting} \"http://127.0.0.1:#{port}/\"`
         
     | 
| 
      
 35 
     | 
    
         
            +
              puts output
         
     | 
| 
      
 36 
     | 
    
         
            +
              (output =~ /Requests\/sec:\s+(\d+)/) && $1.to_i
         
     | 
| 
      
 37 
     | 
    
         
            +
            ensure
         
     | 
| 
      
 38 
     | 
    
         
            +
              Process.kill('KILL', pid)
         
     | 
| 
      
 39 
     | 
    
         
            +
              Process.wait(pid)
         
     | 
| 
      
 40 
     | 
    
         
            +
              3.times { puts }
         
     | 
| 
      
 41 
     | 
    
         
            +
            end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
            def perform_benchmark
         
     | 
| 
      
 44 
     | 
    
         
            +
              results = []
         
     | 
| 
      
 45 
     | 
    
         
            +
              SETTINGS.each do |s|
         
     | 
| 
      
 46 
     | 
    
         
            +
                results << SERVERS.inject({}) do |h, (n, o)|
         
     | 
| 
      
 47 
     | 
    
         
            +
                  h[n] = run_test(n, o[:port], o[:cmd], s)
         
     | 
| 
      
 48 
     | 
    
         
            +
                  h
         
     | 
| 
      
 49 
     | 
    
         
            +
                end
         
     | 
| 
      
 50 
     | 
    
         
            +
              end
         
     | 
| 
      
 51 
     | 
    
         
            +
              results
         
     | 
| 
      
 52 
     | 
    
         
            +
            end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
            results = []
         
     | 
| 
      
 55 
     | 
    
         
            +
            3.times { results << perform_benchmark }
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
            require 'pp'
         
     | 
| 
      
 58 
     | 
    
         
            +
            puts "results:"
         
     | 
| 
      
 59 
     | 
    
         
            +
            pp results
         
     | 
| 
         @@ -0,0 +1,33 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'eventmachine'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'http/parser'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'socket'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module HTTPServer
         
     | 
| 
      
 6 
     | 
    
         
            +
              def post_init
         
     | 
| 
      
 7 
     | 
    
         
            +
                @parser = Http::Parser.new
         
     | 
| 
      
 8 
     | 
    
         
            +
                @pending_requests = []
         
     | 
| 
      
 9 
     | 
    
         
            +
                @parser.on_message_complete = proc { @pending_requests << @parser }
         
     | 
| 
      
 10 
     | 
    
         
            +
              end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
              def receive_data(data)
         
     | 
| 
      
 13 
     | 
    
         
            +
                @parser << data
         
     | 
| 
      
 14 
     | 
    
         
            +
                write_response while @pending_requests.shift
         
     | 
| 
      
 15 
     | 
    
         
            +
              end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
              def write_response
         
     | 
| 
      
 18 
     | 
    
         
            +
                status_code = "200 OK"
         
     | 
| 
      
 19 
     | 
    
         
            +
                data = "Hello world!\n"
         
     | 
| 
      
 20 
     | 
    
         
            +
                headers = "Content-Type: text/plain\r\nContent-Length: #{data.bytesize}\r\n"
         
     | 
| 
      
 21 
     | 
    
         
            +
                send_data "HTTP/1.1 #{status_code}\r\n#{headers}\r\n#{data}"
         
     | 
| 
      
 22 
     | 
    
         
            +
              end
         
     | 
| 
      
 23 
     | 
    
         
            +
            end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
            EventMachine::run do
         
     | 
| 
      
 26 
     | 
    
         
            +
              EventMachine::start_server(
         
     | 
| 
      
 27 
     | 
    
         
            +
                '0.0.0.0',
         
     | 
| 
      
 28 
     | 
    
         
            +
                1236,
         
     | 
| 
      
 29 
     | 
    
         
            +
                HTTPServer
         
     | 
| 
      
 30 
     | 
    
         
            +
              )
         
     | 
| 
      
 31 
     | 
    
         
            +
              puts "pid #{Process.pid} EventMachine listening on port 1236"
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -4,42 +4,31 @@ require 'bundler/setup' 
     | 
|
| 
       4 
4 
     | 
    
         
             
            require 'polyphony'
         
     | 
| 
       5 
5 
     | 
    
         
             
            require 'http/parser'
         
     | 
| 
       6 
6 
     | 
    
         | 
| 
       7 
     | 
    
         
            -
            $connection_count = 0
         
     | 
| 
       8 
     | 
    
         
            -
             
     | 
| 
       9 
7 
     | 
    
         
             
            def handle_client(socket)
         
     | 
| 
       10 
     | 
    
         
            -
               
     | 
| 
      
 8 
     | 
    
         
            +
              pending_requests = []
         
     | 
| 
       11 
9 
     | 
    
         
             
              parser = Http::Parser.new
         
     | 
| 
       12 
     | 
    
         
            -
               
     | 
| 
       13 
     | 
    
         
            -
             
     | 
| 
       14 
     | 
    
         
            -
             
     | 
| 
       15 
     | 
    
         
            -
              end
         
     | 
| 
       16 
     | 
    
         
            -
              socket.read_loop do |data|
         
     | 
| 
      
 10 
     | 
    
         
            +
              parser.on_message_complete = proc { pending_requests << parser }
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
              socket.recv_loop do |data|
         
     | 
| 
       17 
13 
     | 
    
         
             
                parser << data
         
     | 
| 
       18 
     | 
    
         
            -
                 
     | 
| 
       19 
     | 
    
         
            -
                  handle_request(socket, req)
         
     | 
| 
       20 
     | 
    
         
            -
                  req = nil
         
     | 
| 
       21 
     | 
    
         
            -
                  snooze
         
     | 
| 
       22 
     | 
    
         
            -
                end
         
     | 
| 
      
 14 
     | 
    
         
            +
                write_response(socket) while pending_requests.shift
         
     | 
| 
       23 
15 
     | 
    
         
             
              end
         
     | 
| 
       24 
16 
     | 
    
         
             
            rescue IOError, SystemCallError => e
         
     | 
| 
       25 
17 
     | 
    
         
             
              # do nothing
         
     | 
| 
       26 
18 
     | 
    
         
             
            ensure
         
     | 
| 
       27 
     | 
    
         
            -
              $connection_count -= 1
         
     | 
| 
       28 
19 
     | 
    
         
             
              socket&.close
         
     | 
| 
       29 
20 
     | 
    
         
             
            end
         
     | 
| 
       30 
21 
     | 
    
         | 
| 
       31 
     | 
    
         
            -
            def  
     | 
| 
      
 22 
     | 
    
         
            +
            def write_response(socket)
         
     | 
| 
       32 
23 
     | 
    
         
             
              status_code = "200 OK"
         
     | 
| 
       33 
24 
     | 
    
         
             
              data = "Hello world!\n"
         
     | 
| 
       34 
25 
     | 
    
         
             
              headers = "Content-Type: text/plain\r\nContent-Length: #{data.bytesize}\r\n"
         
     | 
| 
       35 
     | 
    
         
            -
               
     | 
| 
      
 26 
     | 
    
         
            +
              socket.write "HTTP/1.1 #{status_code}\r\n#{headers}\r\n#{data}"
         
     | 
| 
       36 
27 
     | 
    
         
             
            end
         
     | 
| 
       37 
28 
     | 
    
         | 
| 
       38 
29 
     | 
    
         
             
            server = TCPServer.open('0.0.0.0', 1234)
         
     | 
| 
       39 
     | 
    
         
            -
            puts "pid #{Process.pid}"
         
     | 
| 
       40 
     | 
    
         
            -
            puts "listening on port 1234"
         
     | 
| 
      
 30 
     | 
    
         
            +
            puts "pid #{Process.pid} Polyphony (#{Thread.current.backend.kind}) listening on port 1234"
         
     | 
| 
       41 
31 
     | 
    
         | 
| 
       42 
     | 
    
         
            -
             
     | 
| 
       43 
     | 
    
         
            -
               
     | 
| 
       44 
     | 
    
         
            -
              spin { handle_client(client) }
         
     | 
| 
      
 32 
     | 
    
         
            +
            server.accept_loop do |c|
         
     | 
| 
      
 33 
     | 
    
         
            +
              spin { handle_client(c) }
         
     | 
| 
       45 
34 
     | 
    
         
             
            end
         
     | 
| 
         @@ -1,23 +1,30 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            require 'thread'
         
     | 
| 
       2 
1 
     | 
    
         
             
            require 'http/parser'
         
     | 
| 
       3 
2 
     | 
    
         
             
            require 'socket'
         
     | 
| 
       4 
3 
     | 
    
         | 
| 
       5 
     | 
    
         
            -
            def handle_client( 
     | 
| 
       6 
     | 
    
         
            -
               
     | 
| 
       7 
     | 
    
         
            -
             
     | 
| 
       8 
     | 
    
         
            -
             
     | 
| 
       9 
     | 
    
         
            -
             
     | 
| 
       10 
     | 
    
         
            -
             
     | 
| 
       11 
     | 
    
         
            -
             
     | 
| 
       12 
     | 
    
         
            -
             
     | 
| 
       13 
     | 
    
         
            -
                end
         
     | 
| 
       14 
     | 
    
         
            -
                client.read_loop { |data| parser << data }
         
     | 
| 
       15 
     | 
    
         
            -
                client.close
         
     | 
| 
      
 4 
     | 
    
         
            +
            def handle_client(socket)
         
     | 
| 
      
 5 
     | 
    
         
            +
              pending_requests = []
         
     | 
| 
      
 6 
     | 
    
         
            +
              parser = Http::Parser.new
         
     | 
| 
      
 7 
     | 
    
         
            +
              parser.on_message_complete = proc { pending_requests << parser }
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
              while (data = socket.recv(8192))
         
     | 
| 
      
 10 
     | 
    
         
            +
                parser << data
         
     | 
| 
      
 11 
     | 
    
         
            +
                write_response(socket) while pending_requests.shift
         
     | 
| 
       16 
12 
     | 
    
         
             
              end
         
     | 
| 
      
 13 
     | 
    
         
            +
            rescue IOError, SystemCallError => e
         
     | 
| 
      
 14 
     | 
    
         
            +
              # ignore
         
     | 
| 
      
 15 
     | 
    
         
            +
            ensure
         
     | 
| 
      
 16 
     | 
    
         
            +
              socket.close
         
     | 
| 
      
 17 
     | 
    
         
            +
            end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
            def write_response(socket)
         
     | 
| 
      
 20 
     | 
    
         
            +
              status_code = "200 OK"
         
     | 
| 
      
 21 
     | 
    
         
            +
              data = "Hello world!\n"
         
     | 
| 
      
 22 
     | 
    
         
            +
              headers = "Content-Type: text/plain\r\nContent-Length: #{data.bytesize}\r\n"
         
     | 
| 
      
 23 
     | 
    
         
            +
              socket.write "HTTP/1.1 #{status_code}\r\n#{headers}\r\n#{data}"
         
     | 
| 
       17 
24 
     | 
    
         
             
            end
         
     | 
| 
       18 
25 
     | 
    
         | 
| 
       19 
     | 
    
         
            -
            server = TCPServer.open( 
     | 
| 
       20 
     | 
    
         
            -
            puts " 
     | 
| 
      
 26 
     | 
    
         
            +
            server = TCPServer.open(1235)
         
     | 
| 
      
 27 
     | 
    
         
            +
            puts "pid #{Process.pid} threaded listening on port 1235"
         
     | 
| 
       21 
28 
     | 
    
         
             
            while socket = server.accept
         
     | 
| 
       22 
     | 
    
         
            -
              handle_client(socket)
         
     | 
| 
      
 29 
     | 
    
         
            +
              Thread.new { handle_client(socket) }
         
     | 
| 
       23 
30 
     | 
    
         
             
            end
         
     |