runtime: Getting file descriptor count on macOS throws

Description

A known way to get the file descriptor count on macOS is by using /dev/fd. Here is an example implementation in Rust.

The problem is that some file descriptors listed under /dev/fd are not stat-able, so they will fail when queried. One example is kqueues.

Reproduction Steps

Directory.EnumerateFileSystemEntries("/dev/fd").Count(); on macOS.

Expected behavior

The number of open file descriptors.

Actual behavior

C# > Directory.EnumerateFileSystemEntries("/dev/fd").Count();
╭─❌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ System.UnauthorizedAccessException: Access to the path '/dev/fd/5' is denied.                                                          │
│  ---> System.IO.IOException: Bad file descriptor                                                                                       │
│    --- End of inner exception stack trace ---                                                                                          │
│    at System.IO.FileStatus.ThrowOnCacheInitializationError(ReadOnlySpan`1 path)                                                        │
│    at System.IO.Enumeration.FileSystemEntry.get_IsSymbolicLink()                                                                       │
│    at System.IO.Enumeration.FileSystemEntry.Initialize(FileSystemEntry& entry, DirectoryEntry directoryEntry, ReadOnlySpan`1           │
│ directory, ReadOnlySpan`1 rootDirectory, ReadOnlySpan`1 originalRootDirectory, Span`1 pathBuffer)                                      │
│    at System.IO.Enumeration.FileSystemEnumerator`1.MoveNext()                                                                          │
│    at System.Linq.Enumerable.Count[TSource](IEnumerable`1 source)                                                                      │
│    at Submission#3.<<Initialize>>d__0.MoveNext()                                                                                       │
│ --- End of stack trace from previous location ---                                                                                      │
│    at Microsoft.CodeAnalysis.Scripting.ScriptExecutionState.RunSubmissionsAsync[TResult](ImmutableArray`1 precedingExecutors, Func`2   │
│ currentExecutor, StrongBox`1 exceptionHolderOpt, Func`2 catchExceptionOpt, CancellationToken cancellationToken)                        │
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯

Regression?

Not sure if it’s a regression.

Known Workarounds

Manually PInvoke opendir and then iterate with readdir.

Configuration

dotnet 6.0.101, macOS 10.14+, x64 and arm64

Other information

No response

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 20 (19 by maintainers)

Most upvoted comments

Thank you, @Therzok. Since you have the (albeit unfortunate) workaround in place are are unblocked, and the backport isn’t 100% straightforward/contained, I’m not inclined to backport the fix. If others report the same issue in a way that they are blocked, that would encourage the backport though.

Hey @jeffhandley , thanks for the response!

Could you describe the type of application this is impacting for you and whether you’d be able to adopt a preview release of .NET 7.0 to gain the fix?

I work on Visual Studio for Mac. We use this API to check whether we are close to hitting the ridiculously low limit of open file handles (256). There currently is a workaround in place where we count filescriptors in C - did not have one at the time.

I’m not sure if publishing a release on top of .NET 7.0 is an option right now.

Testing on net7

Console.WriteLine(Directory.EnumerateFileSystemEntries("/dev/fd").Count());

Yields 38 which is correct and matches lsof -p.

lsof -p output
...
net7    27990 therzok    0u     CHR               16,6  0t38803                 757 /dev/ttys006
net7    27990 therzok    1u     CHR               16,6  0t38803                 757 /dev/ttys006
net7    27990 therzok    2u     CHR               16,6  0t38803                 757 /dev/ttys006
net7    27990 therzok    3     PIPE 0xcc8e9a57a037605b    16384                     ->0xefd942e58ae5b326
net7    27990 therzok    4     PIPE 0xefd942e58ae5b326    16384                     ->0xcc8e9a57a037605b
net7    27990 therzok    5u  KQUEUE                                                 count=0, state=0xa
net7    27990 therzok    6u     CHR               16,6  0t38803                 757 /dev/ttys006
net7    27990 therzok    7u     CHR               16,6  0t38803                 757 /dev/ttys006
net7    27990 therzok    8u     CHR               16,6  0t38803                 757 /dev/ttys006
net7    27990 therzok    9u    unix 0xb90d2201eb329317      0t0                     /var/folders/vl/jslq2mpn1s940dlk4wdx93zc0000gn/T/dotnet-diagnostic-27990-1649337425-socket
net7    27990 therzok   11r     REG                1,5 10703872            97260277 /usr/local/share/dotnet/shared/Microsoft.NETCore.App/7.0.0-preview.2.22152.2/System.Private.CoreLib.dll
net7    27990 therzok   12r     REG                1,5 10703872            97260277 /usr/local/share/dotnet/shared/Microsoft.NETCore.App/7.0.0-preview.2.22152.2/System.Private.CoreLib.dll
net7    27990 therzok   13     PIPE 0x727c006809e6d393    16384                     ->0x159a16bc09365536
net7    27990 therzok   14     PIPE 0x159a16bc09365536    16384                     ->0x727c006809e6d393
net7    27990 therzok   15u  KQUEUE                                                 count=0, state=0
net7    27990 therzok   16r     REG                1,5     5120            97264134 /Users/therzok/Projects/net7/bin/Debug/net7.0/net7.dll
net7    27990 therzok   17r     REG                1,5     5120            97264134 /Users/therzok/Projects/net7/bin/Debug/net7.0/net7.dll
net7    27990 therzok   18r     REG                1,5    32768            97260327 /usr/local/share/dotnet/shared/Microsoft.NETCore.App/7.0.0-preview.2.22152.2/System.Runtime.dll
net7    27990 therzok   19r     REG                1,5    32768            97260327 /usr/local/share/dotnet/shared/Microsoft.NETCore.App/7.0.0-preview.2.22152.2/System.Runtime.dll
net7    27990 therzok   20r     REG                1,5   196096            97260230 /usr/local/share/dotnet/shared/Microsoft.NETCore.App/7.0.0-preview.2.22152.2/System.Console.dll
net7    27990 therzok   21r     REG                1,5   196096            97260230 /usr/local/share/dotnet/shared/Microsoft.NETCore.App/7.0.0-preview.2.22152.2/System.Console.dll
net7    27990 therzok   22r     REG                1,5   530944            97260351 /usr/local/share/dotnet/shared/Microsoft.NETCore.App/7.0.0-preview.2.22152.2/System.Linq.dll
net7    27990 therzok   23r     REG                1,5   530944            97260351 /usr/local/share/dotnet/shared/Microsoft.NETCore.App/7.0.0-preview.2.22152.2/System.Linq.dll
net7    27990 therzok   24r     REG                1,5     5632            97260347 /usr/local/share/dotnet/shared/Microsoft.NETCore.App/7.0.0-preview.2.22152.2/System.Threading.Thread.dll
net7    27990 therzok   25r     REG                1,5     5632            97260347 /usr/local/share/dotnet/shared/Microsoft.NETCore.App/7.0.0-preview.2.22152.2/System.Threading.Thread.dll
net7    27990 therzok   26r     REG                1,5    70144            97260221 /usr/local/share/dotnet/shared/Microsoft.NETCore.App/7.0.0-preview.2.22152.2/System.Threading.dll
net7    27990 therzok   27r     REG                1,5    70144            97260221 /usr/local/share/dotnet/shared/Microsoft.NETCore.App/7.0.0-preview.2.22152.2/System.Threading.dll
net7    27990 therzok   28r     REG                1,5    41472            97260255 /usr/local/share/dotnet/shared/Microsoft.NETCore.App/7.0.0-preview.2.22152.2/System.Runtime.InteropServices.dll
net7    27990 therzok   29r     REG                1,5    41472            97260255 /usr/local/share/dotnet/shared/Microsoft.NETCore.App/7.0.0-preview.2.22152.2/System.Runtime.InteropServices.dll
net7    27990 therzok   30u     CHR               16,6  0t38803                 757 /dev/ttys006
net7    27990 therzok   31r     REG                1,5     5632            97260360 /usr/local/share/dotnet/shared/Microsoft.NETCore.App/7.0.0-preview.2.22152.2/Microsoft.Win32.Primitives.dll
net7    27990 therzok   32r     REG                1,5     5632            97260360 /usr/local/share/dotnet/shared/Microsoft.NETCore.App/7.0.0-preview.2.22152.2/Microsoft.Win32.Primitives.dll
net7    27990 therzok   33     PIPE 0xec3abe33a5671d10    16384                     ->0x6ba264456d0ba0ba
net7    27990 therzok   34     PIPE 0x6ba264456d0ba0ba    16384                     ->0xec3abe33a5671d10
net7    27990 therzok   35r     REG                1,5   165888            97260376 /usr/local/share/dotnet/shared/Microsoft.NETCore.App/7.0.0-preview.2.22152.2/System.Memory.dll
net7    27990 therzok   36r     REG                1,5   255488            97260297 /usr/local/share/dotnet/shared/Microsoft.NETCore.App/7.0.0-preview.2.22152.2/System.Collections.dll
net7    27990 therzok   37r     REG                1,5   255488            97260297 /usr/local/share/dotnet/shared/Microsoft.NETCore.App/7.0.0-preview.2.22152.2/System.Collections.dll
net7    27990 therzok   38r     REG                1,5   165888            97260376 /usr/local/share/dotnet/shared/Microsoft.NETCore.App/7.0.0-preview.2.22152.2/System.Memory.dll
...

Closing as we added a workaround on our end, but having this API work in net6 would be nice.

it’s a regression that has been introduced in net6.0

It was introduced in https://github.com/dotnet/runtime/pull/52235

and it’s specific to macOS.

Yes, and also specific to the special directory of /dev/fd which represents the open file descriptors of the process. One of these file descriptors doesn’t support stat probably because its something special that is not ordinarily found in a real file system.

how difficult would it be to backport part of https://github.com/dotnet/runtime/pull/60214 (the bug fix without refactor) to 6.0?

https://github.com/dotnet/runtime/pull/60214 is a bugfix PR, it doesn’t do much refactoring. There are little things to be left out. I think you should take it as a whole.

There was a separate PR to refactor some things: https://github.com/dotnet/runtime/pull/62721.

This code has been recently refactored by @tmds:

Yes, this was refactored (and fixed) in https://github.com/dotnet/runtime/pull/60214. One thing that was missing is continueOnError: true for IsSymbolicLink which is the cause for this exception.