From e2e498404279f13a880828f472a32f1f3c82c9e7 Mon Sep 17 00:00:00 2001 From: Chris Hasinski Date: Fri, 2 Oct 2026 17:32:49 +0200 Subject: [PATCH 1/2] File#flock: offer flock(2) on macOS, where the rest of Posix is not Posix.IsAvailable is false on macOS, because the class is built on statx and Linux's errno numbers. File#flock sat behind the same gate, so on a Mac it raised NotImplementedError ("flock() function is unimplemented on this machine"). RubyGems takes a lock on every binstub it writes (Gem.open_file_with_flock from Gem::Installer#generate_bin_script), so `gem install` of anything with an executable failed on macOS at the very end, after the gem itself had been installed. flock(2) is the same call with the same LOCK_* bits on every Unix, so it gets its own gate, Posix.IsFlockAvailable, and the one errno it has to recognise: EWOULDBLOCK, which is 11 on Linux and 35 on macOS. ruby/spec core/file/flock_spec.rb: 5 examples, 0 failures, on arm64 macOS on .NET 10. --- Src/Libraries/Builtins/FileOps.cs | 4 ++-- Src/Libraries/Builtins/Posix.cs | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Src/Libraries/Builtins/FileOps.cs b/Src/Libraries/Builtins/FileOps.cs index 72fcbe932..6194d887c 100644 --- a/Src/Libraries/Builtins/FileOps.cs +++ b/Src/Libraries/Builtins/FileOps.cs @@ -1098,7 +1098,7 @@ internal static int GetNativeFileDescriptor(RubyIO/*!*/ io) { [RubyMethod("flock", BuildConfig = "FEATURE_FILESYSTEM")] public static object FileLock(RubyFile/*!*/ self, [DefaultProtocol]int operation) { self.RequireInitialized(); - if (!Posix.IsAvailable) { + if (!Posix.IsFlockAvailable) { throw new IronRuby.Builtins.NotImplementedError("flock() function is unimplemented on this machine"); } @@ -1118,7 +1118,7 @@ public static object FileLock(RubyFile/*!*/ self, [DefaultProtocol]int operation if (Posix.Flock(fd, operation | Posix.LOCK_NB, out errno) == 0) { return 0; } - if (errno != Posix.EWOULDBLOCK) { + if (errno != Posix.EWouldBlock) { throw Posix.Error(errno, self.Path); } if (nonBlocking) { diff --git a/Src/Libraries/Builtins/Posix.cs b/Src/Libraries/Builtins/Posix.cs index 5f0195cc4..c9038101a 100644 --- a/Src/Libraries/Builtins/Posix.cs +++ b/Src/Libraries/Builtins/Posix.cs @@ -25,6 +25,17 @@ internal static class Posix { System.IO.Path.DirectorySeparatorChar == '/' && !RuntimeInformation.IsOSPlatform(OSPlatform.OSX); + /// + /// flock(2) is the same call with the same operation bits on every Unix, so it is + /// offered on macOS too, where the rest of this class (statx, Linux errno numbers) + /// is not. RubyGems takes a lock on every binstub it writes, so without this + /// `gem install` of anything with an executable failed on a Mac. + /// + internal static readonly bool IsFlockAvailable = System.IO.Path.DirectorySeparatorChar == '/'; + + /// errno for "would block" on this platform: EAGAIN is 11 on Linux and 35 on macOS. + internal static readonly int EWouldBlock = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 35 : EWOULDBLOCK; + #region errno numbers internal const int EPERM = 1; From 49d091b1425cc83d533528364012b717b0dd9b89 Mon Sep 17 00:00:00 2001 From: Largo Date: Tue, 13 Oct 2026 23:48:36 +0200 Subject: [PATCH 2/2] File#flock: EWOULDBLOCK is 35 on every BSD, not only macOS IsFlockAvailable lets every Unix in, and FreeBSD, NetBSD and OpenBSD number their errnos the way macOS does: EAGAIN/EWOULDBLOCK is 35 there too. With 35 picked for macOS alone, a contended LOCK_NB on a BSD raised Errno instead of returning false, and a blocking lock raised instead of waiting. --- Src/Libraries/Builtins/Posix.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Src/Libraries/Builtins/Posix.cs b/Src/Libraries/Builtins/Posix.cs index c9038101a..197adc863 100644 --- a/Src/Libraries/Builtins/Posix.cs +++ b/Src/Libraries/Builtins/Posix.cs @@ -33,8 +33,20 @@ internal static class Posix { /// internal static readonly bool IsFlockAvailable = System.IO.Path.DirectorySeparatorChar == '/'; - /// errno for "would block" on this platform: EAGAIN is 11 on Linux and 35 on macOS. - internal static readonly int EWouldBlock = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 35 : EWOULDBLOCK; + /// + /// errno for "would block" on this platform: EAGAIN is 11 on Linux and 35 on macOS and + /// the other BSDs, whose errno numbering macOS inherited. IsFlockAvailable lets every + /// Unix in, so a BSD has to get 35 too, or a contended LOCK_NB raises instead of + /// returning false and a blocking lock never retries. + /// + internal static readonly int EWouldBlock = IsBsdErrno() ? 35 : EWOULDBLOCK; + + private static bool IsBsdErrno() { + return RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || + RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD) || + RuntimeInformation.IsOSPlatform(OSPlatform.Create("NETBSD")) || + RuntimeInformation.IsOSPlatform(OSPlatform.Create("OPENBSD")); + } #region errno numbers