Skip to content

Commit

Permalink
Fix bugzilla 24524: Very slow process fork if RLIMIT_NOFILE is too high
Browse files Browse the repository at this point in the history
  • Loading branch information
trikko committed Apr 27, 2024
1 parent 54eb95c commit 53ccbae
Showing 1 changed file with 77 additions and 39 deletions.
116 changes: 77 additions & 39 deletions std/process.d
Original file line number Diff line number Diff line change
Expand Up @@ -1034,54 +1034,92 @@ private Pid spawnProcessPosix(scope const(char[])[] args,

if (!(config.flags & Config.Flags.inheritFDs))
{
// NOTE: malloc() and getrlimit() are not on the POSIX async
// signal safe functions list, but practically this should
// not be a problem. Java VM and CPython also use malloc()
// in its own implementation via opendir().
import core.stdc.stdlib : malloc;
import core.sys.posix.poll : pollfd, poll, POLLNVAL;
import core.sys.posix.sys.resource : rlimit, getrlimit, RLIMIT_NOFILE;

// Get the maximum number of file descriptors that could be open.
rlimit r;
if (getrlimit(RLIMIT_NOFILE, &r) != 0)
{
abortOnError(forkPipeOut, InternalError.getrlimit, .errno);
}
immutable maxDescriptors = cast(int) r.rlim_cur;
import core.sys.posix.dirent : dirent, opendir, readdir, closedir, DIR;
import core.sys.posix.unistd : close;
import core.sys.posix.stdlib : atoi, malloc, free;

// The above, less stdin, stdout, and stderr
immutable maxToClose = maxDescriptors - 3;
// Missing druntime declaration
pragma(mangle, "dirfd")
extern(C) nothrow @nogc int dirfd(DIR* dir);

// Call poll() to see which ones are actually open:
auto pfds = cast(pollfd*) malloc(pollfd.sizeof * maxToClose);
if (pfds is null)
{
abortOnError(forkPipeOut, InternalError.malloc, .errno);
}
foreach (i; 0 .. maxToClose)
// Try to open the directory /dev/fd or /proc/self/fd
DIR* dir = opendir("/dev/fd");
if (dir is null) dir = opendir("/proc/self/fd");

// If we have a directory, close all file descriptors except stdin, stdout, stderr and forkPipeOut
if (dir)
{
pfds[i].fd = i + 3;
pfds[i].events = 0;
pfds[i].revents = 0;
scope(exit) closedir(dir);

int opendirfd = dirfd(dir);

// Iterate over all file descriptors
while(true)
{
dirent* entry = readdir(dir);

if (entry is null) break;
if (entry.d_name[0] == '.') continue;

int fd = atoi(cast(char*)entry.d_name);

// Don't close stdin, stdout, stderr, forkPipeOut or the directory file descriptor
if (fd < 3 || fd == forkPipeOut || fd == opendirfd) continue;

close(fd);
}
}
if (poll(pfds, maxToClose, 0) >= 0)
else // Fallback
{
// NOTE: malloc() and getrlimit() are not on the POSIX async
// signal safe functions list, but practically this should
// not be a problem. Java VM and CPython also use malloc()
// in its own implementation via opendir().
import core.stdc.stdlib : malloc;
import core.sys.posix.poll : pollfd, poll, POLLNVAL;
import core.sys.posix.sys.resource : rlimit, getrlimit, RLIMIT_NOFILE;

// Get the maximum number of file descriptors that could be open.
rlimit r;
if (getrlimit(RLIMIT_NOFILE, &r) != 0)
{
abortOnError(forkPipeOut, InternalError.getrlimit, .errno);
}
immutable maxDescriptors = cast(int) r.rlim_cur;

// The above, less stdin, stdout, and stderr
immutable maxToClose = maxDescriptors - 3;

// Call poll() to see which ones are actually open:
auto pfds = cast(pollfd*) malloc(pollfd.sizeof * maxToClose);
if (pfds is null)
{
abortOnError(forkPipeOut, InternalError.malloc, .errno);
}
foreach (i; 0 .. maxToClose)
{
// don't close pipe write end
if (pfds[i].fd == forkPipeOut) continue;
// POLLNVAL will be set if the file descriptor is invalid.
if (!(pfds[i].revents & POLLNVAL)) close(pfds[i].fd);
pfds[i].fd = i + 3;
pfds[i].events = 0;
pfds[i].revents = 0;
}
}
else
{
// Fall back to closing everything.
foreach (i; 3 .. maxDescriptors)
if (poll(pfds, maxToClose, 0) >= 0)
{
foreach (i; 0 .. maxToClose)
{
// don't close pipe write end
if (pfds[i].fd == forkPipeOut) continue;
// POLLNVAL will be set if the file descriptor is invalid.
if (!(pfds[i].revents & POLLNVAL)) close(pfds[i].fd);
}
}
else
{
if (i == forkPipeOut) continue;
close(i);
// Fall back to closing everything.
foreach (i; 3 .. maxDescriptors)
{
if (i == forkPipeOut) continue;
close(i);
}
}
}
}
Expand Down

0 comments on commit 53ccbae

Please sign in to comment.