[#114774] [Ruby master Feature#19884] Make Safe Navigation Operator work on classes — "p8 (Petrik de Heus) via ruby-core" <ruby-core@...>
Issue #19884 has been reported by p8 (Petrik de Heus).
13 messages
2023/09/15
[ruby-core:114838] [Ruby master Bug#19624] Backticks - IO object leakage
From:
"byroot (Jean Boussier) via ruby-core" <ruby-core@...>
Date:
2023-09-20 13:01:12 UTC
List:
ruby-core #114838
Issue #19624 has been updated by byroot (Jean Boussier).
> Do you have any short reproducible code?
No sorry, I don't understand the fiber scheduler enough to reduce the test =
suite.
I discovered the bug via: https://github.com/rmosolgo/graphql-ruby/issues/4=
640#issuecomment-1727220148
```
GraphQL::Dataloader::AsyncDataloader::With the toy scheduler from Ruby's te=
sts#test_0003_works with GraphQL:
NotImplementedError: method `hash' called on hidden T_FILE object (0x000000=
0119cd5ca0 flags=3D0xb)
src/graphql-ruby/spec/support/dummy_scheduler.rb:159:in `io_wait'
src/graphql-ruby/spec/graphql/dataloader/async_dataloader_spec.rb:69:in=
``'
src/graphql-ruby/spec/graphql/dataloader/async_dataloader_spec.rb:69:in=
`sleep'
```
I tried extracting their scheduler to make a self contained repro, but with=
out success:
```ruby
class DummyScheduler
def initialize
@readable =3D {}
@writable =3D {}
@waiting =3D {}
@closed =3D false
@lock =3D Mutex.new
@blocking =3D 0
@ready =3D []
@urgent =3D IO.pipe
end
attr :readable
attr :writable
attr :waiting
def next_timeout
_fiber, timeout =3D @waiting.min_by{|key, value| value}
if timeout
offset =3D timeout - current_time
if offset < 0
return 0
else
return offset
end
end
end
def run
# $stderr.puts [__method__, Fiber.current].inspect
while @readable.any? or @writable.any? or @waiting.any? or @blocking.po=
sitive?
# Can only handle file descriptors up to 1024...
readable, writable =3D IO.select(@readable.keys + [@urgent.first], @w=
ritable.keys, [], next_timeout)
# puts "readable: #{readable}" if readable&.any?
# puts "writable: #{writable}" if writable&.any?
selected =3D {}
readable && readable.each do |io|
if fiber =3D @readable.delete(io)
selected[fiber] =3D IO::READABLE
elsif io =3D=3D @urgent.first
@urgent.first.read_nonblock(1024)
end
end
writable && writable.each do |io|
if fiber =3D @writable.delete(io)
selected[fiber] |=3D IO::WRITABLE
end
end
selected.each do |fiber, events|
fiber.resume(events)
end
if @waiting.any?
time =3D current_time
waiting, @waiting =3D @waiting, {}
waiting.each do |fiber, timeout|
if fiber.alive?
if timeout <=3D time
fiber.resume
else
@waiting[fiber] =3D timeout
end
end
end
end
if @ready.any?
ready =3D nil
@lock.synchronize do
ready, @ready =3D @ready, []
end
ready.each do |fiber|
fiber.resume
end
end
end
end
def close
# $stderr.puts [__method__, Fiber.current].inspect
raise "Scheduler already closed!" if @closed
self.run
ensure
@urgent.each(&:close)
@urgent =3D nil
@closed =3D true
# We freeze to detect any unintended modifications after the scheduler =
is closed:
self.freeze
end
def closed?
@closed
end
def current_time
Process.clock_gettime(Process::CLOCK_MONOTONIC)
end
def timeout_after(duration, klass, message, &block)
fiber =3D Fiber.current
self.fiber do
sleep(duration)
if fiber && fiber.alive?
fiber.raise(klass, message)
end
end
begin
yield(duration)
ensure
fiber =3D nil
end
end
def process_wait(pid, flags)
# $stderr.puts [__method__, pid, flags, Fiber.current].inspect
# This is a very simple way to implement a non-blocking wait:
Thread.new do
Process::Status.wait(pid, flags)
end.value
end
def io_wait(io, events, duration)
p io
# $stderr.puts [__method__, io, events, duration, Fiber.current].inspect
unless (events & IO::READABLE).zero?
@readable[io] =3D Fiber.current
end
unless (events & IO::WRITABLE).zero?
@writable[io] =3D Fiber.current
end
Fiber.yield
end
# Used for Kernel#sleep and Mutex#sleep
def kernel_sleep(duration =3D nil)
# $stderr.puts [__method__, duration, Fiber.current].inspect
self.block(:sleep, duration)
return true
end
# Used when blocking on synchronization (Mutex#lock, Queue#pop, SizedQueu=
e#push, ...)
def block(blocker, timeout =3D nil)
# $stderr.puts [__method__, blocker, timeout].inspect
if timeout
@waiting[Fiber.current] =3D current_time + timeout
begin
Fiber.yield
ensure
# Remove from @waiting in the case #unblock was called before the t=
imeout expired:
@waiting.delete(Fiber.current)
end
else
@blocking +=3D 1
begin
Fiber.yield
ensure
@blocking -=3D 1
end
end
end
# Used when synchronization wakes up a previously-blocked fiber (Mutex#un=
lock, Queue#push, ...).
# This might be called from another thread.
def unblock(blocker, fiber)
# $stderr.puts [__method__, blocker, fiber].inspect
# $stderr.puts blocker.backtrace.inspect
# $stderr.puts fiber.backtrace.inspect
@lock.synchronize do
@ready << fiber
end
io =3D @urgent.last
io.write_nonblock('.')
end
def fiber(&block)
fiber =3D Fiber.new(blocking: false, &block)
fiber.resume
return fiber
end
def address_resolve(hostname)
Thread.new do
Addrinfo.getaddrinfo(hostname, nil).map(&:ip_address).uniq
end.value
end
end
Fiber.set_scheduler(DummyScheduler.new)
p `sleep 2`
```
Perhaps @ioquatix would know how to reproduce?
----------------------------------------
Bug #19624: Backticks - IO object leakage
https://bugs.ruby-lang.org/issues/19624#change-104683
* Author: pineman (Jo=E3o Pinheiro)
* Status: Open
* Priority: Normal
* ruby -v: ruby 3.2.2 (2023-03-30 revision e51014f9c0) [arm64-darwin22]
* Backport: 3.0: UNKNOWN, 3.1: UNKNOWN, 3.2: UNKNOWN
----------------------------------------
Hi,
This code works on ruby 3.0.6:
```ruby
`echo`
ObjectSpace.each_object(IO) do |io|
if ![STDIN, STDOUT, STDERR].include?(io)
io.close
end
end
```
but raises `IOError` on 3.2.2:
```
minimal-repro-case.rb:8:in `close': uninitialized stream (IOError)
```
I found it started failing on ruby 3.1.0 and after, on macOS and Linux.
This code is useful for closing unneeded IO objects in forked processes.
It looks like backticks is 'leaking' IO objects, waiting for GC, and it did=
n't used to before 3.1.0.
In ruby 3.1.0, inside `rb_f_backquote` in `io.c`, `rb_gc_force_recycle` was=
removed in favor of `RB_GC_GUARD` (commit `aeae6e2842e`). I wonder if this=
has something to do with the problem.
Is this code incorrect since ruby 3.1.0 or is it a bug in ruby?
Thanks.
---Files--------------------------------
minimal-repro-case.rb (109 Bytes)
--=20
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/postorius/lists/ruby-c=
ore.ml.ruby-lang.org/