-
Notifications
You must be signed in to change notification settings - Fork 20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use posix_spawn on macOS 12 #47
base: master
Are you sure you want to change the base?
Conversation
The default macos-latest image is/was macOS 11, but vfork is deprecated in macOS 12. So we specify testing on both macOSes 11 and 12. Signed-off-by: Jesse Tov <[email protected]>
Mac OS 12 deprecates vfork and provides a richer posix_spawn than standard. So, if we're on mac OS >= 12 and posix_spawn is available, we use that. Signed-off-by: Jesse Tov <[email protected]>
|
||
#if defined(USE_POSIX_SPAWN) | ||
|
||
CAMLprim value spawn_unix(value v_env, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know if it makes sense in this case, especially since code has already been written, but:
we generally try to put logic on the OCaml side if we can.
In unix_spawn
we couldn't do that (while staying sane) since forking interacts poorly with OCaml runtime.
I'm thinking posix_spawn
doesn't share that problem, so we could move much of this code to OCaml, instead creating C bindings for the individual posix_spawnattr_*
functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting. Yeah, I see how that could be good. On the other hand, I could extend the blocking section around posix_spawn
to include all the related posix_spawn*
calls, if you think it's helpful to release the runtime system for longer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extending the blocking section is also a liability because you can't access the OCaml heap (or raise exceptions) from the blocking section. For that reason I'm not exactly excited about extending it. (although if some of those functions are known to be blocking/slow then that may be worth it)
} | ||
|
||
for (int fd = 0; fd < 3; fd++) { | ||
int tmp_fd = tmp_fds[fd] = safe_dup(info.std_fds[fd]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're dupping fds in the parent now, which I think resets close-on-exec, which means that any processes spawned concurrently (through our library or not) will inherit these fds, which seems sad.
I can't think of any way to do it race-free (dup3 doesn't seem to help because it requires us to know a free fd), but at least maybe we should set close-on-exec after dup?
(reading the doc of posix_spawn
, it seems it should be able to deal with close-on-exec fds?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Crazy idea how to do it race-free (sans error handling):
int dup_close_on_exec(int fd) {
int new_fd = open("/", O_CLOEXEC);
dup3(fd, new_fd, O_CLOEXEC);
return new_fd;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm. Not that crazy. What do you make of this?:
/* Same as [dup] but 1) ensures the result isn't a standard FD, 2) applies
[flags] (via dup3) to the new FD, and 3) on error, if [culprit] is non-null,
assigns to [*culprit] a string literal naming the function that failed.
*/
static int safe_dup(int fd, int flags, char const **culprit)
{
int new_fd = flags? open("/", O_CLOEXEC) : dup(fd);
if (new_fd < 0) {
if (culprit) {
*culprit = flags? "open" : "dup";
}
}
else if (new_fd < 3) {
int next = safe_dup(fd, flags, culprit);
close(new_fd);
new_fd = next;
}
else if (flags) {
new_fd = dup3(fd, new_fd, flags);
if (new_fd < 0 && culprit) {
*culprit = "dup3";
}
}
return new_fd;
}
This might be easier in OCaml, too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure this function benefitted from solving every problem at the same time. I would probably still keep at least the retry loop in a separate function. Anyway, I found that F_DUPFD_CLOEXEC
is a thing (a flag you can pass to fcntl
). Maybe that just works, and we don't need the ugly open
/dup3
trick?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall (other than inline comments):
- I think this cpp file is growing a bit too large. If you can think of a way to split it that'd be great.
- I don't understand your comment about error handling: how can
posix_spawn
possibly return good/better error messages if the only thing it returns is an int? - I'm curious about performance. Have you tried measuring it against a fork-based version?
By the way, does this function just work in linux? (at least when reviewing it I saw nothing that isn't documented in linux man pages) |
Some error cases that were raising an out-of-memory error now raise a specific unix error instead. Signed-off-by: Jesse Tov <[email protected]>
The unix error should name the actual function that failed, not the function that performs that same job in the [v]fork-based implementation. Signed-off-by: Jesse Tov <[email protected]>
When fchdir fails, there's no arg in the unix_error, since fchdir takes a file descriptor, but the arg field is for holding a string. So don't put that there. Signed-off-by: Jesse Tov <[email protected]>
Signed-off-by: Jesse Tov <[email protected]>
We were traversing the sigprocmask argument too late, after entering the blocking section. Now we traverse the argument earlier, when considering the other arguments, and only do the actual sigmask operations in the blocking section. This costs an extra system call (to pthread_sigmask) in the non-posix_spawn case, because previously the code was using a single pthread_sigmask call to both get the current sigmask and block all signals. Now we use it twice: before the blocking section to get the current mask, and then inside the blocking section to block all signals. Signed-off-by: Jesse Tov <[email protected]>
Yeah, I can do that.
Linux's
Not yet, no.
Linux |
I'm also wondering about user-visible API questions. The current code in this PR doesn't respect the
I was trying to keep this API as simple as a conservative extension as possible, because without knowing about use cases, it's hard to think about how else it might be used. |
It looks like |
This doesn't work yet. Signed-off-by: Jesse Tov <[email protected]>
Oh well, it's unfortunate that it doesn't work well on Linux. I think we just shouldn't enable |
Yeah, I just discovered F_DUPFD_CLOEXEC as well, because MacOS doesn't have
dup3. And apparently fcntl(F_DUPFD_CLOEXEC, ...) is better anyway, assuming
we don't need to support Linux < 2.6.
J
…On Fri, Jul 29, 2022 at 12:19 PM Arseniy Alekseyev ***@***.***> wrote:
***@***.**** commented on this pull request.
------------------------------
In src/spawn_stubs.c
<#47 (comment)>:
> + e_arg = Field(v_cwd, 0);
+ goto cleanup;
+ }
+ break;
+ case FD:
+ e_error = posix_spawn_file_actions_addfchdir_np(&actions, info.cwd.fd);
+ if (e_error) {
+ e_function = "fchdir";
+ e_arg = Field(v_cwd, 0);
+ goto cleanup;
+ }
+ break;
+ }
+
+ for (int fd = 0; fd < 3; fd++) {
+ int tmp_fd = tmp_fds[fd] = safe_dup(info.std_fds[fd]);
I'm not sure this function benefitted from solving every problem at the
same time. I would probably still keep at least the retry loop in a
separate function. Anyway, I found that F_DUPFD_CLOEXEC is a thing (a
flag you can pass to fcntl). Maybe that just works, and we don't need the
ugly open/dup3 trick?
—
Reply to this email directly, view it on GitHub
<#47 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAFS3BHNUGXIK7F4VXXQGP3VWQABRANCNFSM54WVD6ZA>
.
You are receiving this because you authored the thread.Message ID:
***@***.***>
--
Jesse Tov
he/him/his
Jane Street
VES, 4th Floor, 4D3A6
New York, NY 10281
tel: +1 (646) 908-5576
|
It's also broken on macos :) |
On mac, there seems to be a workaround for the bug around doing chdir with the posix_spawn api libuv/libuv#3257 We could use |
(picked from janestreet#47) Signed-off-by: Jesse Tov <[email protected]> Signed-off-by: Rudi Grinberg <[email protected]>
vfork is deprecated on macOS 12. This PR uses posix_spawn on macOS 12 instead. (The standard posix_spawn does insufficient error reporting for our API, but the Mac posix_spawn's errors are more informative.)