Skip to content

Commit 9fe78db

Browse files
committed
Mitigation for spawn FD closing taking forever with a big FD limit
On systems with a large file descriptor limit, Mono takes a very long time to spawn a new process; with informal testing on the AIX CI builder, (with a POWER7) it took ~30 minutes. This is obviously very undesirable, but hand our is forced - libraries the user might be calling could be creating non-CLOEXEC files we're unaware of. As such, we started from the FD limit and worked our way down until we hit stdio. Using APIs such as posix_spawn aren't desirable as we do some fiddling with the child before we exec; and even then, closing FDs with posix_spawn relies on non-standard file actions like Solaris' addclosefrom not present on many systems. (All of this is unnecessary on Windows, of course.) This presents an alternative way (currently only implemented on AIX but with notes how for other platforms) to try to close the child's loose FDs before exec; by trying to get the highest number FD in use, then work our way down. In the event we can't, we simply fall back to the old logic. See mono#6555 for a discussion and the initial problem being mitigated.
1 parent d319f72 commit 9fe78db

File tree

1 file changed

+43
-1
lines changed

1 file changed

+43
-1
lines changed

mono/metadata/w32process-unix.c

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@
4747
#include <utime.h>
4848
#endif
4949

50+
// For max_fd_count
51+
#if defined (_AIX)
52+
#include <procinfo.h>
53+
#endif
54+
5055
#include <mono/metadata/object-internals.h>
5156
#include <mono/metadata/w32process.h>
5257
#include <mono/metadata/w32process-internals.h>
@@ -1594,6 +1599,43 @@ is_managed_binary (const char *filename)
15941599
return managed;
15951600
}
15961601

1602+
/**
1603+
* Gets the biggest numbered file descriptor for the current process; failing
1604+
* that, the system's file descriptor limit. This is called by the fork child
1605+
* in process_create.
1606+
*/
1607+
static inline guint32
1608+
max_fd_count (void)
1609+
{
1610+
#if defined (_AIX)
1611+
struct procentry64 pe;
1612+
pid_t p;
1613+
p = getpid (); // will be called by the child in process_create
1614+
// getprocs 3rd/4th arg is for getting the associated FDs for a proc,
1615+
// but all we need is just the biggest FD, which is in procentry64
1616+
if (getprocs64 (&pe, sizeof (pe), NULL, 0, &p, 1) != -1) {
1617+
mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_IO_LAYER_PROCESS,
1618+
"%s: maximum returned fd in child is %u",
1619+
__func__, pe.pi_maxofile);
1620+
return pe.pi_maxofile; // biggest + 1
1621+
}
1622+
// TODO: Other platforms.
1623+
// * On Linux, we can just walk /proc/self/fd? Perhaps in that approach,
1624+
// you can also just enumerate and close FDs that way instead?
1625+
// * On macOS, use proc_pidinfo + PROC_PIDLISTFDS? See:
1626+
// http://blog.palominolabs.com/2012/06/19/getting-the-files-being-used-by-a-process-on-mac-os-x/
1627+
// (I have no idea how this plays out on i/watch/tvOS.)
1628+
// * On the BSDs, there's likely a sysctl for this.
1629+
// * On Solaris, there exists posix_spawn_file_actions_addclosefrom_np,
1630+
// but that assumes we're using posix_spawn; we aren't, as we do some
1631+
// complex stuff between fork and exec. There's likely a way to get
1632+
// the FD list/count though (maybe look at addclosefrom source in
1633+
// illumos?) or just walk /proc/pid/fd like Linux?
1634+
#endif
1635+
// fallback to user/system limit if unsupported/error
1636+
return eg_getdtablesize ();
1637+
}
1638+
15971639
static gboolean
15981640
process_create (const gunichar2 *appname, const gunichar2 *cmdline,
15991641
const gunichar2 *cwd, StartupHandles *startup_handles, MonoW32ProcessInfo *process_info)
@@ -1976,7 +2018,7 @@ process_create (const gunichar2 *appname, const gunichar2 *cmdline,
19762018
dup2 (err_fd, 2);
19772019

19782020
/* Close all file descriptors */
1979-
for (i = eg_getdtablesize() - 1; i > 2; i--)
2021+
for (i = max_fd_count () - 1; i > 2; i--)
19802022
close (i);
19812023

19822024
#ifdef DEBUG_ENABLED

0 commit comments

Comments
 (0)