polyphony 0.47.5.1 → 0.50.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +29 -0
- data/Gemfile.lock +1 -1
- data/LICENSE +1 -1
- data/TODO.md +37 -17
- data/examples/core/nested.rb +21 -0
- data/examples/core/suspend.rb +13 -0
- data/examples/core/terminate_main_fiber.rb +12 -0
- data/examples/io/tcp_proxy.rb +32 -0
- data/examples/performance/line_splitting.rb +34 -0
- data/examples/performance/loop.rb +32 -0
- data/examples/performance/thread-vs-fiber/polyphony_server.rb +6 -2
- data/ext/polyphony/backend_common.h +3 -3
- data/ext/polyphony/backend_io_uring.c +29 -68
- data/ext/polyphony/backend_libev.c +18 -59
- data/ext/polyphony/event.c +1 -1
- data/ext/polyphony/fiber.c +2 -1
- data/ext/polyphony/polyphony.c +0 -2
- data/ext/polyphony/polyphony.h +6 -4
- data/ext/polyphony/queue.c +2 -2
- data/ext/polyphony/runqueue.c +6 -0
- data/ext/polyphony/runqueue_ring_buffer.c +9 -0
- data/ext/polyphony/runqueue_ring_buffer.h +1 -0
- data/ext/polyphony/thread.c +23 -28
- data/lib/polyphony.rb +2 -1
- data/lib/polyphony/adapters/postgres.rb +3 -3
- data/lib/polyphony/adapters/process.rb +2 -0
- data/lib/polyphony/core/exceptions.rb +1 -0
- data/lib/polyphony/core/global_api.rb +15 -3
- data/lib/polyphony/core/thread_pool.rb +3 -1
- data/lib/polyphony/core/throttler.rb +1 -1
- data/lib/polyphony/core/timer.rb +115 -0
- data/lib/polyphony/extensions/core.rb +4 -4
- data/lib/polyphony/extensions/fiber.rb +30 -13
- data/lib/polyphony/extensions/io.rb +8 -14
- data/lib/polyphony/extensions/openssl.rb +4 -4
- data/lib/polyphony/extensions/socket.rb +1 -1
- data/lib/polyphony/extensions/thread.rb +1 -2
- data/lib/polyphony/net.rb +3 -6
- data/lib/polyphony/version.rb +1 -1
- data/polyphony.gemspec +1 -1
- data/test/helper.rb +2 -2
- data/test/test_backend.rb +26 -1
- data/test/test_fiber.rb +95 -1
- data/test/test_global_api.rb +30 -0
- data/test/test_io.rb +26 -0
- data/test/test_signal.rb +1 -2
- data/test/test_socket.rb +5 -5
- data/test/test_supervise.rb +1 -1
- data/test/test_timer.rb +157 -0
- metadata +11 -4
- data/ext/polyphony/backend.h +0 -26
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 2aac80a08ca16498c3ee0044b0ba6de039b179425051cbcee67af4c9b948c349
         | 
| 4 | 
            +
              data.tar.gz: bbe016d6b2ad3c5c89129089f9ebb8457a37159e03be2ff84f2572454880b0c2
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 2f91d0b40bceb4600e4bbd22f39cdbd6bfd4ac5b5fc038f77deb7d26a464ce3cad177884f9aadde60c78bc48df170ab6909c44d133283af3abf77a090ac58332
         | 
| 7 | 
            +
              data.tar.gz: 37dfa412a25f41dd704a203a865817bf273a23db0ccbe1bbec1a3f6017b9cd953301d67077e3b58601485f4dd96db94bef77da3d46cabd57fb563d3652173175
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -1,3 +1,32 @@ | |
| 1 | 
            +
            ## 0.50.0
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            - Use `Process::CLOCK_MONOTONIC` in Timer
         | 
| 4 | 
            +
            - Add `Timer#sleep`, `Timer#after`, `Timer#every`
         | 
| 5 | 
            +
            - Prevent fiber from being resumed after terminating
         | 
| 6 | 
            +
            - Add `Thread#fiber_index_of` method
         | 
| 7 | 
            +
            - Use `Backend#wait_event` in `Fiber#await`
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            ## 0.49.2
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            - Fix hang with 100s or more child fibers when terminating
         | 
| 12 | 
            +
            - Fix double pending_count increment in io_uring backend
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            ## 0.49.1
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            - Use `TCPSocket` instead of `Socket` in `Net.tcp_connect`
         | 
| 17 | 
            +
            - Catch `Errno::ERSCH` in `Process.kill_and_await`
         | 
| 18 | 
            +
            - Set io_uring queue size to 2048
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            ## 0.49.0
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            - Implement `Polyphony::Timer` for performant timeouts
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            ## 0.48.0
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            - Implement graceful shutdown
         | 
| 27 | 
            +
            - Add support for `break` / `StopIteration` in `spin_loop`
         | 
| 28 | 
            +
            - Fix `IO#gets`, `IO#readpartial`
         | 
| 29 | 
            +
             | 
| 1 30 | 
             
            ## 0.47.5.1
         | 
| 2 31 |  | 
| 3 32 | 
             
            - Add missing `Socket#accept_loop` method
         | 
    
        data/Gemfile.lock
    CHANGED
    
    
    
        data/LICENSE
    CHANGED
    
    
    
        data/TODO.md
    CHANGED
    
    | @@ -1,29 +1,49 @@ | |
| 1 | 
            -
            -  | 
| 1 | 
            +
            - Check segfault when resetting a `cancel_after` timeout lots of times at very high rate
         | 
| 2 | 
            +
            - Check why `throttled_loop` inside of `move_on_after` fails to stop
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            - Commented out `io_uring_sqe_set_flags(sqe, IOSQE_ASYNC);` in `io_uring_backend_defer_submit_and_await`:
         | 
| 5 | 
            +
              - This flag should be set for I/O ops, not for other stuff
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            - Override stock `::SizedQueue` impl with Queue with capacity
         | 
| 2 8 |  | 
| 3 | 
            -
             | 
| 4 | 
            -
                - `Fiber#terminate(graceul)` - with a graceful flag
         | 
| 5 | 
            -
                - `Fiber#terminate_all_children(graceful)` - with a graceful flag
         | 
| 6 | 
            -
                - `Fiber#shutdown_all_children(graceful)` - with a graceful flag
         | 
| 9 | 
            +
            - Add support for `break` and `StopIteration` in all loops (with tests)
         | 
| 7 10 |  | 
| 8 | 
            -
             | 
| 9 | 
            -
               | 
| 10 | 
            -
             | 
| 11 | 
            +
            - Change `IO#gets` to use `String#split` to cut into lines, much faster (see
         | 
| 12 | 
            +
              examples/performance/line_splitting.rb)
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            - More tight loops
         | 
| 15 | 
            +
              - `IO#gets_loop`, `Socket#gets_loop`, `OpenSSL::Socket#gets_loop` (medium effort)
         | 
| 16 | 
            +
              - `Fiber#receive_loop` (very little effort, should be implemented in C)
         | 
| 11 17 |  | 
| 12 | 
            -
             | 
| 18 | 
            +
             | 
| 19 | 
            +
            - Add `Backend#splice`, `Backend#splice_loop` for implementing stuff like proxying:
         | 
| 13 20 |  | 
| 14 21 | 
             
              ```ruby
         | 
| 15 | 
            -
               | 
| 16 | 
            -
                 | 
| 17 | 
            -
             | 
| 18 | 
            -
                 | 
| 22 | 
            +
              def two_way_proxy(socket1, socket2)
         | 
| 23 | 
            +
                backend = Thread.current.backend
         | 
| 24 | 
            +
                f1 = spin { backend.splice_loop(socket1, socket2) }
         | 
| 25 | 
            +
                f2 = spin { backend.splice_loop(socket2, socket1) }
         | 
| 26 | 
            +
                Fiber.await(f1, f2)
         | 
| 19 27 | 
             
              end
         | 
| 20 28 | 
             
              ```
         | 
| 21 29 |  | 
| 30 | 
            +
            - Graceful shutdown again:
         | 
| 31 | 
            +
              - What happens to children when doing a graceful shutdown?
         | 
| 32 | 
            +
              - What are the implications of passing graceful shutdown flag to children?
         | 
| 33 | 
            +
              - What about errors while doing a graceful shutdown?
         | 
| 34 | 
            +
              - What about graceful restarts?
         | 
| 35 | 
            +
              - Some interesting discussions:
         | 
| 36 | 
            +
                - https://trio.discourse.group/search?q=graceful%20shutdown
         | 
| 37 | 
            +
                - https://github.com/python-trio/trio/issues/147
         | 
| 38 | 
            +
                - https://github.com/python-trio/trio/issues/143
         | 
| 39 | 
            +
                - https://trio.discourse.group/t/graceful-shutdown/93/2
         | 
| 40 | 
            +
                - https://250bpm.com/blog:146/
         | 
| 41 | 
            +
                - https://www.rodrigoaraujo.me/posts/golang-pattern-graceful-shutdown-of-concurrent-events/
         | 
| 42 | 
            +
                - https://github.com/tj/go-gracefully
         | 
| 22 43 | 
             
              - `Fiber#finalize_children` should pass graceful shutdown flag to children
         | 
| 23 | 
            -
             | 
| 24 | 
            -
            -  | 
| 25 | 
            -
             | 
| 26 | 
            -
              - Fiber#receive_loop (very little effort, should be implemented in C)
         | 
| 44 | 
            +
              - A good use case is an HTTP server that on graceful shutdown:
         | 
| 45 | 
            +
                - stops listening
         | 
| 46 | 
            +
                - waits for all ongoing requests to finish, optionally with a timeout
         | 
| 27 47 |  | 
| 28 48 | 
             
            ## Roadmap for Polyphony 1.0
         | 
| 29 49 |  | 
| @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'bundler/setup'
         | 
| 4 | 
            +
            require 'polyphony'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            def process
         | 
| 7 | 
            +
              p :b_start
         | 
| 8 | 
            +
              sleep 1
         | 
| 9 | 
            +
              p :b_stop
         | 
| 10 | 
            +
            end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            spin do
         | 
| 13 | 
            +
              p :a_start
         | 
| 14 | 
            +
              spin { process }
         | 
| 15 | 
            +
              sleep 60
         | 
| 16 | 
            +
              p :a_stop
         | 
| 17 | 
            +
            end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            p :main_start
         | 
| 20 | 
            +
            sleep 120
         | 
| 21 | 
            +
            p :main_stop
         | 
| @@ -0,0 +1,32 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'bundler/setup'
         | 
| 4 | 
            +
            require 'polyphony'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            server1 = TCPServer.open('127.0.0.1', 1234)
         | 
| 7 | 
            +
            server2 = TCPServer.open('127.0.0.1', 1235)
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            puts "Pid: #{Process.pid}"
         | 
| 10 | 
            +
            puts 'Proxying port 1234 => port 1235'
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            client1 = client2 = nil
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            f1 = spin {
         | 
| 15 | 
            +
              client1 = server1.accept
         | 
| 16 | 
            +
              loop do
         | 
| 17 | 
            +
                if client2
         | 
| 18 | 
            +
                  Thread.current.backend.splice_loop(client1, client2)
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
            }
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            f2 = spin {
         | 
| 24 | 
            +
              client2 = server2.accept
         | 
| 25 | 
            +
              loop do
         | 
| 26 | 
            +
                if client1
         | 
| 27 | 
            +
                  Thread.current.backend.splice_loop(client2, client1)
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
              end
         | 
| 30 | 
            +
            }
         | 
| 31 | 
            +
             | 
| 32 | 
            +
            Fiber.await(f1, f2)
         | 
| @@ -0,0 +1,34 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "benchmark/ips"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            def slice
         | 
| 6 | 
            +
              str = ('*' * 40) + "\n" + ('*' * 40) + "\n" + ('*' * 40) + "\n" + ('*' * 40) + "\n" + ('*' * 40)
         | 
| 7 | 
            +
              lines = []
         | 
| 8 | 
            +
              while true
         | 
| 9 | 
            +
                idx = str.index("\n")
         | 
| 10 | 
            +
                break unless idx
         | 
| 11 | 
            +
                
         | 
| 12 | 
            +
                lines << str.slice!(0, idx + 1)
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
              raise unless lines.size == 4
         | 
| 15 | 
            +
              raise unless str == ('*' * 40)
         | 
| 16 | 
            +
            end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            def split
         | 
| 19 | 
            +
              str = ('*' * 40) + "\n" + ('*' * 40) + "\n" + ('*' * 40) + "\n" + ('*' * 40) + "\n" + ('*' * 40)
         | 
| 20 | 
            +
              lines = str.split("\n")
         | 
| 21 | 
            +
              if str[-1] == "\n"
         | 
| 22 | 
            +
                str = ''
         | 
| 23 | 
            +
              else
         | 
| 24 | 
            +
                str = lines.pop
         | 
| 25 | 
            +
              end
         | 
| 26 | 
            +
              raise unless lines.size == 4
         | 
| 27 | 
            +
              raise unless str == ('*' * 40)
         | 
| 28 | 
            +
            end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
            Benchmark.ips do |x|
         | 
| 31 | 
            +
              x.report("slice") { slice }
         | 
| 32 | 
            +
              x.report("split") { split }
         | 
| 33 | 
            +
              x.compare!
         | 
| 34 | 
            +
            end
         | 
| @@ -0,0 +1,32 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'benchmark'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            LIMIT = 1_000_0
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            def do_while
         | 
| 8 | 
            +
              i = 0
         | 
| 9 | 
            +
              while true
         | 
| 10 | 
            +
                i += 1
         | 
| 11 | 
            +
                break if i == LIMIT
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
            end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            def do_loop
         | 
| 16 | 
            +
              i = 0
         | 
| 17 | 
            +
              loop do
         | 
| 18 | 
            +
                i += 1
         | 
| 19 | 
            +
                break if i == LIMIT
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
            end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            GC.disable
         | 
| 24 | 
            +
            Benchmark.bm do |x|
         | 
| 25 | 
            +
              x.report('while') do
         | 
| 26 | 
            +
                LIMIT.times { do_while }
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
              x.report('loop') do
         | 
| 29 | 
            +
                LIMIT.times { do_loop }
         | 
| 30 | 
            +
              end
         | 
| 31 | 
            +
            end
         | 
| 32 | 
            +
             | 
| @@ -26,8 +26,12 @@ def write_response(socket) | |
| 26 26 | 
             
              socket.write "HTTP/1.1 #{status_code}\r\n#{headers}\r\n#{data}"
         | 
| 27 27 | 
             
            end
         | 
| 28 28 |  | 
| 29 | 
            -
            server = TCPServer.open('0.0.0.0',  | 
| 30 | 
            -
            puts "pid #{Process.pid} Polyphony (#{Thread.current.backend.kind}) listening on port  | 
| 29 | 
            +
            server = TCPServer.open('0.0.0.0', 4411)
         | 
| 30 | 
            +
            puts "pid #{Process.pid} Polyphony (#{Thread.current.backend.kind}) listening on port 4411"
         | 
| 31 | 
            +
             | 
| 32 | 
            +
            spin_loop(interval: 10) do
         | 
| 33 | 
            +
              p Thread.current.fiber_scheduling_stats
         | 
| 34 | 
            +
            end
         | 
| 31 35 |  | 
| 32 36 | 
             
            server.accept_loop do |c|
         | 
| 33 37 | 
             
              spin { handle_client(c) }
         | 
| @@ -70,9 +70,9 @@ inline VALUE io_enc_str(VALUE str, rb_io_t *fptr) { | |
| 70 70 |  | 
| 71 71 | 
             
            inline VALUE backend_await(Backend_t *backend) {
         | 
| 72 72 | 
             
              VALUE ret;
         | 
| 73 | 
            -
              backend-> | 
| 73 | 
            +
              backend->pending_count++;
         | 
| 74 74 | 
             
              ret = Thread_switch_fiber(rb_thread_current());
         | 
| 75 | 
            -
              backend-> | 
| 75 | 
            +
              backend->pending_count--;
         | 
| 76 76 | 
             
              RB_GC_GUARD(ret);
         | 
| 77 77 | 
             
              return ret;
         | 
| 78 78 | 
             
            }
         | 
| @@ -114,7 +114,7 @@ inline double current_time() { | |
| 114 114 | 
             
              struct timespec ts;
         | 
| 115 115 | 
             
              clock_gettime(CLOCK_MONOTONIC, &ts);
         | 
| 116 116 | 
             
              long long ns = ts.tv_sec;
         | 
| 117 | 
            -
              ns = ns *  | 
| 117 | 
            +
              ns = ns * 1e9 + ts.tv_nsec;
         | 
| 118 118 | 
             
              double t = ns;
         | 
| 119 119 | 
             
              return t / 1e9;
         | 
| 120 120 | 
             
            }
         | 
| @@ -30,11 +30,14 @@ static int pidfd_open(pid_t pid, unsigned int flags) { | |
| 30 30 | 
             
            VALUE SYM_io_uring;
         | 
| 31 31 |  | 
| 32 32 | 
             
            typedef struct Backend_t {
         | 
| 33 | 
            +
              // common fields
         | 
| 34 | 
            +
              unsigned int        currently_polling;
         | 
| 35 | 
            +
              unsigned int        pending_count;
         | 
| 36 | 
            +
              unsigned int        poll_no_wait_count;
         | 
| 37 | 
            +
             | 
| 38 | 
            +
              // implementation-specific fields
         | 
| 33 39 | 
             
              struct io_uring     ring;
         | 
| 34 40 | 
             
              op_context_store_t  store;
         | 
| 35 | 
            -
              int                 waiting_for_cqe;
         | 
| 36 | 
            -
              unsigned int        ref_count;
         | 
| 37 | 
            -
              unsigned int        run_no_wait_count;
         | 
| 38 41 | 
             
              unsigned int        pending_sqes;
         | 
| 39 42 | 
             
              unsigned int        prepared_limit;
         | 
| 40 43 | 
             
              int                 event_fd;
         | 
| @@ -65,11 +68,11 @@ static VALUE Backend_initialize(VALUE self) { | |
| 65 68 | 
             
              Backend_t *backend;
         | 
| 66 69 | 
             
              GetBackend(self, backend);
         | 
| 67 70 |  | 
| 68 | 
            -
              backend-> | 
| 69 | 
            -
              backend-> | 
| 70 | 
            -
              backend-> | 
| 71 | 
            +
              backend->currently_polling = 0;
         | 
| 72 | 
            +
              backend->pending_count = 0;
         | 
| 73 | 
            +
              backend->poll_no_wait_count = 0;
         | 
| 71 74 | 
             
              backend->pending_sqes = 0;
         | 
| 72 | 
            -
              backend->prepared_limit =  | 
| 75 | 
            +
              backend->prepared_limit = 2048;
         | 
| 73 76 |  | 
| 74 77 | 
             
              context_store_initialize(&backend->store);
         | 
| 75 78 | 
             
              io_uring_queue_init(backend->prepared_limit, &backend->ring, 0);
         | 
| @@ -95,46 +98,19 @@ VALUE Backend_post_fork(VALUE self) { | |
| 95 98 | 
             
              io_uring_queue_exit(&backend->ring);
         | 
| 96 99 | 
             
              io_uring_queue_init(backend->prepared_limit, &backend->ring, 0);
         | 
| 97 100 | 
             
              context_store_free(&backend->store);
         | 
| 98 | 
            -
              backend-> | 
| 99 | 
            -
              backend-> | 
| 100 | 
            -
              backend-> | 
| 101 | 
            +
              backend->currently_polling = 0;
         | 
| 102 | 
            +
              backend->pending_count = 0;
         | 
| 103 | 
            +
              backend->poll_no_wait_count = 0;
         | 
| 101 104 | 
             
              backend->pending_sqes = 0;
         | 
| 102 105 |  | 
| 103 106 | 
             
              return self;
         | 
| 104 107 | 
             
            }
         | 
| 105 108 |  | 
| 106 | 
            -
             | 
| 107 | 
            -
              Backend_t *backend;
         | 
| 108 | 
            -
              GetBackend(self, backend);
         | 
| 109 | 
            -
             | 
| 110 | 
            -
              backend->ref_count++;
         | 
| 111 | 
            -
              return self;
         | 
| 112 | 
            -
            }
         | 
| 113 | 
            -
             | 
| 114 | 
            -
            VALUE Backend_unref(VALUE self) {
         | 
| 109 | 
            +
            unsigned int Backend_pending_count(VALUE self) {
         | 
| 115 110 | 
             
              Backend_t *backend;
         | 
| 116 111 | 
             
              GetBackend(self, backend);
         | 
| 117 112 |  | 
| 118 | 
            -
              backend-> | 
| 119 | 
            -
              return self;
         | 
| 120 | 
            -
            }
         | 
| 121 | 
            -
             | 
| 122 | 
            -
            int Backend_ref_count(VALUE self) {
         | 
| 123 | 
            -
              Backend_t *backend;
         | 
| 124 | 
            -
              GetBackend(self, backend);
         | 
| 125 | 
            -
             | 
| 126 | 
            -
              return backend->ref_count;
         | 
| 127 | 
            -
            }
         | 
| 128 | 
            -
             | 
| 129 | 
            -
            void Backend_reset_ref_count(VALUE self) {
         | 
| 130 | 
            -
              Backend_t *backend;
         | 
| 131 | 
            -
              GetBackend(self, backend);
         | 
| 132 | 
            -
             | 
| 133 | 
            -
              backend->ref_count = 0;
         | 
| 134 | 
            -
            }
         | 
| 135 | 
            -
             | 
| 136 | 
            -
            VALUE Backend_pending_count(VALUE self) {
         | 
| 137 | 
            -
              return INT2NUM(0);
         | 
| 113 | 
            +
              return backend->pending_count;
         | 
| 138 114 | 
             
            }
         | 
| 139 115 |  | 
| 140 116 | 
             
            typedef struct poll_context {
         | 
| @@ -158,7 +134,7 @@ static inline bool cq_ring_needs_flush(struct io_uring *ring) { | |
| 158 134 |  | 
| 159 135 | 
             
            void io_uring_backend_handle_completion(struct io_uring_cqe *cqe, Backend_t *backend) {
         | 
| 160 136 | 
             
              op_context_t *ctx = io_uring_cqe_get_data(cqe);
         | 
| 161 | 
            -
              if (ctx | 
| 137 | 
            +
              if (!ctx) return;
         | 
| 162 138 |  | 
| 163 139 | 
             
              ctx->result = cqe->res;
         | 
| 164 140 |  | 
| @@ -211,9 +187,9 @@ void io_uring_backend_poll(Backend_t *backend) { | |
| 211 187 | 
             
                io_uring_submit(&backend->ring);
         | 
| 212 188 | 
             
              }
         | 
| 213 189 |  | 
| 214 | 
            -
              backend-> | 
| 190 | 
            +
              backend->currently_polling = 1;
         | 
| 215 191 | 
             
              rb_thread_call_without_gvl(io_uring_backend_poll_without_gvl, (void *)&poll_ctx, RUBY_UBF_IO, 0);
         | 
| 216 | 
            -
              backend-> | 
| 192 | 
            +
              backend->currently_polling = 0;
         | 
| 217 193 | 
             
              if (poll_ctx.result < 0) return;
         | 
| 218 194 |  | 
| 219 195 | 
             
              io_uring_backend_handle_completion(poll_ctx.cqe, backend);
         | 
| @@ -226,14 +202,14 @@ VALUE Backend_poll(VALUE self, VALUE nowait, VALUE current_fiber, VALUE runqueue | |
| 226 202 | 
             
              GetBackend(self, backend);
         | 
| 227 203 |  | 
| 228 204 | 
             
              if (is_nowait) {
         | 
| 229 | 
            -
                backend-> | 
| 230 | 
            -
                if (backend-> | 
| 205 | 
            +
                backend->poll_no_wait_count++;
         | 
| 206 | 
            +
                if (backend->poll_no_wait_count < 10) return self;
         | 
| 231 207 |  | 
| 232 208 | 
             
                long runnable_count = Runqueue_len(runqueue);
         | 
| 233 | 
            -
                if (backend-> | 
| 209 | 
            +
                if (backend->poll_no_wait_count < runnable_count) return self;
         | 
| 234 210 | 
             
              }
         | 
| 235 211 |  | 
| 236 | 
            -
              backend-> | 
| 212 | 
            +
              backend->poll_no_wait_count = 0;
         | 
| 237 213 |  | 
| 238 214 | 
             
              if (is_nowait && backend->pending_sqes) {
         | 
| 239 215 | 
             
                backend->pending_sqes = 0;
         | 
| @@ -252,7 +228,7 @@ VALUE Backend_wakeup(VALUE self) { | |
| 252 228 | 
             
              Backend_t *backend;
         | 
| 253 229 | 
             
              GetBackend(self, backend);
         | 
| 254 230 |  | 
| 255 | 
            -
              if (backend-> | 
| 231 | 
            +
              if (backend->currently_polling) {
         | 
| 256 232 | 
             
                // Since we're currently blocking while waiting for a completion, we add a
         | 
| 257 233 | 
             
                // NOP which would cause the io_uring_enter syscall to return
         | 
| 258 234 | 
             
                struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
         | 
| @@ -284,12 +260,10 @@ int io_uring_backend_defer_submit_and_await( | |
| 284 260 | 
             
              VALUE switchpoint_result = Qnil;
         | 
| 285 261 |  | 
| 286 262 | 
             
              io_uring_sqe_set_data(sqe, ctx);
         | 
| 287 | 
            -
              io_uring_sqe_set_flags(sqe, IOSQE_ASYNC);
         | 
| 263 | 
            +
              // io_uring_sqe_set_flags(sqe, IOSQE_ASYNC);
         | 
| 288 264 | 
             
              io_uring_backend_defer_submit(backend);
         | 
| 289 265 |  | 
| 290 | 
            -
              backend->ref_count++;
         | 
| 291 266 | 
             
              switchpoint_result = backend_await(backend);
         | 
| 292 | 
            -
              backend->ref_count--;
         | 
| 293 267 |  | 
| 294 268 | 
             
              if (!ctx->completed) {
         | 
| 295 269 | 
             
                ctx->result = -ECANCELED;
         | 
| @@ -351,7 +325,7 @@ VALUE Backend_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof) | |
| 351 325 |  | 
| 352 326 | 
             
                if (result < 0)
         | 
| 353 327 | 
             
                  rb_syserr_fail(-result, strerror(-result));
         | 
| 354 | 
            -
                else if (result | 
| 328 | 
            +
                else if (!result)
         | 
| 355 329 | 
             
                  break; // EOF
         | 
| 356 330 | 
             
                else {
         | 
| 357 331 | 
             
                  total += result;
         | 
| @@ -373,7 +347,7 @@ VALUE Backend_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof) | |
| 373 347 | 
             
              io_set_read_length(str, total, shrinkable);
         | 
| 374 348 | 
             
              io_enc_str(str, fptr);
         | 
| 375 349 |  | 
| 376 | 
            -
              if (total | 
| 350 | 
            +
              if (!total) return Qnil;
         | 
| 377 351 |  | 
| 378 352 | 
             
              return str;
         | 
| 379 353 | 
             
            }
         | 
| @@ -410,7 +384,7 @@ VALUE Backend_read_loop(VALUE self, VALUE io) { | |
| 410 384 |  | 
| 411 385 | 
             
                if (result < 0)
         | 
| 412 386 | 
             
                  rb_syserr_fail(-result, strerror(-result));
         | 
| 413 | 
            -
                else if (result | 
| 387 | 
            +
                else if (!result)
         | 
| 414 388 | 
             
                  break; // EOF
         | 
| 415 389 | 
             
                else {
         | 
| 416 390 | 
             
                  total = result;
         | 
| @@ -581,7 +555,7 @@ VALUE Backend_recv(VALUE self, VALUE io, VALUE str, VALUE length) { | |
| 581 555 | 
             
              io_set_read_length(str, total, shrinkable);
         | 
| 582 556 | 
             
              io_enc_str(str, fptr);
         | 
| 583 557 |  | 
| 584 | 
            -
              if (total | 
| 558 | 
            +
              if (!total) return Qnil;
         | 
| 585 559 |  | 
| 586 560 | 
             
              return str;
         | 
| 587 561 | 
             
            }
         | 
| @@ -618,7 +592,7 @@ VALUE Backend_recv_loop(VALUE self, VALUE io) { | |
| 618 592 |  | 
| 619 593 | 
             
                if (result < 0)
         | 
| 620 594 | 
             
                  rb_syserr_fail(-result, strerror(-result));
         | 
| 621 | 
            -
                else if (result | 
| 595 | 
            +
                else if (!result)
         | 
| 622 596 | 
             
                  break; // EOF
         | 
| 623 597 | 
             
                else {
         | 
| 624 598 | 
             
                  total = result;
         | 
| @@ -950,10 +924,6 @@ void Init_Backend() { | |
| 950 924 | 
             
              rb_define_method(cBackend, "initialize", Backend_initialize, 0);
         | 
| 951 925 | 
             
              rb_define_method(cBackend, "finalize", Backend_finalize, 0);
         | 
| 952 926 | 
             
              rb_define_method(cBackend, "post_fork", Backend_post_fork, 0);
         | 
| 953 | 
            -
              rb_define_method(cBackend, "pending_count", Backend_pending_count, 0);
         | 
| 954 | 
            -
             | 
| 955 | 
            -
              rb_define_method(cBackend, "ref", Backend_ref, 0);
         | 
| 956 | 
            -
              rb_define_method(cBackend, "unref", Backend_unref, 0);
         | 
| 957 927 |  | 
| 958 928 | 
             
              rb_define_method(cBackend, "poll", Backend_poll, 3);
         | 
| 959 929 | 
             
              rb_define_method(cBackend, "break", Backend_wakeup, 0);
         | 
| @@ -977,15 +947,6 @@ void Init_Backend() { | |
| 977 947 | 
             
              rb_define_method(cBackend, "kind", Backend_kind, 0);
         | 
| 978 948 |  | 
| 979 949 | 
             
              SYM_io_uring = ID2SYM(rb_intern("io_uring"));
         | 
| 980 | 
            -
             | 
| 981 | 
            -
              __BACKEND__.pending_count   = Backend_pending_count;
         | 
| 982 | 
            -
              __BACKEND__.poll            = Backend_poll;
         | 
| 983 | 
            -
              __BACKEND__.ref             = Backend_ref;
         | 
| 984 | 
            -
              __BACKEND__.ref_count       = Backend_ref_count;
         | 
| 985 | 
            -
              __BACKEND__.reset_ref_count = Backend_reset_ref_count;
         | 
| 986 | 
            -
              __BACKEND__.unref           = Backend_unref;
         | 
| 987 | 
            -
              __BACKEND__.wait_event      = Backend_wait_event;
         | 
| 988 | 
            -
              __BACKEND__.wakeup          = Backend_wakeup;
         | 
| 989 950 | 
             
            }
         | 
| 990 951 |  | 
| 991 952 | 
             
            #endif // POLYPHONY_BACKEND_LIBURING
         |