[#121215] [Ruby master Bug#21166] Fiber Scheduler is unable to be interrupted by `IO#close`. — "ioquatix (Samuel Williams) via ruby-core" <ruby-core@...>

Issue #21166 has been reported by ioquatix (Samuel Williams).

13 messages 2025/03/02

[#121222] [Ruby master Bug#21167] Visual Studio 2022 17.13.x couldn't build ruby.exe — "hsbt (Hiroshi SHIBATA) via ruby-core" <ruby-core@...>

Issue #21167 has been reported by hsbt (Hiroshi SHIBATA).

8 messages 2025/03/03

[#121234] [Ruby master Bug#21168] Prism doesn't require argument parentheses (in some cases) when a block is present but parse.y does — "Earlopain (Earlopain _) via ruby-core" <ruby-core@...>

Issue #21168 has been reported by Earlopain (Earlopain _).

8 messages 2025/03/04

[#121389] [Ruby Bug#21187] Strings concatenated with `\` getting frozen with literal hashes (PRISM only) — LocoDelAssembly via ruby-core <ruby-core@...>

Issue #21187 has been reported by LocoDelAssembly (Hern=E1n Pereira).

12 messages 2025/03/17

[#121413] [Ruby Bug#21193] Inherited callback returns `nil` for `Object.const_source_location` — "eileencodes (Eileen Uchitelle) via ruby-core" <ruby-core@...>

Issue #21193 has been reported by eileencodes (Eileen Uchitelle).

15 messages 2025/03/20

[#121451] [Ruby Bug#21201] Performance regression when defining methods inside `refine` blocks — "alpaca-tc (Hiroyuki Ishii) via ruby-core" <ruby-core@...>

Issue #21201 has been reported by alpaca-tc (Hiroyuki Ishii).

8 messages 2025/03/27

[ruby-core:121220] [Ruby master Bug#21166] Fiber Scheduler is unable to be interrupted by `IO#close`.

From: "ioquatix (Samuel Williams) via ruby-core" <ruby-core@...>
Date: 2025-03-02 23:54:11 UTC
List: ruby-core #121220
Issue #21166 has been updated by ioquatix (Samuel Williams).


> when you can accomplish the same thing by allowing the io operation to be interrupted by a close in every case like the regular thread scheduler does

Because the point at which IO operations become interruptible is not the entire operation, but usually the point at where the fiber yields back to the event loop, e.g. <https://github.com/socketry/io-event/pull/130/files#diff-3e7a68b220a9360ead1d2e7a5ec23e7d36de591eab138721efdc61b565fc5194R552> - adding interrupts around the entire operation is unlikely to be safe nor desirable.

Maybe you can propose in more detail how your suggestion would work. I suppose you are suggesting to wrap the scheduler code in `rb_fiber_scheduler_io_wait` and so on?

----------------------------------------
Bug #21166: Fiber Scheduler is unable to be interrupted by `IO#close`.
https://bugs.ruby-lang.org/issues/21166#change-112162

* Author: ioquatix (Samuel Williams)
* Status: Open
* Backport: 3.1: UNKNOWN, 3.2: UNKNOWN, 3.3: UNKNOWN, 3.4: UNKNOWN
----------------------------------------
## Background

Ruby's `IO#close` can cause `IO#read`, `IO#write`, `IO#wait`, `IO#wait_readable` and `IO#wait_writable` to be interrupted with an IOError (stream closed in another thread). For reference, `IO#select` cannot be interrupted in this way.

```ruby
r, w = IO.pipe

thread = Thread.new do
  r.read(1)
end

Thread.pass until thread.status == "sleep"

r.close

thread.join
# ./test.rb:6:in 'IO#read': stream closed in another thread (IOError)
```

## Problem

The fiber scheduler provides hooks for `io_read`, `io_write` and `io_wait` which are used by `IO#read`, `IO#write`, `IO#wait`, `IO#wait_readable` and `IO#wait_writable`, but those hooks are not interrupted when `IO#close` is invoked. That is because `rb_notify_fd_close` is not scheduler aware, and the fiber scheduler is unable to register itself into the "waiting file descriptor" list.

```ruby
#!/usr/bin/env ruby

require 'async'

r, w = IO.pipe

thread = Thread.new do
  Async do
    r.wait_readable
  end
end

Thread.pass until thread.status == "sleep"

r.close

thread.join
```

In this test program, `rb_notify_fd_close` will incorrectly terminate the entire fiber scheduler thread:

```
#<Thread:0x00007faa5b161bf8 /home/samuel/Developer/socketry/io-event/test.rb:7 run> terminated with exception (report_on_exception is true):
/home/samuel/Developer/socketry/io-event/lib/io/event/selector/select.rb:470:in 'IO.select': closed stream (IOError)
  from /home/samuel/Developer/socketry/io-event/lib/io/event/selector/select.rb:470:in 'block in IO::Event::Selector::Select#select'
  from /home/samuel/Developer/socketry/io-event/lib/io/event/selector/select.rb:468:in 'Thread.handle_interrupt'
  from /home/samuel/Developer/socketry/io-event/lib/io/event/selector/select.rb:468:in 'IO::Event::Selector::Select#select'
  from /home/samuel/.gem/ruby/3.4.1/gems/async-2.23.0/lib/async/scheduler.rb:396:in 'Async::Scheduler#run_once!'
...
```

## Solution

This PR introduces some new functions:

- `VALUE rb_io_interruptible_operation(VALUE self, VALUE(*function)(VALUE), VALUE argument)` for wrapping user IO operations so they can be interrupted.
- `IO#interruptable_operation(&block)` the same as above.
- `VALUE rb_fiber_scheduler_fiber_interrupt(VALUE scheduler, VALUE fiber, VALUE exception)` for interrupting a specific fiber on the fiber scheduler.

`rb_notify_fd_close` is modified so that it is fiber scheduler aware and uses `rb_fiber_scheduler_fiber_interrupt` to interrupt a fiber. In addition, we also change the internal `struct waiting_fd` to track the `rb_execution_context_t` rather than just the `rb_thread_t` instance, so that we can correctly wake up either the waiting thread or fiber.

The public interface `rb_io_interruptible_operation` and `IO#interruptible_operation` are introduced so that the scheduler implementation can wrap IO operations that should be interruptible, e.g.

```ruby
Fiber.schedule do
  io.interruptible_operation do
    io.wait_readable
  end
end

# Will interrupt above fiber:
io.close
```

See <https://github.com/ruby/ruby/pull/12585> for the proposed implementation and <https://github.com/socketry/io-event/pull/130> for example of how `io-event` gem uses both the C and Ruby interfaces.



-- 
https://bugs.ruby-lang.org/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/


In This Thread