[#121791] [Ruby Bug#21298] `ObjectSpace.allocation_class_path` returns inconsistent results depending on `TracePoint` state — "mame (Yusuke Endoh) via ruby-core" <ruby-core@...>

Issue #21298 has been reported by mame (Yusuke Endoh).

6 messages 2025/05/01

[#121830] [Ruby Feature#21309] Can Thread::Mutex be Ractor shareable? — "osyoyu (Daisuke Aritomo) via ruby-core" <ruby-core@...>

Issue #21309 has been reported by osyoyu (Daisuke Aritomo).

11 messages 2025/05/05

[#121837] [Ruby Feature#21311] Namespace on read (revised) — "tagomoris (Satoshi Tagomori) via ruby-core" <ruby-core@...>

Issue #21311 has been reported by tagomoris (Satoshi Tagomori).

109 messages 2025/05/06

[#121941] [Ruby Bug#21315] Finalizers violate the `rb_ractor_confirm_belonging` assertion — "byroot (Jean Boussier) via ruby-core" <ruby-core@...>

Issue #21315 has been reported by byroot (Jean Boussier).

8 messages 2025/05/09

[#121950] [Ruby Bug#21316] Namespaces leak with permanent names — "fxn (Xavier Noria) via ruby-core" <ruby-core@...>

Issue #21316 has been reported by fxn (Xavier Noria).

10 messages 2025/05/09

[#121956] [Ruby Bug#21321] Namespaces do not support extending mixins — "fxn (Xavier Noria) via ruby-core" <ruby-core@...>

Issue #21321 has been reported by fxn (Xavier Noria).

8 messages 2025/05/09

[#121973] [Ruby Bug#21322] Namespaces and builtin classes as arguments and return values — "fxn (Xavier Noria) via ruby-core" <ruby-core@...>

Issue #21322 has been reported by fxn (Xavier Noria).

8 messages 2025/05/10

[#122054] [Ruby Bug#21333] heap-use-after-free caused by rehash during update — "cyruscyliu (Qiang Liu) via ruby-core" <ruby-core@...>

Issue #21333 has been reported by cyruscyliu (Qiang Liu).

9 messages 2025/05/13

[#122086] [Ruby Bug#21337] Using `not` on the RHS of a logical operator becomes valid syntax with Prism — "koic (Koichi ITO) via ruby-core" <ruby-core@...>

Issue #21337 has been reported by koic (Koichi ITO).

7 messages 2025/05/14

[#122101] [Ruby Bug#21340] Bump autoconf version to properly handle C23 bool/stdbool defines — "christo (Chris Alberti) via ruby-core" <ruby-core@...>

Issue #21340 has been reported by christo (Chris Alberti).

12 messages 2025/05/14

[#122114] [Ruby Bug#21341] `Namespace is not a module (TypeError)` without enabling the namespace — "yahonda (Yasuo Honda) via ruby-core" <ruby-core@...>

Issue #21341 has been reported by yahonda (Yasuo Honda).

7 messages 2025/05/15

[#122121] [Ruby Bug#21342] Segfault: invalid keeping_mutexes when using Mutex in Thread then Fiber after GC — "maciej.mensfeld (Maciej Mensfeld) via ruby-core" <ruby-core@...>

Issue #21342 has been reported by maciej.mensfeld (Maciej Mensfeld).

11 messages 2025/05/15

[#122154] [Ruby Feature#21346] Introduce `String#ensure_suffix` — "matheusrich (Matheus Richard) via ruby-core" <ruby-core@...>

Issue #21346 has been reported by matheusrich (Matheus Richard).

21 messages 2025/05/16

[#122164] [Ruby Feature#21347] Add `open_timeout` as an overall timeout option for `Socket.tcp` — "shioimm (Misaki Shioi) via ruby-core" <ruby-core@...>

SXNzdWUgIzIxMzQ3IGhhcyBiZWVuIHJlcG9ydGVkIGJ5IHNoaW9pbW0gKE1pc2FraSBTaGlvaSku

9 messages 2025/05/17

[#122184] [Ruby Misc#21350] Bundled gems lack online documentation — "osyoyu (Daisuke Aritomo) via ruby-core" <ruby-core@...>

Issue #21350 has been reported by osyoyu (Daisuke Aritomo).

8 messages 2025/05/18

[#122218] [Ruby Bug#21357] Crash in Hash#merge! with ruby-dev in rubocop-rspec test suite — "Earlopain (Earlopain _) via ruby-core" <ruby-core@...>

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

7 messages 2025/05/21

[#122228] [Ruby Feature#21359] Introduce `Exception#cause=` for Post-Initialization Assignment — "ioquatix (Samuel Williams) via ruby-core" <ruby-core@...>

SXNzdWUgIzIxMzU5IGhhcyBiZWVuIHJlcG9ydGVkIGJ5IGlvcXVhdGl4IChTYW11ZWwgV2lsbGlh

9 messages 2025/05/22

[#122242] [Ruby Feature#21365] Add `Namespace#eval` — "tenderlovemaking (Aaron Patterson) via ruby-core" <ruby-core@...>

Issue #21365 has been reported by tenderlovemaking (Aaron Patterson).

12 messages 2025/05/22

[#122258] [Ruby Misc#21367] Remove link to ruby-doc.org from www.ruby-lang.org/en/documentation/ — "p8 (Petrik de Heus) via ruby-core" <ruby-core@...>

Issue #21367 has been reported by p8 (Petrik de Heus).

11 messages 2025/05/23

[#122277] [Ruby Bug#21371] Proposal to Remove SPARC Architecture Support from Ruby — "ioquatix (Samuel Williams) via ruby-core" <ruby-core@...>

SXNzdWUgIzIxMzcxIGhhcyBiZWVuIHJlcG9ydGVkIGJ5IGlvcXVhdGl4IChTYW11ZWwgV2lsbGlh

10 messages 2025/05/24

[#122343] [Ruby Misc#21385] Namespace: Suggesting a rename — "fxn (Xavier Noria) via ruby-core" <ruby-core@...>

SXNzdWUgIzIxMzg1IGhhcyBiZWVuIHJlcG9ydGVkIGJ5IGZ4biAoWGF2aWVyIE5vcmlhKS4NCg0K

32 messages 2025/05/30

[#122345] [Ruby Feature#21386] Introduce `Enumerable#join_map` — "matheusrich (Matheus Richard) via ruby-core" <ruby-core@...>

SXNzdWUgIzIxMzg2IGhhcyBiZWVuIHJlcG9ydGVkIGJ5IG1hdGhldXNyaWNoIChNYXRoZXVzIFJp

12 messages 2025/05/30

[ruby-core:122248] [Ruby Bug#18455] `IO#close` has poor performance and difficult to understand semantics.

From: "ioquatix (Samuel Williams) via ruby-core" <ruby-core@...>
Date: 2025-05-23 07:44:01 UTC
List: ruby-core #122248
Issue #18455 has been updated by ioquatix (Samuel Williams).

Status changed from Assigned to Closed

After merging https://github.com/ruby/ruby/pull/12839, we see nice performance improvements and the semantics are well documented.

```
> make benchmark BENCH_RUBY=/Users/samuel/.rubies/ruby-head/bin/ruby COMPARE_RUBY=/Users/samuel/.rubies/ruby-a6435bef/bin/ruby ITEM=io_close
/Users/samuel/.rubies/ruby-3.4.2/bin/ruby --disable=gems -rrubygems -I./benchmark/lib ./benchmark/benchmark-driver/exe/benchmark-driver \
	            --executables="compare-ruby::/Users/samuel/.rubies/ruby-a6435bef/bin/ruby -I.ext/common --disable-gem" \
	            --executables="built-ruby::/Users/samuel/.rubies/ruby-head/bin/ruby --disable-gem" \
	            --output=markdown --output-compare -v $(find ./benchmark -maxdepth 1 -name 'io_close' -o -name '*io_close*.yml' -o -name '*io_close*.rb' | sort) 
compare-ruby: ruby 3.5.0dev (2025-05-13T08:35:34Z master a6435befa7) +PRISM [arm64-darwin24]
built-ruby: ruby 3.5.0dev (2025-05-17T00:42:12Z master aa0f689bf4) +PRISM [arm64-darwin24]
last_commit=[ruby/net-http] Freeze some constants to improve Ractor compatibility
# Iteration per second (i/s)

|                    |compare-ruby|built-ruby|
|:-------------------|-----------:|---------:|
|io_close_contended  |     117.394|   179.029|
|                    |           -|     1.53x|
|io_close            |     821.862|   900.114|
|                    |           -|     1.10x|
```

----------------------------------------
Bug #18455: `IO#close` has poor performance and difficult to understand semantics.
https://bugs.ruby-lang.org/issues/18455#change-113394

* Author: ioquatix (Samuel Williams)
* Status: Closed
* Assignee: ioquatix (Samuel Williams)
* Backport: 2.6: UNKNOWN, 2.7: UNKNOWN, 3.0: UNKNOWN, 3.1: UNKNOWN
----------------------------------------
`IO#close` should be responsible for closing the file descriptor referred to by the IO instance. When dealing with buffered IO, one can also expect this to flush the internal buffers if possible.

Currently, all blocking IO operations release the GVL and perform the blocking system call using `rb_thread_io_blocking_region`. The current implementation takes a file descriptor and adds an entry to the VM global `waiting_fds` list. When the operation is completed, the entry is removed from `waiting_fds`.

When calling `IO#close`, this list is traversed and any threads performing blocking operations with a matching file descriptor are interrupted. The performance of this is O(number of blocking IO operations) which in practice the performance of `IO#close` can take milliseconds with 10,000 threads performing blocking IO. This performance is unacceptable.

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

require 'benchmark'

class Reading
  def initialize
    @r, @w = IO.pipe

    @thread = Thread.new do
      @r.read
    rescue IOError
      # Ignore.
    end
  end

  attr :r
  attr :w

  attr :thread

  def join
    @thread.join
  end
end

def measure(count = 10)
  readings = count.times.map do
    Reading.new
  end

  sleep 10

  duration = Benchmark.measure do
    readings.each do |reading|
      reading.r.close
      reading.w.close
    end
  end

  average = (duration.total / count) * 1000.0
  pp count: count, average: sprintf("%0.2fms", average)

  readings.each(&:join)
end

measure(   10)
measure(  100)
measure( 1000)
measure(10000)
```

In addition, the semantics of this operation are confusing at best. While Ruby programs are dealing with IO instances, the VM is dealing with file descriptors, in effect performing some internal de-duplication of IO state. In practice, this leads to strange behaviour:

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

r, w = IO.pipe
r2 = IO.for_fd(r.to_i)
pp r: r, r2: r2

t = Thread.new do
  r2.read rescue nil
  r2.read # EBADF
end

sleep 0.5
r.close
t.join rescue nil

pp r: r, r2: r2
# r is closed, r2 is valid but will raise EBADF on any operation.
```

In addition, this confusing behaviour extends to Ractor and state is leaked between the two:

``` ruby
r, w = IO.pipe

ractor = Ractor.new(r.to_i) do |fd|
  r2 = IO.for_fd(fd)
  r2.read
  # r2.read # EBADF
end

sleep 0.5
r.close

pp take: ractor.take
```

I propose the following changes to simplify the semantics and improve performance:

- Move the semantics of `waiting_fds` from per-fd to per-IO. This means that `IO#close` only interrupts blocking operations performed on the same IO instance rather than ANY IO which refers to the same file descriptor. I think this behaviour is easier to understand and still protects against the vast majority of incorrect usage.
- Move the details of `struct rb_io_t` to `internal/io.h` so that the implementation details are not part of the public interface.

## Benchmarks

Before:

```
{:count=>10, :average=>"0.19ms"}
{:count=>100, :average=>"0.11ms"}
{:count=>1000, :average=>"0.18ms"}
{:count=>10000, :average=>"1.16ms"}
```

After:

```
{:count=>10, :average=>"0.20ms"}
{:count=>100, :average=>"0.11ms"}
{:count=>1000, :average=>"0.15ms"}
{:count=>10000, :average=>"0.68ms"}
```

After investigating this further I found that the `rb_thread_io_blocking_region` using `ubf_select` can be incredibly slow, proportional to the number of threads. I don't know whether it's advisable but:

``` c
        BLOCKING_REGION(blocking_node.thread, {
            val = func(data1);
            saved_errno = errno;
        }, NULL /* ubf_select */, blocking_node.thread, FALSE);
```

Disabling the UBF function and relying on `read(fd, ...)`/`write(fd, ...)` blocking operations to fail when `close(fd)` is invoked might be sufficient? This needs more investigation but after making this change, we have constant-time IO#close.

```
{:count=>10, :average=>"0.13ms"}
{:count=>100, :average=>"0.06ms"}
{:count=>1000, :average=>"0.04ms"}
{:count=>10000, :average=>"0.09ms"}
```

Which is ideally what we want.



-- 
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

Prev Next