nogvl_readdir reported a readdir failure by reading errno after
re-acquiring the GVL. Under M:N threads, the thread may be resumed on
another native thread there, and errno is per native thread, so the
exception had a wrong class.
# strace -f -e trace=getdents64 -e inject=getdents64:error=EIO ruby test.rb
p Ractor.new {
4.times { Thread.new { loop { File.exist?("/nonexistent") } } }
sleep 0.1
Dir.children(".") rescue $!.class
}.value
#=> Errno::ENOENT (should be Errno::EIO)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`Dir.scan` broke the build on Haiku. Leave the type unknown there and let the `lstat` fallback below fill it in.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
These still held String and Array lengths and indices in long, or cast them to long. rb_len_t is still long everywhere, so the generated code does not change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The file, dir, load and ruby.c changes go together because rb_get_expanded_load_path() returns the maximum path length through a pointer. Lengths passed to the Windows string conversion helpers that take `long *` stay `long`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
include/ruby/win32.h and win32/dir.h have mapped them to the rb_w32_u* functions since 5b98b2ce39.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DOSISH is defined only on _WIN32, which has used USE_NAME_ON_FS_REAL_BASENAME since 45df1c24d2, so this code is never compiled. It even passes an undeclared `basename` to do_opendir.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
last_cwd is set on init in the main Ractor, but Dir.pwd attempts to
store into it. When this is done on a child Ractor we violate the
invariant that the owning and registering Ractor must be the same. On a
normal build the use-after-free is invisible, because we only zero out
the flags, so the cache check compares the slot bytes as usual and just
thinks it's a cache miss.
It gets flagged on ASAN builds
If the Dir object is closed in the block of Dir#each/each_child/scan, then
it will crash because dirp->dir will be a NULL pointer. The following
script demonstrates the crash:
d = Dir.open("/")
d.each { d.close }
Track explicit lengths while parsing command strings and comparing the
current directory. This avoids relying on Ruby string terminators and
makes separators in the generated argument buffer explicit.
Set RUBY_TYPED_THREAD_SAFE_FREE on TypedData types whose dfree function
is trivially safe, and only frees its own memory
Types not yet marked as THREAD_SAFE_FREE:
- id2ref_tbl_type: sets the process-global id2ref_tbl to NULL.
- mutex_data_type: unlinks itself from another thread's keeping_mutexes list.
- autoload_data_type: deletes nodes from a shared intrusive list.
- autoload_const_type: deletes itself from a shared list.
- rb_cont_data_type: mutates the shared fiber pool and the global first_jit_cont list.
- rb_fiber_data_type: delegates to cont_free.
- FiberPoolDataType: mutates shared fiber pool free-lists.
- ractor_data_type: reconfigures global VM event-hook flags and call caches.
- exported_object_registry: frees a global table under the VM lock.
- rb_box_data_type (Box::Entry): unlinks classext from other live class and module objects.
- box_ext_cleanup_type: dereferences another String object and calls unlink.
- monitor_data_type: uses the default free, so the flag has no effect.
Co-authored-by: Luke Gruber <luke.gruber@shopify.com>
In glob_opendir(), each directory entry is copied before the entries
array is grown. If growing ent->sort.entries fails, the function jumps
to the nomem label before the copied entry is stored in the array.
glob_dir_finish() only frees entries already recorded in
ent->sort.entries, so the current rdp is leaked on that error path.
Free rdp before jumping to nomem.
Signed-off-by: Mikhail Dmitrichenko <m.dmitrichenko222@gmail.com>
`rb_dir_getwd_ospath()` is called quite frequently, but
the overwhelming majority of the time, the current directory
didn't change.
We can also assume that most of the time, `PATH_MAX` is enough
for `getcwd`, hence we can first attempt to use a small stack
buffer rather than always allocate on the heap.
This way we can keep the last `pwd` and revalidate it with no
allocation.
On macOS syscalls are fairly slow, so the gain isn't very large.
macOS:
```
compare-ruby: ruby 4.1.0dev (2026-04-09T05:19:02Z master c091c186e4) +PRISM [arm64-darwin25]
built-ruby: ruby 4.1.0dev (2026-04-09T06:37:20Z get-cwd-cache ea02126d79) +PRISM [arm64-darwin25]
```
| |compare-ruby|built-ruby|
|:--------|-----------:|---------:|
|Dir.pwd | 105.183k| 113.420k|
| | -| 1.08x|
```
Linux (inside virtualized Docker)
```
compare-ruby: ruby 4.1.0dev (2026-04-07T08:26:25Z master fcd210086c) +PRISM [aarch64-linux]
built-ruby: ruby 4.1.0dev (2026-04-09T06:38:09Z get-cwd-cache 6774af9ba7) +PRISM [aarch64-linux]
```
| |compare-ruby|built-ruby|
|:--------|-----------:|---------:|
|Dir.pwd | 4.157M| 5.541M|
| | -| 1.33x|
This replaces GC-based buffer guards (rb_imemo_tmpbuf,
TypedData_Wrap_Struct) with rb_ensure to clean up malloc/xmalloc memory
when an exception occurs.
This was part of the spec accepted in [Feature #17001] but that
I forgot to include in the initial implementation.
to ensure coverage, we always go though the fallback path in debug
mode.
[Feature #21800]
There are numerous ruby tools that need to recursively scan
the project directory, such as Zeitwerk, rubocop, etc.
All of them end up listing childs of a directory then for each child
emit a `stat` call to check if it's a directory or not.
This is common enough for a pattern that on most operating
systems, `struct dirent` include a `dtype` member that allows to
check the file type without issuing a any extra system calls.
By yielding that type, we can make these routines twice as fast.
```
$ hyperfine './miniruby --disable-all --yjit ../test.rb' 'OPT=1 ./miniruby --disable-all --yjit ../test.rb'
Benchmark 1: ./miniruby --disable-all --yjit ../test.rb
Time (mean ± σ): 1.428 s ± 0.062 s [User: 0.342 s, System: 1.070 s]
Range (min … max): 1.396 s … 1.601 s 10 runs
Benchmark 2: OPT=1 ./miniruby --disable-all --yjit ../test.rb
Time (mean ± σ): 673.8 ms ± 5.8 ms [User: 146.0 ms, System: 527.3 ms]
Range (min … max): 659.7 ms … 679.6 ms 10 runs
Summary
OPT=1 ./miniruby --disable-all --yjit ../test.rb ran
2.12 ± 0.09 times faster than ./miniruby --disable-all --yjit ../test.rb
```
```ruby
if ENV['OPT']
def count_ruby_files
count = 0
queue = [File.expand_path(__dir__)]
while dir = queue.pop
Dir.scan(dir) do |name, type|
next if name.start_with?(".")
case type
when :directory
queue << File.join(dir, name)
when :file
count += 1 if name.end_with?(".rb")
end
end
end
count
end
else
def count_ruby_files
count = 0
queue = [File.expand_path(__dir__)]
while dir = queue.pop
Dir.each_child(dir) do |name|
next if name.start_with?(".")
abspath = File.join(dir, name)
if File.directory?(abspath)
queue << abspath
else
count += 1 if name.end_with?(".rb")
end
end
end
count
end
end
10.times do
count_ruby_files
end
```
Delete duplicated `Document-const:` directives
Those directives are attached to `rb_define_const` and `rb_file_const` where we don’t need them because the functions are supported by RDoc.
Raise SystemCallError exception when these functions return an error.
This changes behavior for the following case (found by the tests):
```ruby
dir1 = Dir.new('..')
dir2 = Dir.for_fd(dir1.fileno)
dir1.close
dir2.close
```
The above code is basically broken, as `dir1.close` closed the file
descriptor. The subsequent `dir2.close` call is undefined behavior.
When run in isolation, it raises Errno::EBADF after the change, but
if another thread opens a file descriptor between the `dir1.close`
and `dir2.close` calls, the `dir2.close` call could close the file
descriptor opened by the other thread. Raising an exception is much
better in this case as it makes it obvious there is a bug in the code.
For the readdir check, since the GVL has already been released,
reacquire it rb_thread_call_with_gvl if an exception needs to be
raised.
Due to the number of closedir calls, this adds static close_dir_data
and check_closedir functions. The close_dir_data takes a
struct dir_data * and handles setting the dir entry to NULL regardless
of failure.
Fixes [Bug #20586]
Co-authored-by: Nobuyoshi Nakada <nobu.nakada@gmail.com>
* Release GVL for fdopendir calls
* Release GVL for readdir calls
* Release GVL for chdir call in dir_chdir0
* Release GVL for fchdir call in dir_fchdir
* Release GVL for chroot calls
* Release GVL for lstat calls
* Release GVL for stat calls
* Release GVL for fstatat calls
* Release GVL for getpwnam call in rb_home_dir_of
(technically in file.c, but called from dir.c)
This does not release GVL for readdir/stat/lstat on Windows,
as that causes issues because the emulation functions that
are called in win32.c require the GVL.
This also removes some explicit casts either to or from void *,
which are allowed implicitly. The remaining casts to or from
void * are part of function pointer casts, which are not
allowed implicitly and will generate a warning.
Ref: https://github.com/ruby/ruby/pull/10872
These should be the last internal uses of the old `Data` API
inside Ruby itself. Some use remain in a couple default gems.
The macro SafeStringValue() became just StringValue() in c5c05460ac,
and it is deprecated nowadays.
This patch replaces remaining macro usage. Some occurrences are left in
ext/stringio and ext/win32ole, they should be fixed upstream.
The macro itself is not deleted, because it may be used in extensions.
This patch introduce M:N thread scheduler for Ractor system.
In general, M:N thread scheduler employs N native threads (OS threads)
to manage M user-level threads (Ruby threads in this case).
On the Ruby interpreter, 1 native thread is provided for 1 Ractor
and all Ruby threads are managed by the native thread.
From Ruby 1.9, the interpreter uses 1:1 thread scheduler which means
1 Ruby thread has 1 native thread. M:N scheduler change this strategy.
Because of compatibility issue (and stableness issue of the implementation)
main Ractor doesn't use M:N scheduler on default. On the other words,
threads on the main Ractor will be managed with 1:1 thread scheduler.
There are additional settings by environment variables:
`RUBY_MN_THREADS=1` enables M:N thread scheduler on the main ractor.
Note that non-main ractors use the M:N scheduler without this
configuration. With this configuration, single ractor applications
run threads on M:1 thread scheduler (green threads, user-level threads).
`RUBY_MAX_CPU=n` specifies maximum number of native threads for
M:N scheduler (default: 8).
This patch will be reverted soon if non-easy issues are found.
[Bug #19842]
(https://github.com/ruby/irb/pull/648)
IRB already has several features that rely on rdoc, such as:
- Autocompletion's document dialog
- Autocompletion's `PerfectMatchedProc`
- The `show_doc` command
- Easter egg
And we could use its pager more in the future too. So it makes sense to
declare rdoc as a dependency instead of relying on the one bundled with
Ruby.
https://github.com/ruby/irb/commit/4dffbb1dd3