Skip to content

win32: canonicalize paths with GetFinalPathNameByHandleW - #2

Open
Largo wants to merge 1 commit into
masterfrom
win32-realpath
Open

Largo wants to merge 1 commit into
masterfrom
win32-realpath

Conversation

@Largo

@Largo Largo commented Aug 9, 2026

Copy link
Copy Markdown
Owner

rb_check_realpath_internal falls back to realpath_rec, which stats every component of every path. Ruby runs it over each loaded feature while canonicalizing $LOADED_FEATURES — about 14 stats per file at typical depths. Windows can do the same job with one CreateFileW + GetFinalPathNameByHandleW + CloseHandle.

win32/win32.c gains rb_w32_realpath(). It handles absolute drive-letter paths only, leaving relative and UNC paths to the walker; converts input and result in stack buffers with a heap fallback for long paths; strips the \\?\ NT namespace prefix but not \\?\UNC\; and normalizes backslashes to forward slashes so $LOADED_FEATURES dedupes against Ruby's canonical form (a C:\foo\bar.rb canonical would not match a C:/foo/bar.rb added earlier, and the file would load twice).

file.c calls it from rb_check_realpath_internal and falls through to the emulated walker whenever it returns NULL — relative path, missing file, access denied.

Measured on this VM: stat calls during require "gtk3" drop 2495 → 413 (−83%), the require itself ~33 ms faster; File.realpath 107 → 74 µs. tzinfo and sqlite3 unchanged.

Windows-only; every other platform takes the existing path. Not built or tested on Windows CI — measured in a local MSYS2/UCRT64 VM.

rb_check_realpath_internal falls back to realpath_rec, which stats every
component of every path, and Ruby runs it over each loaded feature while
canonicalizing $LOADED_FEATURES - around 14 stats per file at typical depths.
Windows can do the same job with one CreateFileW + GetFinalPathNameByHandleW
+ CloseHandle.

win32/win32.c: add rb_w32_realpath(). It takes absolute drive-letter paths
only, leaving relative and UNC paths to the walker, converts both the input
and the result in stack buffers with a heap fallback for long paths, strips
the "\\?\" NT namespace prefix (but not "\\?\UNC\"), and normalizes
backslashes to forward slashes so $LOADED_FEATURES dedupes against Ruby's
canonical form.

file.c: use it in rb_check_realpath_internal, falling through to the emulated
walker whenever it returns NULL (relative path, missing file, access denied).

Measured on this VM: stat calls during require "gtk3" drop from 2495 to 413
(-83%), the require itself about 33 ms faster; File.realpath 107 -> 74 us.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant