Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 26 additions & 4 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,15 @@ Note: We're only listing outstanding class updates.
waiting on a blocking IO operation when the IO operation is closed.
[[Feature #21166]]

* Introduce `Fiber::Scheduler#yield` to allow the fiber scheduler to
continue processing when signal exceptions are disabled.
[[Bug #21633]]

* Reintroduce the `Fiber::Scheduler#io_close` hook for asynchronous `IO#close`.

* Invoke `Fiber::Scheduler#io_write` when flushing the IO write buffer.
[[Bug #21789]]

* File

* `File::Stat#birthtime` is now available on Linux via the statx
Expand Down Expand Up @@ -266,6 +275,11 @@ Note: We're only listing outstanding class updates.
* Introduce support for `Thread#raise(cause:)` argument similar to
`Kernel#raise`. [[Feature #21360]]

* Pathname

* Pathname has been promoted from a default gem to a core class of Ruby.
[[Feature #17473]]

## Stdlib updates

The following bundled gems are promoted from default gems.
Expand All @@ -284,7 +298,7 @@ The following bundled gems are promoted from default gems.
We only list stdlib changes that are notable feature changes.

Other changes are listed in the following sections. We also listed release
history from the previous bundled version that is Ruby 3.3.0 if it has GitHub
history from the previous bundled version that is Ruby 3.4.0 if it has GitHub
releases.

The following default gem is added.
Expand Down Expand Up @@ -324,9 +338,6 @@ The following default gems are updated.
* weakref 0.1.4
* zlib 3.2.2

The following bundled gems are added.


The following bundled gems are updated.

* minitest 6.0.0
Expand All @@ -349,6 +360,15 @@ The following bundled gems are updated.
* csv 3.3.5
* repl_type_completor 0.1.12

### RubyGems and Bundler

see the following links for details.

* [Upgrading to RubyGems/Bundler 4 - RubyGems Blog](https://blog.rubygems.org/2025/12/03/upgrade-to-rubygems-bundler-4.html)
* [4.0.0 Released - RubyGems Blog](https://blog.rubygems.org/2025/12/03/4.0.0-released.html)
* [4.0.1 Released - RubyGems Blog](https://blog.rubygems.org/2025/12/09/4.0.1-released.html)
* [4.0.2 Released - RubyGems Blog](https://blog.rubygems.org/2025/12/17/4.0.2-released.html)

## Supported platforms

* Windows
Expand Down Expand Up @@ -546,8 +566,10 @@ A lot of work has gone into making Ractors more stable, performant, and usable.
[Feature #21550]: https://bugs.ruby-lang.org/issues/21550
[Feature #21552]: https://bugs.ruby-lang.org/issues/21552
[Feature #21557]: https://bugs.ruby-lang.org/issues/21557
[Bug #21633]: https://bugs.ruby-lang.org/issues/21633
[Bug #21654]: https://bugs.ruby-lang.org/issues/21654
[Feature #21678]: https://bugs.ruby-lang.org/issues/21678
[Bug #21698]: https://bugs.ruby-lang.org/issues/21698
[Feature #21701]: https://bugs.ruby-lang.org/issues/21701
[Feature #21785]: https://bugs.ruby-lang.org/issues/21785
[Bug #21789]: https://bugs.ruby-lang.org/issues/21789
12 changes: 10 additions & 2 deletions box.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "internal/file.h"
#include "internal/gc.h"
#include "internal/hash.h"
#include "internal/io.h"
#include "internal/load.h"
#include "internal/st.h"
#include "internal/variable.h"
Expand Down Expand Up @@ -627,20 +628,27 @@ copy_ext_file(const char *src_path, const char *dst_path)
# else
const int bin = 0;
# endif
const int src_fd = open(src_path, O_RDONLY|bin);
# ifdef O_CLOEXEC
const int cloexec = O_CLOEXEC;
# else
const int cloexec = 0;
# endif
const int src_fd = open(src_path, O_RDONLY|cloexec|bin);
if (src_fd < 0) return COPY_ERROR_SRC_OPEN;
if (!cloexec) rb_maygvl_fd_fix_cloexec(src_fd);

struct stat src_st;
if (fstat(src_fd, &src_st)) {
close(src_fd);
return COPY_ERROR_SRC_STAT;
}

const int dst_fd = open(dst_path, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC|bin, S_IRWXU);
const int dst_fd = open(dst_path, O_WRONLY|O_CREAT|O_EXCL|cloexec|bin, S_IRWXU);
if (dst_fd < 0) {
close(src_fd);
return COPY_ERROR_DST_OPEN;
}
if (!cloexec) rb_maygvl_fd_fix_cloexec(dst_fd);

enum copy_error_type ret = COPY_ERROR_NONE;

Expand Down
2 changes: 2 additions & 0 deletions depend
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,7 @@ box.$(OBJEXT): $(top_srcdir)/internal/file.h
box.$(OBJEXT): $(top_srcdir)/internal/gc.h
box.$(OBJEXT): $(top_srcdir)/internal/hash.h
box.$(OBJEXT): $(top_srcdir)/internal/imemo.h
box.$(OBJEXT): $(top_srcdir)/internal/io.h
box.$(OBJEXT): $(top_srcdir)/internal/load.h
box.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h
box.$(OBJEXT): $(top_srcdir)/internal/serial.h
Expand Down Expand Up @@ -951,6 +952,7 @@ box.$(OBJEXT): {$(VPATH)}internal/value_type.h
box.$(OBJEXT): {$(VPATH)}internal/variable.h
box.$(OBJEXT): {$(VPATH)}internal/warning_push.h
box.$(OBJEXT): {$(VPATH)}internal/xmalloc.h
box.$(OBJEXT): {$(VPATH)}io.h
box.$(OBJEXT): {$(VPATH)}iseq.h
box.$(OBJEXT): {$(VPATH)}method.h
box.$(OBJEXT): {$(VPATH)}missing.h
Expand Down
16 changes: 16 additions & 0 deletions doc/language/packed_data.rdoc
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,22 @@ for one element in the input or output array.
s.unpack('U*')
# => [4194304]

- <tt>'r'</tt> - Signed LEB128-encoded integer
(see {Signed LEB128}[https://en.wikipedia.org/wiki/LEB128#Signed_LEB128])

s = [1, 127, -128, 16383, -16384].pack("r*")
# => "\x01\xFF\x00\x80\x7F\xFF\xFF\x00\x80\x80\x7F"
s.unpack('r*')
# => [1, 127, -128, 16383, -16384]

- <tt>'R'</tt> - Unsigned LEB128-encoded integer
(see {Unsigned LEB128}[https://en.wikipedia.org/wiki/LEB128#Unsigned_LEB128])

s = [1, 127, 128, 16383, 16384].pack("R*")
# => "\x01\x7F\x80\x01\xFF\x7F\x80\x80\x01"
s.unpack('R*')
# => [1, 127, 128, 16383, 16384]

- <tt>'w'</tt> - BER-encoded integer
(see {BER encoding}[https://en.wikipedia.org/wiki/X.690#BER_encoding]):

Expand Down
24 changes: 24 additions & 0 deletions io.c
Original file line number Diff line number Diff line change
Expand Up @@ -1418,10 +1418,34 @@ io_flush_buffer_sync(void *arg)
return (VALUE)-1;
}

static inline VALUE
io_flush_buffer_fiber_scheduler(VALUE scheduler, rb_io_t *fptr)
{
VALUE ret = rb_fiber_scheduler_io_write_memory(scheduler, fptr->self, fptr->wbuf.ptr+fptr->wbuf.off, fptr->wbuf.len, 0);
if (!UNDEF_P(ret)) {
ssize_t result = rb_fiber_scheduler_io_result_apply(ret);
if (result > 0) {
fptr->wbuf.off += result;
fptr->wbuf.len -= result;
}
return result >= 0 ? (VALUE)0 : (VALUE)-1;
}
return ret;
}

static VALUE
io_flush_buffer_async(VALUE arg)
{
rb_io_t *fptr = (rb_io_t *)arg;

VALUE scheduler = rb_fiber_scheduler_current();
if (scheduler != Qnil) {
VALUE result = io_flush_buffer_fiber_scheduler(scheduler, fptr);
if (!UNDEF_P(result)) {
return result;
}
}

return rb_io_blocking_region_wait(fptr, io_flush_buffer_sync, fptr, RUBY_IO_WRITABLE);
}

Expand Down
23 changes: 23 additions & 0 deletions test/fiber/scheduler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,29 @@ def blocking(&block)
end
end

class IOScheduler < Scheduler
def __io_ops__
@__io_ops__ ||= []
end

def io_write(io, buffer, length, offset)
fd = io.fileno
str = buffer.get_string
__io_ops__ << [:io_write, fd, str]
Fiber.blocking { buffer.write(IO.for_fd(fd), 0, offset) }
end
end

class IOErrorScheduler < Scheduler
def io_read(io, buffer, length, offset)
return -Errno::EBADF::Errno
end

def io_write(io, buffer, length, offset)
return -Errno::EINVAL::Errno
end
end

# This scheduler has a broken implementation of `unblock`` in the sense that it
# raises an exception. This is used to test the behavior of the scheduler when
# unblock raises an exception.
Expand Down
89 changes: 89 additions & 0 deletions test/fiber/test_scheduler.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# frozen_string_literal: true
require 'test/unit'
require 'securerandom'
require 'fileutils'
require_relative 'scheduler'

class TestFiberScheduler < Test::Unit::TestCase
Expand Down Expand Up @@ -283,4 +285,91 @@ def test_post_fork_fiber_blocking
ensure
thread.kill rescue nil
end

def test_io_write_on_flush
fn = File.join(Dir.tmpdir, "ruby_test_io_write_on_flush_#{SecureRandom.hex}")
write_fd = nil
io_ops = nil
thread = Thread.new do
scheduler = IOScheduler.new
Fiber.set_scheduler scheduler

Fiber.schedule do
File.open(fn, 'w+') do |f|
write_fd = f.fileno
f << 'foo'
f.flush
f << 'bar'
end
end
io_ops = scheduler.__io_ops__
end
thread.join
assert_equal [
[:io_write, write_fd, 'foo'],
[:io_write, write_fd, 'bar']
], io_ops

assert_equal 'foobar', IO.read(fn)
ensure
thread.kill rescue nil
FileUtils.rm_f(fn)
end

def test_io_read_error
fn = File.join(Dir.tmpdir, "ruby_test_io_read_error_#{SecureRandom.hex}")
exception = nil
thread = Thread.new do
scheduler = IOErrorScheduler.new
Fiber.set_scheduler scheduler
Fiber.schedule do
File.open(fn, 'w+') { it.read }
rescue => e
exception = e
end
end
thread.join
assert_kind_of Errno::EBADF, exception
ensure
thread.kill rescue nil
FileUtils.rm_f(fn)
end

def test_io_write_error
fn = File.join(Dir.tmpdir, "ruby_test_io_write_error_#{SecureRandom.hex}")
exception = nil
thread = Thread.new do
scheduler = IOErrorScheduler.new
Fiber.set_scheduler scheduler
Fiber.schedule do
File.open(fn, 'w+') { it.sync = true; it << 'foo' }
rescue => e
exception = e
end
end
thread.join
assert_kind_of Errno::EINVAL, exception
ensure
thread.kill rescue nil
FileUtils.rm_f(fn)
end

def test_io_write_flush_error
fn = File.join(Dir.tmpdir, "ruby_test_io_write_flush_error_#{SecureRandom.hex}")
exception = nil
thread = Thread.new do
scheduler = IOErrorScheduler.new
Fiber.set_scheduler scheduler
Fiber.schedule do
File.open(fn, 'w+') { it << 'foo' }
rescue => e
exception = e
end
end
thread.join
assert_kind_of Errno::EINVAL, exception
ensure
thread.kill rescue nil
FileUtils.rm_f(fn)
end
end
3 changes: 3 additions & 0 deletions tool/rbs_skip_tests_windows
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
ARGFTest Failing on Windows

RactorSingletonTest Hangs up on Windows
RactorInstanceTest Hangs up on Windows

# NotImplementedError: fileno() function is unimplemented on this machine
test_fileno(DirInstanceTest)
test_fchdir(DirSingletonTest)
Expand Down