polyphony 0.64 → 0.68
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +1 -1
- data/CHANGELOG.md +22 -0
- data/Gemfile.lock +1 -1
- data/TODO.md +10 -40
- data/bin/pdbg +112 -0
- data/examples/core/await.rb +9 -1
- data/ext/polyphony/backend_common.c +14 -1
- data/ext/polyphony/backend_common.h +3 -1
- data/ext/polyphony/backend_io_uring.c +85 -25
- data/ext/polyphony/backend_io_uring_context.c +42 -0
- data/ext/polyphony/backend_io_uring_context.h +6 -9
- data/ext/polyphony/backend_libev.c +85 -39
- data/ext/polyphony/fiber.c +20 -0
- data/ext/polyphony/polyphony.c +2 -0
- data/ext/polyphony/polyphony.h +5 -2
- data/ext/polyphony/queue.c +1 -1
- data/ext/polyphony/runqueue.c +7 -3
- data/ext/polyphony/runqueue.h +4 -3
- data/ext/polyphony/runqueue_ring_buffer.c +25 -14
- data/ext/polyphony/runqueue_ring_buffer.h +2 -0
- data/ext/polyphony/thread.c +2 -8
- data/lib/polyphony.rb +6 -0
- data/lib/polyphony/debugger.rb +225 -0
- data/lib/polyphony/extensions/debug.rb +1 -1
- data/lib/polyphony/extensions/fiber.rb +64 -71
- data/lib/polyphony/extensions/io.rb +4 -2
- data/lib/polyphony/extensions/openssl.rb +66 -0
- data/lib/polyphony/extensions/socket.rb +8 -2
- data/lib/polyphony/net.rb +1 -0
- data/lib/polyphony/version.rb +1 -1
- data/test/helper.rb +6 -5
- data/test/stress.rb +6 -2
- data/test/test_backend.rb +13 -4
- data/test/test_fiber.rb +35 -11
- data/test/test_global_api.rb +9 -4
- data/test/test_io.rb +2 -0
- data/test/test_socket.rb +14 -11
- data/test/test_supervise.rb +24 -24
- data/test/test_thread.rb +3 -0
- data/test/test_thread_pool.rb +1 -1
- data/test/test_throttler.rb +2 -2
- data/test/test_timer.rb +5 -3
- metadata +5 -3
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: d23a9c0f60530fdee1d367f34359e4f694bd4a42ba3c0112e61433df05c29a81
         | 
| 4 | 
            +
              data.tar.gz: bf75e3f1a633f5c94a43fd45a2de2545d87b1e751753980abb8e1d3eddd4eade
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: b810c7ddbd383039e5f34fe76dbea089807417d4415e79a952f6d6a46538cf99c55ae7a34f3eadf61d22af23ac29a0910c62ea5c97dff6e86b94083d98444edf
         | 
| 7 | 
            +
              data.tar.gz: '063459edd8f669a024e0b7b8f1daa47983db4cf9ac7f3ca81a69359de92eace4831d843e9b74645db08a50c26d83a2878a5fdb6b125adc260afd13a3135e710c'
         | 
    
        data/.github/workflows/test.yml
    CHANGED
    
    
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -1,3 +1,25 @@ | |
| 1 | 
            +
            ## 0.68 2021-08-13
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            - Fix missing default value in socket classes' `#readpartial`
         | 
| 4 | 
            +
            - Fix linking of operations in `Backend#chain` (io_uring version)
         | 
| 5 | 
            +
            - Rename `Fiber#attach` to `Fiber#attach_to`
         | 
| 6 | 
            +
            - Expose original `SSLServer#accept`
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            ## 0.67 2021-08-06
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            - Improve fiber monitoring
         | 
| 11 | 
            +
            - Add fiber parking (a parked fiber is prevented from running). This is in
         | 
| 12 | 
            +
              preparation for the upcoming work on an integrated debugger.
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            ## 0.66 2021-08-01
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            - Fix all splicing APIs on non-linux OSes (#63)
         | 
| 17 | 
            +
            - Add GC marking of buffers when cancelling read/write ops in io_uring backend 
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            ## 0.65 2021-07-29
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            - Add `#__polyphony_read_method__` method for read method detection
         | 
| 22 | 
            +
             | 
| 1 23 | 
             
            ## 0.64 2021-07-26
         | 
| 2 24 |  | 
| 3 25 | 
             
            - Add optional raise_on_eof argument to `#readpartial`
         | 
    
        data/Gemfile.lock
    CHANGED
    
    
    
        data/TODO.md
    CHANGED
    
    | @@ -1,3 +1,13 @@ | |
| 1 | 
            +
            - io_uring backend:
         | 
| 2 | 
            +
              - if `io_uring_get_sqe` returns null, call `io_uring_submit`, (snooze fiber)?
         | 
| 3 | 
            +
                and try again
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            - Tracing:
         | 
| 6 | 
            +
              - Emit events on I/O ops, e.g.:
         | 
| 7 | 
            +
                - [:op_read_submit, id, io, len]
         | 
| 8 | 
            +
                - [:op_read_complete, id, io, len, buffer]
         | 
| 9 | 
            +
              - Prevent tracing while an event is being emitted (to allow the trace proc to perform I/O)
         | 
| 10 | 
            +
             | 
| 1 11 | 
             
            - Add support for IPv6:
         | 
| 2 12 | 
             
              https://www.reddit.com/r/ruby/comments/lyen23/understanding_ipv6_and_why_its_important_to_you/
         | 
| 3 13 |  | 
| @@ -14,47 +24,8 @@ | |
| 14 24 | 
             
              - `IO#gets_loop`, `Socket#gets_loop`, `OpenSSL::Socket#gets_loop` (medium effort)
         | 
| 15 25 | 
             
              - `Fiber#receive_loop` (very little effort, should be implemented in C)
         | 
| 16 26 |  | 
| 17 | 
            -
             | 
| 18 | 
            -
            - Add `Backend#splice`, `Backend#splice_to_eof` for implementing stuff like proxying:
         | 
| 19 | 
            -
             | 
| 20 | 
            -
              ```ruby
         | 
| 21 | 
            -
              def two_way_proxy(socket1, socket2)
         | 
| 22 | 
            -
                backend = Thread.current.backend
         | 
| 23 | 
            -
                f1 = spin { backend.splice_to_eof(socket1, socket2) }
         | 
| 24 | 
            -
                f2 = spin { backend.splice_to_eof(socket2, socket1) }
         | 
| 25 | 
            -
                Fiber.await(f1, f2)
         | 
| 26 | 
            -
              end
         | 
| 27 | 
            -
              ```
         | 
| 28 | 
            -
             | 
| 29 27 | 
             
            - Add support for `close` to io_uring backend
         | 
| 30 28 |  | 
| 31 | 
            -
            - Add support for submission of multiple requests to io_uring backend:
         | 
| 32 | 
            -
             | 
| 33 | 
            -
              ```ruby
         | 
| 34 | 
            -
              Thread.current.backend.submit(
         | 
| 35 | 
            -
                [:send, sock, chunk_header(len)],
         | 
| 36 | 
            -
                [:splice, file, sock, len]
         | 
| 37 | 
            -
              )
         | 
| 38 | 
            -
              ```
         | 
| 39 | 
            -
             | 
| 40 | 
            -
              Full example (for writing chunks from a file to an HTTP response):
         | 
| 41 | 
            -
             | 
| 42 | 
            -
              ```ruby
         | 
| 43 | 
            -
              def serve_io(io)
         | 
| 44 | 
            -
                i, o = IO.pipe
         | 
| 45 | 
            -
                backend = Thread.current.backend
         | 
| 46 | 
            -
                while true
         | 
| 47 | 
            -
                  len = o.splice(io, 8192)
         | 
| 48 | 
            -
                  break if len == 0
         | 
| 49 | 
            -
                  
         | 
| 50 | 
            -
                  backend.submit(
         | 
| 51 | 
            -
                    [:write, sock, chunk_header(len)],
         | 
| 52 | 
            -
                    [:splice, i, sock, len]
         | 
| 53 | 
            -
                  )
         | 
| 54 | 
            -
                end
         | 
| 55 | 
            -
              end
         | 
| 56 | 
            -
              ```
         | 
| 57 | 
            -
             | 
| 58 29 | 
             
            - Graceful shutdown again:
         | 
| 59 30 | 
             
              - What happens to children when doing a graceful shutdown?
         | 
| 60 31 | 
             
              - What are the implications of passing graceful shutdown flag to children?
         | 
| @@ -92,7 +63,6 @@ | |
| 92 63 |  | 
| 93 64 | 
             
            -----------------------------------------------------
         | 
| 94 65 |  | 
| 95 | 
            -
            - Add `Backend#splice(in, out, nbytes)` API
         | 
| 96 66 | 
             
            - Adapter for io/console (what does `IO#raw` do?)
         | 
| 97 67 | 
             
            - Adapter for Pry and IRB (Which fixes #5 and #6)
         | 
| 98 68 | 
             
            - allow backend selection at runtime
         | 
    
        data/bin/pdbg
    ADDED
    
    | @@ -0,0 +1,112 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
            # frozen_string_literal: true
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            require 'bundler/setup'
         | 
| 5 | 
            +
            require 'polyphony'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            UNIX_SOCKET_PATH = '/tmp/pdbg.sock'
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            cmd = ARGV.join(' ')
         | 
| 10 | 
            +
            injected_lib_path = File.expand_path('../lib/polyphony/debugger/server_inject.rb', __dir__)
         | 
| 11 | 
            +
            pid = fork { exec("env POLYPHONY_DEBUG_SOCKET_PATH=#{UNIX_SOCKET_PATH} ruby #{cmd}") }
         | 
| 12 | 
            +
            puts "Started debugged process (#{pid})"
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            socket = nil
         | 
| 15 | 
            +
            while !socket
         | 
| 16 | 
            +
              socket = UNIXSocket.new(UNIX_SOCKET_PATH) rescue nil
         | 
| 17 | 
            +
            end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            def parse_command(cmd)
         | 
| 20 | 
            +
              case cmd
         | 
| 21 | 
            +
              when /^(step|s)$/
         | 
| 22 | 
            +
                { cmd: :step }
         | 
| 23 | 
            +
              when /^(state|st)$/
         | 
| 24 | 
            +
                { cmd: :state }
         | 
| 25 | 
            +
              when /^(help|h)$/
         | 
| 26 | 
            +
                { cmd: :help }
         | 
| 27 | 
            +
              when /^(list|l)$/
         | 
| 28 | 
            +
                { cmd: :list }
         | 
| 29 | 
            +
              else
         | 
| 30 | 
            +
                nil
         | 
| 31 | 
            +
              end
         | 
| 32 | 
            +
            end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
            def display_info(info)
         | 
| 35 | 
            +
              info = eval(info)
         | 
| 36 | 
            +
              case (info && info[:kind])
         | 
| 37 | 
            +
              when :listing
         | 
| 38 | 
            +
                print_listing(info)
         | 
| 39 | 
            +
              when :state
         | 
| 40 | 
            +
                print_state(info)
         | 
| 41 | 
            +
              else
         | 
| 42 | 
            +
                p info
         | 
| 43 | 
            +
              end
         | 
| 44 | 
            +
            rescue SyntaxError
         | 
| 45 | 
            +
              puts "Failed to eval:"
         | 
| 46 | 
            +
              p info
         | 
| 47 | 
            +
            end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
            FILE_LINES_CACHE = {}
         | 
| 50 | 
            +
             | 
| 51 | 
            +
            def self.get_snippet(path, lineno)
         | 
| 52 | 
            +
              lines = FILE_LINES_CACHE[path] ||= IO.read(path).lines
         | 
| 53 | 
            +
              start_idx = lineno - 5
         | 
| 54 | 
            +
              stop_idx = lineno + 3
         | 
| 55 | 
            +
              stop_idx = lines.size - 1 if stop_idx >= lines.size
         | 
| 56 | 
            +
              start_idx = 0 if start_idx < 0
         | 
| 57 | 
            +
              (start_idx..stop_idx).map { |idx| [idx + 1, lines[idx]]}
         | 
| 58 | 
            +
            end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
            def print_snippet(info, snippet, cur_line)
         | 
| 61 | 
            +
              places = FILE_LINES_CACHE[info[:path]].size.to_s.size
         | 
| 62 | 
            +
              snippet.each do |(lineno, line)|
         | 
| 63 | 
            +
                is_cur = lineno == cur_line
         | 
| 64 | 
            +
                formatted = format("%s% #{places}d %s", is_cur ? '=> ' : '   ', lineno, line)
         | 
| 65 | 
            +
                puts formatted
         | 
| 66 | 
            +
              end
         | 
| 67 | 
            +
            end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
            def print_listing(info)
         | 
| 70 | 
            +
              snippet = get_snippet(info[:path], info[:lineno])
         | 
| 71 | 
            +
              puts "Fiber: #{info[:fiber]} Location: #{info[:path]}:#{info[:lineno]}"
         | 
| 72 | 
            +
              puts
         | 
| 73 | 
            +
              print_snippet(info, snippet, info[:lineno])
         | 
| 74 | 
            +
              puts
         | 
| 75 | 
            +
            end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
            def print_help
         | 
| 78 | 
            +
              puts
         | 
| 79 | 
            +
              puts "Here's some help..."
         | 
| 80 | 
            +
              puts
         | 
| 81 | 
            +
            end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
            def print_state(info)
         | 
| 84 | 
            +
              p info
         | 
| 85 | 
            +
            end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
            def get_user_cmd
         | 
| 88 | 
            +
              while true
         | 
| 89 | 
            +
                STDOUT << "(pdbg) "
         | 
| 90 | 
            +
                cmd = parse_command(STDIN.gets)
         | 
| 91 | 
            +
                next unless cmd
         | 
| 92 | 
            +
                
         | 
| 93 | 
            +
                if cmd[:cmd] == :help
         | 
| 94 | 
            +
                  print_help
         | 
| 95 | 
            +
                else
         | 
| 96 | 
            +
                  return cmd if cmd
         | 
| 97 | 
            +
                end
         | 
| 98 | 
            +
              end
         | 
| 99 | 
            +
            end
         | 
| 100 | 
            +
             | 
| 101 | 
            +
            socket.puts 'pdbg'
         | 
| 102 | 
            +
            response = socket.gets
         | 
| 103 | 
            +
            if response.chomp == 'pdbg'
         | 
| 104 | 
            +
              puts 'Connected to process'
         | 
| 105 | 
            +
            end
         | 
| 106 | 
            +
            loop do
         | 
| 107 | 
            +
              info = socket.gets.chomp
         | 
| 108 | 
            +
              display_info(info)
         | 
| 109 | 
            +
             | 
| 110 | 
            +
              cmd = get_user_cmd
         | 
| 111 | 
            +
              socket.puts cmd.inspect
         | 
| 112 | 
            +
            end
         | 
    
        data/examples/core/await.rb
    CHANGED
    
    | @@ -2,7 +2,11 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            require 'bundler/setup'
         | 
| 4 4 | 
             
            require 'polyphony'
         | 
| 5 | 
            +
            require 'polyphony/extensions/debug'
         | 
| 5 6 |  | 
| 7 | 
            +
            Exception.__disable_sanitized_backtrace__ = true
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            puts '----- start await example ------'
         | 
| 6 10 | 
             
            sleeper = spin do
         | 
| 7 11 | 
             
              puts 'going to sleep'
         | 
| 8 12 | 
             
              sleep 1
         | 
| @@ -17,4 +21,8 @@ waiter = spin do | |
| 17 21 | 
             
              puts 'done waiting'
         | 
| 18 22 | 
             
            end
         | 
| 19 23 |  | 
| 20 | 
            -
             | 
| 24 | 
            +
            trace :before_await
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            sleep 2
         | 
| 27 | 
            +
            waiter.await
         | 
| 28 | 
            +
            trace :after_await
         | 
| @@ -7,6 +7,7 @@ | |
| 7 7 |  | 
| 8 8 | 
             
            inline void backend_base_initialize(struct Backend_base *base) {
         | 
| 9 9 | 
             
              runqueue_initialize(&base->runqueue);
         | 
| 10 | 
            +
              runqueue_initialize(&base->parked_runqueue);
         | 
| 10 11 | 
             
              base->currently_polling = 0;
         | 
| 11 12 | 
             
              base->op_count = 0;
         | 
| 12 13 | 
             
              base->switch_count = 0;
         | 
| @@ -20,12 +21,14 @@ inline void backend_base_initialize(struct Backend_base *base) { | |
| 20 21 |  | 
| 21 22 | 
             
            inline void backend_base_finalize(struct Backend_base *base) {
         | 
| 22 23 | 
             
              runqueue_finalize(&base->runqueue);
         | 
| 24 | 
            +
              runqueue_finalize(&base->parked_runqueue);
         | 
| 23 25 | 
             
            }
         | 
| 24 26 |  | 
| 25 27 | 
             
            inline void backend_base_mark(struct Backend_base *base) {
         | 
| 26 28 | 
             
              if (base->idle_proc != Qnil) rb_gc_mark(base->idle_proc);
         | 
| 27 29 | 
             
              if (base->trace_proc != Qnil) rb_gc_mark(base->trace_proc);
         | 
| 28 30 | 
             
              runqueue_mark(&base->runqueue);
         | 
| 31 | 
            +
              runqueue_mark(&base->parked_runqueue);
         | 
| 29 32 | 
             
            }
         | 
| 30 33 |  | 
| 31 34 | 
             
            const unsigned int ANTI_STARVE_SWITCH_COUNT_THRESHOLD = 64;
         | 
| @@ -91,7 +94,10 @@ void backend_base_schedule_fiber(VALUE thread, VALUE backend, struct Backend_bas | |
| 91 94 |  | 
| 92 95 | 
             
              COND_TRACE(base, 4, SYM_fiber_schedule, fiber, value, prioritize ? Qtrue : Qfalse);
         | 
| 93 96 |  | 
| 94 | 
            -
               | 
| 97 | 
            +
              runqueue_t *runqueue = rb_ivar_get(fiber, ID_ivar_parked) == Qtrue ? 
         | 
| 98 | 
            +
                &base->parked_runqueue : &base->runqueue;
         | 
| 99 | 
            +
             | 
| 100 | 
            +
              (prioritize ? runqueue_unshift : runqueue_push)(runqueue, fiber, value, already_runnable);
         | 
| 95 101 | 
             
              if (!already_runnable) {
         | 
| 96 102 | 
             
                rb_ivar_set(fiber, ID_ivar_runnable, Qtrue);
         | 
| 97 103 | 
             
                if (rb_thread_current() != thread) {
         | 
| @@ -105,6 +111,13 @@ void backend_base_schedule_fiber(VALUE thread, VALUE backend, struct Backend_bas | |
| 105 111 | 
             
              }
         | 
| 106 112 | 
             
            }
         | 
| 107 113 |  | 
| 114 | 
            +
            inline void backend_base_park_fiber(struct Backend_base *base, VALUE fiber) {
         | 
| 115 | 
            +
              runqueue_migrate(&base->runqueue, &base->parked_runqueue, fiber);
         | 
| 116 | 
            +
            }
         | 
| 117 | 
            +
             | 
| 118 | 
            +
            inline void backend_base_unpark_fiber(struct Backend_base *base, VALUE fiber) {
         | 
| 119 | 
            +
              runqueue_migrate(&base->parked_runqueue, &base->runqueue, fiber);
         | 
| 120 | 
            +
            }
         | 
| 108 121 |  | 
| 109 122 | 
             
            inline void backend_trace(struct Backend_base *base, int argc, VALUE *argv) {
         | 
| 110 123 | 
             
              if (base->trace_proc == Qnil) return;
         | 
| @@ -17,6 +17,7 @@ struct backend_stats { | |
| 17 17 |  | 
| 18 18 | 
             
            struct Backend_base {
         | 
| 19 19 | 
             
              runqueue_t runqueue;
         | 
| 20 | 
            +
              runqueue_t parked_runqueue;
         | 
| 20 21 | 
             
              unsigned int currently_polling;
         | 
| 21 22 | 
             
              unsigned int op_count;
         | 
| 22 23 | 
             
              unsigned int switch_count;
         | 
| @@ -33,6 +34,8 @@ void backend_base_finalize(struct Backend_base *base); | |
| 33 34 | 
             
            void backend_base_mark(struct Backend_base *base);
         | 
| 34 35 | 
             
            VALUE backend_base_switch_fiber(VALUE backend, struct Backend_base *base);
         | 
| 35 36 | 
             
            void backend_base_schedule_fiber(VALUE thread, VALUE backend, struct Backend_base *base, VALUE fiber, VALUE value, int prioritize);
         | 
| 37 | 
            +
            void backend_base_park_fiber(struct Backend_base *base, VALUE fiber);
         | 
| 38 | 
            +
            void backend_base_unpark_fiber(struct Backend_base *base, VALUE fiber);
         | 
| 36 39 | 
             
            void backend_trace(struct Backend_base *base, int argc, VALUE *argv);
         | 
| 37 40 | 
             
            struct backend_stats backend_base_stats(struct Backend_base *base);
         | 
| 38 41 |  | 
| @@ -104,7 +107,6 @@ VALUE Backend_sendv(VALUE self, VALUE io, VALUE ary, VALUE flags); | |
| 104 107 | 
             
            VALUE Backend_stats(VALUE self);
         | 
| 105 108 | 
             
            void backend_run_idle_tasks(struct Backend_base *base);
         | 
| 106 109 | 
             
            void io_verify_blocking_mode(rb_io_t *fptr, VALUE io, VALUE blocking);
         | 
| 107 | 
            -
             | 
| 108 110 | 
             
            void backend_setup_stats_symbols();
         | 
| 109 111 |  | 
| 110 112 | 
             
            #endif /* BACKEND_COMMON_H */
         | 
| @@ -25,6 +25,8 @@ VALUE SYM_send; | |
| 25 25 | 
             
            VALUE SYM_splice;
         | 
| 26 26 | 
             
            VALUE SYM_write;
         | 
| 27 27 |  | 
| 28 | 
            +
            VALUE eArgumentError;
         | 
| 29 | 
            +
             | 
| 28 30 | 
             
            #ifdef POLYPHONY_UNSET_NONBLOCK
         | 
| 29 31 | 
             
            #define io_unset_nonblock(fptr, io) io_verify_blocking_mode(fptr, io, Qtrue)
         | 
| 30 32 | 
             
            #else
         | 
| @@ -45,6 +47,7 @@ typedef struct Backend_t { | |
| 45 47 | 
             
            static void Backend_mark(void *ptr) {
         | 
| 46 48 | 
             
              Backend_t *backend = ptr;
         | 
| 47 49 | 
             
              backend_base_mark(&backend->base);
         | 
| 50 | 
            +
              context_store_mark_taken_buffers(&backend->store);
         | 
| 48 51 | 
             
            }
         | 
| 49 52 |  | 
| 50 53 | 
             
            static void Backend_free(void *ptr) {
         | 
| @@ -226,7 +229,7 @@ inline void Backend_unschedule_fiber(VALUE self, VALUE fiber) { | |
| 226 229 | 
             
              Backend_t *backend;
         | 
| 227 230 | 
             
              GetBackend(self, backend);
         | 
| 228 231 |  | 
| 229 | 
            -
              runqueue_delete(&backend->base.runqueue, fiber); | 
| 232 | 
            +
              runqueue_delete(&backend->base.runqueue, fiber);
         | 
| 230 233 | 
             
            }
         | 
| 231 234 |  | 
| 232 235 | 
             
            inline VALUE Backend_switch_fiber(VALUE self) {
         | 
| @@ -349,8 +352,11 @@ VALUE Backend_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof, | |
| 349 352 |  | 
| 350 353 | 
             
                int result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
         | 
| 351 354 | 
             
                int completed = context_store_release(&backend->store, ctx);
         | 
| 352 | 
            -
                 | 
| 353 | 
            -
             | 
| 355 | 
            +
                if (!completed) {
         | 
| 356 | 
            +
                  context_attach_buffers(ctx, 1, &str);
         | 
| 357 | 
            +
                  RAISE_IF_EXCEPTION(resume_value);
         | 
| 358 | 
            +
                  return resume_value;
         | 
| 359 | 
            +
                }
         | 
| 354 360 | 
             
                RB_GC_GUARD(resume_value);
         | 
| 355 361 |  | 
| 356 362 | 
             
                if (result < 0)
         | 
| @@ -410,8 +416,11 @@ VALUE Backend_read_loop(VALUE self, VALUE io, VALUE maxlen) { | |
| 410 416 |  | 
| 411 417 | 
             
                ssize_t result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
         | 
| 412 418 | 
             
                int completed = context_store_release(&backend->store, ctx);
         | 
| 413 | 
            -
                 | 
| 414 | 
            -
             | 
| 419 | 
            +
                if (!completed) {
         | 
| 420 | 
            +
                  context_attach_buffers(ctx, 1, &str);
         | 
| 421 | 
            +
                  RAISE_IF_EXCEPTION(resume_value);
         | 
| 422 | 
            +
                  return resume_value;
         | 
| 423 | 
            +
                }
         | 
| 415 424 | 
             
                RB_GC_GUARD(resume_value);
         | 
| 416 425 |  | 
| 417 426 | 
             
                if (result < 0)
         | 
| @@ -457,8 +466,11 @@ VALUE Backend_feed_loop(VALUE self, VALUE io, VALUE receiver, VALUE method) { | |
| 457 466 |  | 
| 458 467 | 
             
                ssize_t result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
         | 
| 459 468 | 
             
                int completed = context_store_release(&backend->store, ctx);
         | 
| 460 | 
            -
                 | 
| 461 | 
            -
             | 
| 469 | 
            +
                if (!completed) {
         | 
| 470 | 
            +
                  context_attach_buffers(ctx, 1, &str);
         | 
| 471 | 
            +
                  RAISE_IF_EXCEPTION(resume_value);
         | 
| 472 | 
            +
                  return resume_value;
         | 
| 473 | 
            +
                }
         | 
| 462 474 | 
             
                RB_GC_GUARD(resume_value);
         | 
| 463 475 |  | 
| 464 476 | 
             
                if (result < 0)
         | 
| @@ -500,8 +512,11 @@ VALUE Backend_write(VALUE self, VALUE io, VALUE str) { | |
| 500 512 |  | 
| 501 513 | 
             
                int result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
         | 
| 502 514 | 
             
                int completed = context_store_release(&backend->store, ctx);
         | 
| 503 | 
            -
                 | 
| 504 | 
            -
             | 
| 515 | 
            +
                if (!completed) {
         | 
| 516 | 
            +
                  context_attach_buffers(ctx, 1, &str);
         | 
| 517 | 
            +
                  RAISE_IF_EXCEPTION(resume_value);
         | 
| 518 | 
            +
                  return resume_value;
         | 
| 519 | 
            +
                }
         | 
| 505 520 | 
             
                RB_GC_GUARD(resume_value);
         | 
| 506 521 |  | 
| 507 522 | 
             
                if (result < 0)
         | 
| @@ -549,12 +564,10 @@ VALUE Backend_writev(VALUE self, VALUE io, int argc, VALUE *argv) { | |
| 549 564 |  | 
| 550 565 | 
             
                int result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
         | 
| 551 566 | 
             
                int completed = context_store_release(&backend->store, ctx);
         | 
| 552 | 
            -
                if (TEST_EXCEPTION(resume_value)) {
         | 
| 553 | 
            -
                  free(iov);
         | 
| 554 | 
            -
                  RAISE_EXCEPTION(resume_value);
         | 
| 555 | 
            -
                }
         | 
| 556 567 | 
             
                if (!completed) {
         | 
| 557 568 | 
             
                  free(iov);
         | 
| 569 | 
            +
                  context_attach_buffers(ctx, argc, argv);
         | 
| 570 | 
            +
                  RAISE_IF_EXCEPTION(resume_value);
         | 
| 558 571 | 
             
                  return resume_value;
         | 
| 559 572 | 
             
                }
         | 
| 560 573 | 
             
                RB_GC_GUARD(resume_value);
         | 
| @@ -588,8 +601,7 @@ VALUE Backend_writev(VALUE self, VALUE io, int argc, VALUE *argv) { | |
| 588 601 |  | 
| 589 602 | 
             
            VALUE Backend_write_m(int argc, VALUE *argv, VALUE self) {
         | 
| 590 603 | 
             
              if (argc < 2)
         | 
| 591 | 
            -
                 | 
| 592 | 
            -
                rb_raise(rb_eRuntimeError, "(wrong number of arguments (expected 2 or more))");
         | 
| 604 | 
            +
                rb_raise(eArgumentError, "(wrong number of arguments (expected 2 or more))");
         | 
| 593 605 |  | 
| 594 606 | 
             
              return (argc == 2) ?
         | 
| 595 607 | 
             
                Backend_write(self, argv[0], argv[1]) :
         | 
| @@ -628,8 +640,11 @@ VALUE Backend_recv(VALUE self, VALUE io, VALUE str, VALUE length, VALUE pos) { | |
| 628 640 |  | 
| 629 641 | 
             
                int result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
         | 
| 630 642 | 
             
                int completed = context_store_release(&backend->store, ctx);
         | 
| 631 | 
            -
                 | 
| 632 | 
            -
             | 
| 643 | 
            +
                if (!completed) {
         | 
| 644 | 
            +
                  context_attach_buffers(ctx, 1, &str);
         | 
| 645 | 
            +
                  RAISE_IF_EXCEPTION(resume_value);
         | 
| 646 | 
            +
                  return resume_value;
         | 
| 647 | 
            +
                }
         | 
| 633 648 | 
             
                RB_GC_GUARD(resume_value);
         | 
| 634 649 |  | 
| 635 650 | 
             
                if (result < 0)
         | 
| @@ -675,8 +690,11 @@ VALUE Backend_recv_loop(VALUE self, VALUE io, VALUE maxlen) { | |
| 675 690 |  | 
| 676 691 | 
             
                int result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
         | 
| 677 692 | 
             
                int completed = context_store_release(&backend->store, ctx);
         | 
| 678 | 
            -
                 | 
| 679 | 
            -
             | 
| 693 | 
            +
                if (!completed) {
         | 
| 694 | 
            +
                  context_attach_buffers(ctx, 1, &str);
         | 
| 695 | 
            +
                  RAISE_IF_EXCEPTION(resume_value);
         | 
| 696 | 
            +
                  return resume_value;
         | 
| 697 | 
            +
                }
         | 
| 680 698 | 
             
                RB_GC_GUARD(resume_value);
         | 
| 681 699 |  | 
| 682 700 | 
             
                if (result < 0)
         | 
| @@ -721,8 +739,11 @@ VALUE Backend_recv_feed_loop(VALUE self, VALUE io, VALUE receiver, VALUE method) | |
| 721 739 |  | 
| 722 740 | 
             
                int result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
         | 
| 723 741 | 
             
                int completed = context_store_release(&backend->store, ctx);
         | 
| 724 | 
            -
                 | 
| 725 | 
            -
             | 
| 742 | 
            +
                if (!completed) {
         | 
| 743 | 
            +
                  context_attach_buffers(ctx, 1, &str);
         | 
| 744 | 
            +
                  RAISE_IF_EXCEPTION(resume_value);
         | 
| 745 | 
            +
                  return resume_value;
         | 
| 746 | 
            +
                }
         | 
| 726 747 | 
             
                RB_GC_GUARD(resume_value);
         | 
| 727 748 |  | 
| 728 749 | 
             
                if (result < 0)
         | 
| @@ -764,8 +785,11 @@ VALUE Backend_send(VALUE self, VALUE io, VALUE str, VALUE flags) { | |
| 764 785 |  | 
| 765 786 | 
             
                int result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
         | 
| 766 787 | 
             
                int completed = context_store_release(&backend->store, ctx);
         | 
| 767 | 
            -
                 | 
| 768 | 
            -
             | 
| 788 | 
            +
                if (!completed) {
         | 
| 789 | 
            +
                  context_attach_buffers(ctx, 1, &str);
         | 
| 790 | 
            +
                  RAISE_IF_EXCEPTION(resume_value);
         | 
| 791 | 
            +
                  return resume_value;
         | 
| 792 | 
            +
                }
         | 
| 769 793 | 
             
                RB_GC_GUARD(resume_value);
         | 
| 770 794 |  | 
| 771 795 | 
             
                if (result < 0)
         | 
| @@ -1165,6 +1189,24 @@ struct io_uring_sqe *Backend_chain_prepare_splice(Backend_t *backend, VALUE src, | |
| 1165 1189 | 
             
              return sqe;
         | 
| 1166 1190 | 
             
            }
         | 
| 1167 1191 |  | 
| 1192 | 
            +
            void Backend_chain_ctx_attach_buffers(op_context_t *ctx, int argc, VALUE *argv) {
         | 
| 1193 | 
            +
              int count = 0;
         | 
| 1194 | 
            +
              if (argc > 1) ctx->buffers = malloc(sizeof(VALUE) * (argc - 1));
         | 
| 1195 | 
            +
             | 
| 1196 | 
            +
              for (int i = 0; i < argc; i++) {
         | 
| 1197 | 
            +
                VALUE op = argv[i];
         | 
| 1198 | 
            +
                VALUE op_type = RARRAY_AREF(op, 0);
         | 
| 1199 | 
            +
             | 
| 1200 | 
            +
                if (op_type == SYM_write || op_type == SYM_send) {
         | 
| 1201 | 
            +
                  if (!count) ctx->buffer0 = RARRAY_AREF(op, 2);
         | 
| 1202 | 
            +
                  else        ctx->buffers[count - 1] = RARRAY_AREF(op, 2);
         | 
| 1203 | 
            +
                  count++;
         | 
| 1204 | 
            +
                }
         | 
| 1205 | 
            +
              }
         | 
| 1206 | 
            +
              ctx->buffer_count = count;
         | 
| 1207 | 
            +
            }
         | 
| 1208 | 
            +
             | 
| 1209 | 
            +
             | 
| 1168 1210 | 
             
            VALUE Backend_chain(int argc,VALUE *argv, VALUE self) {
         | 
| 1169 1211 | 
             
              VALUE resume_value = Qnil;
         | 
| 1170 1212 | 
             
              unsigned int sqe_count = 0;
         | 
| @@ -1206,7 +1248,7 @@ VALUE Backend_chain(int argc,VALUE *argv, VALUE self) { | |
| 1206 1248 | 
             
                }
         | 
| 1207 1249 |  | 
| 1208 1250 | 
             
                io_uring_sqe_set_data(last_sqe, ctx);
         | 
| 1209 | 
            -
                unsigned int flags = (i == argc - 1) ? IOSQE_ASYNC : IOSQE_ASYNC  | 
| 1251 | 
            +
                unsigned int flags = (i == (argc - 1)) ? IOSQE_ASYNC : IOSQE_ASYNC | IOSQE_IO_LINK;
         | 
| 1210 1252 | 
             
                io_uring_sqe_set_flags(last_sqe, flags);
         | 
| 1211 1253 | 
             
                sqe_count++;
         | 
| 1212 1254 | 
             
              }
         | 
| @@ -1218,6 +1260,8 @@ VALUE Backend_chain(int argc,VALUE *argv, VALUE self) { | |
| 1218 1260 | 
             
              int result = ctx->result;
         | 
| 1219 1261 | 
             
              int completed = context_store_release(&backend->store, ctx);
         | 
| 1220 1262 | 
             
              if (!completed) {
         | 
| 1263 | 
            +
                Backend_chain_ctx_attach_buffers(ctx, argc, argv);
         | 
| 1264 | 
            +
                
         | 
| 1221 1265 | 
             
                // op was not completed (an exception was raised), so we need to cancel it
         | 
| 1222 1266 | 
             
                ctx->result = -ECANCELED;
         | 
| 1223 1267 | 
             
                struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
         | 
| @@ -1409,6 +1453,7 @@ syscallerror: | |
| 1409 1453 | 
             
              if (pipefd[1] != -1) close(pipefd[1]);
         | 
| 1410 1454 | 
             
              rb_syserr_fail(err, strerror(err));
         | 
| 1411 1455 | 
             
            error:
         | 
| 1456 | 
            +
              context_attach_buffers_v(ctx, 4, prefix, postfix, chunk_prefix, chunk_postfix);
         | 
| 1412 1457 | 
             
              if (pipefd[0] != -1) close(pipefd[0]);
         | 
| 1413 1458 | 
             
              if (pipefd[1] != -1) close(pipefd[1]);
         | 
| 1414 1459 | 
             
              return RAISE_EXCEPTION(switchpoint_result);
         | 
| @@ -1429,6 +1474,20 @@ VALUE Backend_trace_proc_set(VALUE self, VALUE block) { | |
| 1429 1474 | 
             
              return self;
         | 
| 1430 1475 | 
             
            }
         | 
| 1431 1476 |  | 
| 1477 | 
            +
            void Backend_park_fiber(VALUE self, VALUE fiber) {
         | 
| 1478 | 
            +
              Backend_t *backend;
         | 
| 1479 | 
            +
              GetBackend(self, backend);
         | 
| 1480 | 
            +
             | 
| 1481 | 
            +
              backend_base_park_fiber(&backend->base, fiber);
         | 
| 1482 | 
            +
            }
         | 
| 1483 | 
            +
             | 
| 1484 | 
            +
            void Backend_unpark_fiber(VALUE self, VALUE fiber) {
         | 
| 1485 | 
            +
              Backend_t *backend;
         | 
| 1486 | 
            +
              GetBackend(self, backend);
         | 
| 1487 | 
            +
             | 
| 1488 | 
            +
              backend_base_unpark_fiber(&backend->base, fiber);
         | 
| 1489 | 
            +
            }
         | 
| 1490 | 
            +
             | 
| 1432 1491 | 
             
            void Init_Backend() {
         | 
| 1433 1492 | 
             
              VALUE cBackend = rb_define_class_under(mPolyphony, "Backend", rb_cObject);
         | 
| 1434 1493 | 
             
              rb_define_alloc_func(cBackend, Backend_allocate);
         | 
| @@ -1469,7 +1528,6 @@ void Init_Backend() { | |
| 1469 1528 | 
             
              rb_define_method(cBackend, "waitpid", Backend_waitpid, 1);
         | 
| 1470 1529 | 
             
              rb_define_method(cBackend, "write", Backend_write_m, -1);
         | 
| 1471 1530 |  | 
| 1472 | 
            -
             | 
| 1473 1531 | 
             
              #ifdef POLYPHONY_UNSET_NONBLOCK
         | 
| 1474 1532 | 
             
              ID_ivar_is_nonblocking = rb_intern("@is_nonblocking");
         | 
| 1475 1533 | 
             
              #endif
         | 
| @@ -1480,6 +1538,8 @@ void Init_Backend() { | |
| 1480 1538 | 
             
              SYM_write = ID2SYM(rb_intern("write"));
         | 
| 1481 1539 |  | 
| 1482 1540 | 
             
              backend_setup_stats_symbols();
         | 
| 1541 | 
            +
             | 
| 1542 | 
            +
              eArgumentError = rb_const_get(rb_cObject, rb_intern("ArgumentError"));
         | 
| 1483 1543 | 
             
            }
         | 
| 1484 1544 |  | 
| 1485 1545 | 
             
            #endif // POLYPHONY_BACKEND_LIBURING
         |