Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upReset signal behavior before starting children with std::process #25784
Conversation
rust-highfive
assigned
alexcrichton
May 25, 2015
geofft
force-pushed the
geofft:subprocess-signal-masks
branch
2 times, most recently
from
3617b67
to
15d62f6
May 25, 2015
pnkfelix
added
the
T-libs
label
May 26, 2015
alexcrichton
reviewed
May 26, 2015
| @@ -143,138 +143,6 @@ mod imp { | |||
| pub unsafe fn drop_handler(handler: &mut Handler) { | |||
| munmap(handler._data, SIGSTKSZ); | |||
| } | |||
|
|
|||
This comment has been minimized.
This comment has been minimized.
alexcrichton
May 26, 2015
Member
Oh dear, thanks for the eagle eyes here! Glad to see this duplication reduced.
alexcrichton
reviewed
May 26, 2015
| c::pthread_sigmask(c::SIG_SETMASK, &set, ptr::null_mut()) != 0 || | ||
| c::signal(libc::SIGPIPE, c::SIG_DFL) == c::SIG_ERR { | ||
| fail(&mut output); | ||
| } |
This comment has been minimized.
This comment has been minimized.
alexcrichton
May 26, 2015
Member
Aha! So back in 33a2191 I was cleaning out some old code in the unix process spawning code, and I removed a call to rust_unset_sigprocmask because there were no comments as to why it existed and I also had no idea why it existed. I think that this explains it, however!
To clarify, however, I've got a few questions:
- The old implementation used
sigprocmaskinstead ofpthread_sigmask, is there a reason that you opted to use the pthread version here instead? At this point there's only one thread, so there shouldn't be any multithreading worries. - The old implementation also did nothing with
SIGPIPEor other signals, so I'm somewhat wary here of explicitly setting it back to the default behavior. I can see how because our runtime explicitly resets this away from the default that it may be the only one we explicitly reset back, however, so I think I'm OK with that.
This comment has been minimized.
This comment has been minimized.
alexcrichton
May 26, 2015
Member
Also, according to the execve manpage:
Signals set to be ignored in the calling process are set to be ignored
in the new process. Signals which are set to be caught in the calling
process image are set to default action in the new process image.
Blocked signals remain blocked regardless of changes to the signal
action. The signal stack is reset to be undefined (see sigaction(2)
for more information).
Could you clarify in this comment that specifically the ignore behavior of SIGPIPE is the only part that's inherited? Any registered handlers will be reset and we're manually unmasking signals, so the only thing that's inherited is ignored signals.
This comment has been minimized.
This comment has been minimized.
geofft
May 26, 2015
Author
Contributor
The old implementation used
sigprocmaskinstead ofpthread_sigmask
Yeah, either is fine here. pthread_sigmask is a strict superset of sigprocmask: it's defined to do the same thing if the process is single-threaded, and has well-defined behavior if the process is multithreaded, so I've just been making myself forget that sigprocmask exists. It's also one fewer thing to bind, which also reduces the temptation for a future commit to call it in a maybe-multithreaded context (e.g., if you wanted to mask signals around the call to fork, as libc system does). I guess there's an argument that it requires -lpthread, so if we expect to have UNIXish ports where we aren't using pthreads (???) we'd prefer sigprocmask, plus a warning comment about threads.
The old implementation also did nothing with
SIGPIPEor other signals
Yes, I think it's libstd's responsibility to unset any ignored-signal dispositions it sets, if that disposition is for internal use. SIGPIPE is the only one that's ignored; the other two signals libstd touches get handlers. (As it happens, I think there's also an argument that SIGPIPE is the only one it ever makes sense to ignore-but-not-inherit, since it's the only one with a corresponding EPIPE. If you're ignoring stuff like SIGHUP, you probably want the rest of your process group to ignore them too.)
Ignoring SIGPIPE is relatively recent (#13158, March 2014). The old sigprocmask code is much older (ea5dc54, March 2011), and Rust was not setting any signal handlers at that time.
Could you clarify in this comment
Just the antecedent of "both of these"? I intend it as saying "SIGPIPE being ignored, and any signal mask", but I can rework that comment to be clearer, sure.
This comment has been minimized.
This comment has been minimized.
alexcrichton
May 26, 2015
Member
pthread_sigmask is a strict superset of sigprocmask: it's defined to do the same thing if the process is single-threaded, and has well-defined behavior if the process is multithreaded, so I've just been making myself forget that sigprocmask exists. It's also one fewer thing to bind, which also reduces the temptation for a future commit to call it in a maybe-multithreaded context (e.g., if you wanted to mask signals around the call to fork, as libc system does).
Sounds good to me
Just the antecedent of "both of these"?
Ah I think I just didn't reread after learning more about what's going on here, I think it's fine.
So just to check I poked around at some other libraries to see what they do. In libuv at least, nothing with signals is handled in terms of forking, but they also don't by default disable SIGPIPE. I couldn't make heads or tails of what Ruby does, but it didn't look like Python did much with signal masks or forking despite ignoring SIGPIPE like we are.
Given this information, it strikes me as somewhat odd that we're resetting the signal mask? Shouldn't that be up to the application instead of the library here instead? For example this makes it so it's impossible to spawn a process with a signal mask in place through the Command API. This info also makes me uneasy about ignoring SIGPIPE by default, but I think that setting it to SIG_DFL here is fine.
This comment has been minimized.
This comment has been minimized.
|
This is some fascinating investigation @geofft, very nice work! |
This comment has been minimized.
This comment has been minimized.
libuv appears to be processing signals via signal handlers. I can look in more detail if you think it's interesting, but if they're doing handlers instead of a thread and asking library users to ignore
Seems to get this right:
Python 3 (at least 3.4) appears to have fixed this relative to Python 2.7:
Yeah, I do actually mostly agree: there is definitely a use case for intentionally starting a child with ignored signals, e.g., writing If you're worried about the regression possibility with signal masks (although the docs don't promise anything either way, and I can't imagine any users will ever care, let alone have cared), we could set things up to reset |
This comment has been minimized.
This comment has been minimized.
|
Yeah I think the change here to reset Perhaps a test could be added at least which requires procmask to be reset on spawn? I think it's fine to do, but it'd be nice to have a regression test to ensure it's not accidentally removed as well! |
geofft
force-pushed the
geofft:subprocess-signal-masks
branch
from
8c0641c
to
8e2e0ee
May 28, 2015
This comment has been minimized.
This comment has been minimized.
|
OK, I've added a Is this ready for merge? Are you happy with the changes to the bindings? |
This comment has been minimized.
This comment has been minimized.
|
Looks good to me, thanks @geofft! Can you also tag the test with |
This comment has been minimized.
This comment has been minimized.
|
Doesn't its presence in libstd/sys/unix/ make that implicit, since that path is only brought in by libstd/lib.rs's |
This comment has been minimized.
This comment has been minimized.
bors
added a commit
that referenced
this pull request
May 28, 2015
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
|
This comment has been minimized.
This comment has been minimized.
|
Oops, fixed. Can I test-build Android without having a local Android dev environment? |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
bors
added a commit
that referenced
this pull request
May 28, 2015
This comment has been minimized.
This comment has been minimized.
|
|
This comment has been minimized.
This comment has been minimized.
|
Bah, I think I should go and set up build toolchains for all platforms. A |
This comment has been minimized.
This comment has been minimized.
|
Ah feel free to throw up just a blanket |
This comment has been minimized.
This comment has been minimized.
|
@bors: r+ 9a676f1 |
This comment has been minimized.
This comment has been minimized.
Can you file a bug for this so we remember to look at it later? |
This comment has been minimized.
This comment has been minimized.
|
|
bors
added a commit
that referenced
this pull request
May 29, 2015
geofft
referenced this pull request
May 29, 2015
Open
Enable segfault / bus error handlers on more UNIX platforms #25872
This comment has been minimized.
This comment has been minimized.
|
|
geofft
force-pushed the
geofft:subprocess-signal-masks
branch
2 times, most recently
from
558a438
to
2a93dca
Jun 20, 2015
This comment has been minimized.
This comment has been minimized.
|
OK, I've gotten this passing against Android API level 18 locally, and rebased against current HEAD. The big problem was that, until Android API level 21 (introduced in Android 5.0), several of the signal-handling functions were only exposed as C inline functions: see Android 4.4's Also, my run-pass test case wasn't working on Android for lack of either |
geofft
referenced this pull request
Jun 20, 2015
Closed
SIGSEGV and SIGBUS cause an exit signal of SIGILL #26458
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
|
This comment has been minimized.
This comment has been minimized.
|
|
geofft
force-pushed the
geofft:subprocess-signal-masks
branch
from
2a93dca
to
3a3d864
Jun 21, 2015
This comment has been minimized.
This comment has been minimized.
|
@bors r=alexcrichton |
This comment has been minimized.
This comment has been minimized.
|
|
This comment has been minimized.
This comment has been minimized.
bors
added a commit
that referenced
this pull request
Jun 21, 2015
This comment has been minimized.
This comment has been minimized.
|
|
This comment has been minimized.
This comment has been minimized.
|
Hrrm. I don't think this test failure is related to my code, despite being on Android again.
Seems related to #26192, but the Android tests passed there, so I'm confused. |
alexcrichton
reviewed
Jun 22, 2015
| c::pthread_sigmask(c::SIG_SETMASK, &set, ptr::null_mut()) != 0 || | ||
| libc::funcs::posix01::signal::signal( | ||
| libc::SIGPIPE, mem::transmute(c::SIG_DFL) | ||
| ) == mem::transmute(c::SIG_ERR) { |
This comment has been minimized.
This comment has been minimized.
alexcrichton
Jun 22, 2015
Member
Could this use as foo instead of mem::transmute? The transmute may be a bit of a heavy hammer for this operation.
This comment has been minimized.
This comment has been minimized.
geofft
Jun 22, 2015
Author
Contributor
Transmute checks size, and SIG_ERR is ~0. Really the problem is that liblibc and c.rs have different sighandler_t types. c.rs uses *mut c_void since that's what we need for struct sigaction (well, really a function pointer); liblibc calls it a size_t, which is probably the same but I didn't want to assume that. Ideally we should synchronize these.
(Actually what I really want is an extended nullable-pointer optimization where I can say that 0, 1, and ~0 are invalid and force an enum {SIG_DFL = 0, SIG_IGN = 1, SIG_ERR = ~0, handler(extern fn(c_int)) to be represented as a single pointer, but I assume that's super hard.)
This comment has been minimized.
This comment has been minimized.
alexcrichton
Jun 23, 2015
Member
Wow that definitely sounds like a mess, this already landed so it's fine for now, but I may do a drive-by fix at some point to just cast these both to a usize and compare that (not critical at all though)
This comment has been minimized.
This comment has been minimized.
|
Ah unfortunately that constant is unstable currently, and it just looks like you deleted the usage of it, so you may be able to delete the associated |
geofft
added some commits
May 22, 2015
geofft
force-pushed the
geofft:subprocess-signal-masks
branch
from
3a3d864
to
a8dbb92
Jun 22, 2015
This comment has been minimized.
This comment has been minimized.
|
@bors r=alexcrichton |
This comment has been minimized.
This comment has been minimized.
|
|
geofft commentedMay 25, 2015
UNIX specifies that signal dispositions and masks get inherited to child processes, but in general, programs are not very robust to being started with non-default signal dispositions or to signals being blocked. For example, libstd sets
SIGPIPEto be ignored, on the grounds that Rust code using libstd will get theEPIPEerrno and handle it correctly. But shell pipelines are built around the assumption thatSIGPIPEwill have its default behavior of killing the process, so that things likeheadwork:Here,
headis supposed to terminate the input process quietly, but the bash subshell has inherited the ignored disposition ofSIGPIPEfrom its Rust grandparent process. So it gets a bunch ofEPIPEs that it doesn't know what to do with, and treats it as a generic, transient error. You can see similar behavior withfind / | head,yes | head, etc.This PR resets Rust's
SIGPIPEhandler, as well as any signal mask that may have been set, before spawning a child. Setting a signal mask, and then using a dedicated thread or something likesignalfdto dequeue signals, is one of two reasonable ways for a library to process signals. See carllerche/mio#16 for more discussion about this approach to signal handling and why it needs a change tostd::process. The other approach is for the library to set a signal-handling function (signal()/sigaction()): in that case, dispositions are reset to the default behavior on exec (since the function pointer isn't valid across exec), so we don't have to care about that here.As part of this PR, I noticed that we had two somewhat-overlapping sets of bindings to signal functionality in
libstd. One dated to old-IO and probably the old runtime, and was mostly unused. The other is currently used bystack_overflow.rs. I consolidated the two bindings into one set, and double-checked them by hand against all supported platforms' headers. This probably means it's safe to enablestack_overflow.rson more targets, but I'm not including such a change in this PR.r? @alexcrichton
cc @Zoxc for changes to
stack_overflow.rs