forked from electron/electron
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Spawning child processes in an Electron application with a hardened runtime has become slow in macOS Big Sur. This patch is a squashed version of libuv/libuv#3064, with the addition of API availability annotations to fix a build warning (since Electron compiles with the `-Wunguarded-availability-new` flag). This patch should be removed when libuv PR 3064 is merged. Fixes: libuv/libuv#3050 Fixes: electron#26143 PR-URL: libuv/libuv#3064 Authored-by: Juan Pablo Canepa <[email protected]> Co-authored-by: Marcello Bastéa-Forte <[email protected]> Electron patch prepared by: Pat DeSantis <[email protected]>
- Loading branch information
Showing
2 changed files
with
315 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,314 @@ | ||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 | ||
From: Pat DeSantis <[email protected]> | ||
Date: Tue, 15 Dec 2020 13:28:07 -0500 | ||
Subject: macOS,libuv: use posix_spawn | ||
|
||
Spawning child processes in an Electron application with a hardened runtime has become slow in macOS Big Sur. See the following issues for more details: | ||
https://github.com/libuv/libuv/issues/3050 | ||
https://github.com/electron/electron/issues/26143 | ||
|
||
This patch is a squashed version of https://github.com/libuv/libuv/pull/3064, with the addition of API availability annotations to fix a build warning (since Electron compiles with the `-Wunguarded-availability-new` flag). This patch should be removed when libuv PR 3064 is merged. | ||
|
||
diff --git a/deps/uv/src/unix/darwin-stub.h b/deps/uv/src/unix/darwin-stub.h | ||
index 433e3efa73079e0fe68dcfdf67c3911723326b72..7b95436e3d60f2acda22ab5bacc2a745e8752faf 100644 | ||
--- a/deps/uv/src/unix/darwin-stub.h | ||
+++ b/deps/uv/src/unix/darwin-stub.h | ||
@@ -23,6 +23,7 @@ | ||
#define UV_DARWIN_STUB_H_ | ||
|
||
#include <stdint.h> | ||
+#include <spawn.h> | ||
|
||
struct CFArrayCallBacks; | ||
struct CFRunLoopSourceContext; | ||
@@ -110,4 +111,9 @@ static const int kFSEventStreamEventFlagRootChanged = 32; | ||
static const int kFSEventStreamEventFlagUnmount = 128; | ||
static const int kFSEventStreamEventFlagUserDropped = 2; | ||
|
||
+/* Copied from https://opensource.apple.com/source/xnu/xnu-6153.101.6/libsyscall/wrappers/spawn/spawn_private.h.auto.html */ | ||
+int posix_spawnattr_set_uid_np(const posix_spawnattr_t*, uid_t) __API_AVAILABLE(macos(10.15), ios(13.0), tvos(13.0), watchos(6.0)); | ||
+int posix_spawnattr_set_gid_np(const posix_spawnattr_t*, gid_t) __API_AVAILABLE(macos(10.15), ios(13.0), tvos(13.0), watchos(6.0)); | ||
+int posix_spawnattr_set_groups_np(const posix_spawnattr_t*, int, gid_t*, uid_t) __API_AVAILABLE(macos(10.15), ios(13.0), tvos(13.0), watchos(6.0)); | ||
+ | ||
#endif /* UV_DARWIN_STUB_H_ */ | ||
diff --git a/deps/uv/src/unix/process.c b/deps/uv/src/unix/process.c | ||
index b021aaeba87d0b466341f40a016ef69e8beb7543..1525619866a779e4d4619b93ecbd357354f6b017 100644 | ||
--- a/deps/uv/src/unix/process.c | ||
+++ b/deps/uv/src/unix/process.c | ||
@@ -34,8 +34,11 @@ | ||
#include <poll.h> | ||
|
||
#if defined(__APPLE__) && !TARGET_OS_IPHONE | ||
+#include <spawn.h> | ||
+#include <sys/kauth.h> | ||
# include <crt_externs.h> | ||
# define environ (*_NSGetEnviron()) | ||
+#include "darwin-stub.h" | ||
#else | ||
extern char **environ; | ||
#endif | ||
@@ -404,6 +407,239 @@ static void uv__process_child_init(const uv_process_options_t* options, | ||
} | ||
#endif | ||
|
||
+#if defined(__APPLE__) | ||
+int uv__spawn_set_posix_spawn_attrs(posix_spawnattr_t* attrs, | ||
+ const uv_process_options_t* options) __API_AVAILABLE(macos(10.15), ios(13.0), tvos(13.0), watchos(6.0)) | ||
+{ | ||
+ int err; | ||
+ unsigned int flags; | ||
+ sigset_t signal_set; | ||
+ | ||
+ err = posix_spawnattr_init(attrs); | ||
+ if (err != 0) { | ||
+ /* If initialization fails, no need to de-init, just return */ | ||
+ return err; | ||
+ } | ||
+ | ||
+ if (options->flags & UV_PROCESS_SETUID) { | ||
+ err = posix_spawnattr_set_uid_np(attrs, options->uid); | ||
+ if (err != 0) | ||
+ goto error; | ||
+ } | ||
+ | ||
+ if (options->flags & UV_PROCESS_SETGID) { | ||
+ err = posix_spawnattr_set_gid_np(attrs, options->gid); | ||
+ if (err != 0) | ||
+ goto error; | ||
+ } | ||
+ | ||
+ if (options->flags & (UV_PROCESS_SETUID | UV_PROCESS_SETGID)) { | ||
+ /* See the comment on the call to setgroups in uv__process_child_init above | ||
+ * for why this is not a fatal error */ | ||
+ SAVE_ERRNO(posix_spawnattr_set_groups_np(attrs, 0, NULL, KAUTH_UID_NONE)); | ||
+ } | ||
+ | ||
+ /* Set flags for spawn behavior | ||
+ * 1) POSIX_SPAWN_CLOEXEC_DEFAULT: (Apple Extension) All descriptors in | ||
+ * the parent will be treated as if they had been created with O_CLOEXEC. | ||
+ * The only fds that will be passed on to the child are those manipulated | ||
+ * by the file actions | ||
+ * 2) POSIX_SPAWN_SETSIGDEF: Signals mentioned in spawn-sigdefault in | ||
+ * the spawn attributes will be reset to behave as their default | ||
+ * 3) POSIX_SPAWN_SETSIGMASK: Signal mask will be set to the value of | ||
+ * spawn-sigmask in attributes | ||
+ * 4) POSIX_SPAWN_SETSID: Make the process a new session leader if a | ||
+ * detached session was requested. */ | ||
+ flags = POSIX_SPAWN_CLOEXEC_DEFAULT | | ||
+ POSIX_SPAWN_SETSIGDEF | | ||
+ POSIX_SPAWN_SETSIGMASK; | ||
+ if (options->flags & UV_PROCESS_DETACHED) | ||
+ flags |= POSIX_SPAWN_SETSID; | ||
+ err = posix_spawnattr_setflags(attrs, flags); | ||
+ if (err != 0) | ||
+ goto error; | ||
+ | ||
+ /* Reset all signal the child to their default behavior */ | ||
+ sigfillset(&signal_set); | ||
+ err = posix_spawnattr_setsigdefault(attrs, &signal_set); | ||
+ if (err != 0) | ||
+ goto error; | ||
+ | ||
+ /* Reset the signal mask for all signals */ | ||
+ sigemptyset(&signal_set); | ||
+ err = posix_spawnattr_setsigmask(attrs, &signal_set); | ||
+ if (err != 0) | ||
+ goto error; | ||
+ | ||
+ return err; | ||
+ | ||
+error: | ||
+ (void) posix_spawnattr_destroy(attrs); | ||
+ return err; | ||
+} | ||
+ | ||
+int uv__spawn_set_posix_spawn_file_actions(posix_spawn_file_actions_t* actions, | ||
+ const uv_process_options_t* options, | ||
+ int stdio_count, | ||
+ int (*pipes)[2]) __API_AVAILABLE(macos(10.15), ios(13.0), tvos(13.0), watchos(6.0)) | ||
+{ | ||
+ int fd; | ||
+ int err; | ||
+ | ||
+ err = posix_spawn_file_actions_init(actions); | ||
+ if (err != 0) { | ||
+ /* If initialization fails, no need to de-init, just return */ | ||
+ return err; | ||
+ } | ||
+ | ||
+ /* Set the current working directory if requested */ | ||
+ if (options->cwd != NULL) { | ||
+ err = posix_spawn_file_actions_addchdir_np(actions, options->cwd); | ||
+ if (err != 0) | ||
+ goto error; | ||
+ } | ||
+ | ||
+ /* First, dupe any required fd into orbit, out of the range of | ||
+ * the descriptors that should be mapped in. */ | ||
+ for(fd = 0 ; fd < stdio_count; ++fd) { | ||
+ if (pipes[fd][1] < 0) | ||
+ continue; | ||
+ | ||
+ err = posix_spawn_file_actions_adddup2(actions, pipes[fd][1], stdio_count + fd); | ||
+ if (err != 0) | ||
+ goto error; | ||
+ } | ||
+ | ||
+ /* Second, move the descriptors into their respective places */ | ||
+ for(fd = 0 ; fd < stdio_count; ++fd) { | ||
+ if (pipes[fd][1] < 0) | ||
+ continue; | ||
+ | ||
+ err = posix_spawn_file_actions_adddup2(actions, stdio_count + fd, fd); | ||
+ if (err != 0) | ||
+ goto error; | ||
+ } | ||
+ | ||
+ /* Finally, close all the superfluous descriptors */ | ||
+ for(fd = 0; fd < stdio_count; ++fd) { | ||
+ if (pipes[fd][1] < 0) | ||
+ continue; | ||
+ | ||
+ err = posix_spawn_file_actions_addclose(actions, stdio_count + fd); | ||
+ if (err != 0) | ||
+ goto error; | ||
+ } | ||
+ | ||
+ /* Finally process the standard streams as per de documentation */ | ||
+ for(fd = 0 ; fd < 3 ; ++fd) { | ||
+ /* If ignored, open as /dev/null */ | ||
+ const int oflags = fd == 0 ? O_RDONLY : O_RDWR; | ||
+ const int mode = 0; | ||
+ | ||
+ if (pipes[fd][1] != -1) | ||
+ continue; | ||
+ | ||
+ err = posix_spawn_file_actions_addopen(actions, fd, "/dev/null", oflags, mode); | ||
+ if (err != 0) | ||
+ goto error; | ||
+ } | ||
+ | ||
+ return err; | ||
+ | ||
+error: | ||
+ (void) posix_spawn_file_actions_destroy(actions); | ||
+ return err; | ||
+} | ||
+ | ||
+int uv__spawn_and_init_child_posix_spawn(const uv_process_options_t* options, | ||
+ int stdio_count, | ||
+ int (*pipes)[2], | ||
+ pid_t* pid) __API_AVAILABLE(macos(10.15), ios(13.0), tvos(13.0), watchos(6.0)) { | ||
+ int err; | ||
+ posix_spawnattr_t attrs; | ||
+ posix_spawn_file_actions_t actions; | ||
+ | ||
+ err = uv__spawn_set_posix_spawn_attrs(&attrs, options); | ||
+ if (err != 0) | ||
+ goto error; | ||
+ | ||
+ err = uv__spawn_set_posix_spawn_file_actions(&actions, options, stdio_count, pipes); | ||
+ if (err != 0) { | ||
+ (void) posix_spawnattr_destroy(&attrs); | ||
+ goto error; | ||
+ } | ||
+ | ||
+ /* Preserve parent environment if not explicitly set */ | ||
+ char** env = options->env ? options->env : environ; | ||
+ | ||
+ /* Spawn the child */ | ||
+ err = posix_spawnp(pid, options->file, &actions, &attrs, options->args, env); | ||
+ | ||
+ /* Destroy the actions/attributes */ | ||
+ (void) posix_spawn_file_actions_destroy(&actions); | ||
+ (void) posix_spawnattr_destroy(&attrs); | ||
+ | ||
+error: | ||
+ /* In an error situation, the attributes and file actions are | ||
+ * already destroyed, only the happy path requires cleanup */ | ||
+ return UV__ERR(err); | ||
+} | ||
+#endif | ||
+ | ||
+int uv__spawn_and_init_child_fork(const uv_process_options_t* options, | ||
+ int stdio_count, | ||
+ int (*pipes)[2], | ||
+ int error_fd, | ||
+ pid_t* pid) { | ||
+ *pid = fork(); | ||
+ | ||
+ if (*pid == -1) { | ||
+ /* Failed to fork */ | ||
+ return UV__ERR(errno); | ||
+ } | ||
+ | ||
+ if (*pid == 0) { | ||
+ /* Fork succeeded, in the child process */ | ||
+ uv__process_child_init(options, stdio_count, pipes, error_fd); | ||
+ abort(); | ||
+ } | ||
+ | ||
+ /* Fork succeeded, in the parent process */ | ||
+ return 0; | ||
+} | ||
+ | ||
+int uv__spawn_and_init_child(const uv_process_options_t* options, | ||
+ int stdio_count, | ||
+ int (*pipes)[2], | ||
+ int error_fd, | ||
+ pid_t* pid) { | ||
+ | ||
+#if defined(__APPLE__) | ||
+ if (__builtin_available(macOS 10.15, *)) { | ||
+ /* Especial child process spawn case for macOS Big Sur (11.0) onwards | ||
+ * | ||
+ * Big Sur introduced a significant performance degradation on a call to | ||
+ * fork/exec when the process has many pages mmaped in with MAP_JIT, like, say | ||
+ * a javascript interpreter. Electron-based applications, for example, | ||
+ * are impacted; though the magnitude of the impact depends on how much the | ||
+ * app relies on subprocesses. | ||
+ * | ||
+ * On macOS, though, posix_spawn is implemented in a way that does not | ||
+ * exhibit the problem. This block implements the forking and preparation | ||
+ * logic with poxis_spawn and its related primitves. It also takes advantage of | ||
+ * the macOS extension POSIX_SPAWN_CLOEXEC_DEFAULT that makes impossible to | ||
+ * leak descriptors to the child process. | ||
+ * | ||
+ * see https://github.com/libuv/libuv/issues/3050 | ||
+ */ | ||
+ return uv__spawn_and_init_child_posix_spawn(options, stdio_count, pipes, pid); | ||
+ } else { | ||
+#endif | ||
+ return uv__spawn_and_init_child_fork(options, stdio_count, pipes, error_fd, pid); | ||
+#if defined(__APPLE__) | ||
+ } | ||
+#endif | ||
+} | ||
|
||
int uv_spawn(uv_loop_t* loop, | ||
uv_process_t* process, | ||
@@ -486,21 +722,16 @@ int uv_spawn(uv_loop_t* loop, | ||
|
||
/* Acquire write lock to prevent opening new fds in worker threads */ | ||
uv_rwlock_wrlock(&loop->cloexec_lock); | ||
- pid = fork(); | ||
|
||
- if (pid == -1) { | ||
- err = UV__ERR(errno); | ||
+ /* Spawn the child */ | ||
+ err = uv__spawn_and_init_child(options, stdio_count, pipes, signal_pipe[1], &pid); | ||
+ if (err != 0) { | ||
uv_rwlock_wrunlock(&loop->cloexec_lock); | ||
uv__close(signal_pipe[0]); | ||
uv__close(signal_pipe[1]); | ||
goto error; | ||
} | ||
|
||
- if (pid == 0) { | ||
- uv__process_child_init(options, stdio_count, pipes, signal_pipe[1]); | ||
- abort(); | ||
- } | ||
- | ||
/* Release lock in parent process */ | ||
uv_rwlock_wrunlock(&loop->cloexec_lock); | ||
uv__close(signal_pipe[1]); |