Skip to content
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

set SO_NOSIGPIPE on apple platforms #36426

Closed
wants to merge 0 commits into from
Closed

Conversation

kali
Copy link
Contributor

@kali kali commented Sep 12, 2016

While integrating a Rust library in an iOS application, an app developper will typically run the application in the simulator with lldb. In this setup, when the rust library hit a EPIPE, SIGPIPE is also sent to the process, and stops execution in lldb. And this happens often.

Handling the SIGPIPE at the process level with signal or sigaction does not help here: lldb will intercept the signal and interrupt the execution regardless the signal handling.

On iOS and MacOS, SIGPIPE can be disabled at the socket level with setsockopt. Other platforms have other ways to do it (linux use a flag on send()).

It looks worse than it really is, but it's a major annoyance in our project, xcode making it very cumbersome to alter this particular lldb behavior. Also, it tends to freak out iOS developers, and this is certainly not what we want :)

@rust-highfive
Copy link
Collaborator

Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @alexcrichton (or someone else) soon.

If any changes to this PR are deemed necessary, please add them as extra commits. This ensures that the reviewer can see what has changed since they last reviewed the code. Due to the way GitHub handles out-of-date commits, this should also make it reasonably obvious what issues have or haven't been addressed. Large or tricky changes may require several passes of review and changes.

Please see the contribution instructions for more information.

@nagisa
Copy link
Member

nagisa commented Sep 12, 2016

I’m not sure about modifying the standard library for that. You can set signal handing mode in debugger:

process handle --pass true --stop false --notify false SIGPIPE

or shorter

proc hand -p true -s false -n false SIGPIPE

You can also put such commands in your lldb configuration file (usually ~/.lldbinit) so they are executed by default on every debugging session.

@kali
Copy link
Contributor Author

kali commented Sep 13, 2016

Yes, the "process handle" thing works, but is very cumbersome to work with and gets in the way. Xcode runs lldb as a library and apparently ignores both .lldbinit and .lldbinit-Xcode.

The only way we could find to get the "process handle" command to work relies on a hackish breakpoint on the main function: http://stackoverflow.com/a/10456557/172537 . This has to be done on every developper workstation. But even then, this is cumbersome: whenever one disable breakpoints (and this is a frequent use case in debugging apps, as you often have to do manually put the app in the state you're about to debug), you'll also disable the main() breakpoint and trip over SIGPIPE again.

Also, there is something that does not feel right in having these signals thrown to the process every time a socket is closed (or so), just to be ignored. It has to cost something. The rust standard library ignores them anyway (which to be honest, feels a bit wrong too: what if a non-rust library relies on them somewhere else in the process ?).

@alexcrichton
Copy link
Member

Thanks for the PR @kali! As I'm sure you're already aware, the standard library already ignores SIGPIPE for any Rust program. Just to make sure we're on the same page, though, that doesn't work for iOS? I think when searching around that's what it looked like...

My next question would be whether your application controls these sockets or whether the sockets are created by the standard library? This does seem like it's a bit of a heavyweight solution, but we could certainly add a set_ignore_sigpipe Unix-specific method to the TCP and UDP sockets if necessary for this kind of functionality.

@kali
Copy link
Contributor Author

kali commented Sep 13, 2016

Hi @alexcrichton! Thanks for your interest in this.

I'm aware rust stdlib makes a signal(SIGPIPE, SIG_IGN). It does work as specified, including on iOS: this does not suppress the signal, it handles it by ignoring it. We have no problem in "production" mode (release build on an actual phone) with or without the proposed patch in stdlib. It is a just a major annoyance for people in charge of developing the iOS application that uses my rust library: using lldb is the "natural workflow" set by xcode, and lldb catches the SIGPIPE before the SIG_IGN has a chance to ignore it. As a result, the app developer workflow is completely disrupted. There is a workaround, but it still very cumbersome.

In my case, the sockets are actually created and controller by hyper. I guess a set_ignore_sigpipe() should work, if I can get a patch in hyper. Note that if we want to have something consistent on linux, we would need to piggyback our flag on the socket struct to know if we should set the MSG_NOSIGNAL at each and every call on send().

I must say I'm not sure I see any real problem with my approach, and I also kind of think it's better in the long run... Signal are not free, why would we "pay" for this signal to be actually emitted more or less regularly on typical network operations just to get ignored by a process wide handler, when the libc offers a clean way to solve the question at its root ?

Rust stdlib has already decided that SIGPIPE was obsolete anyway... I would honestly go for setting both SO_NOSIGPIPE on ios and macos, as proposed, and setting MSG_NOSIGNAL on linux (and everywhere it's supported) as well. I'm ready to do that if you think it's preferable.

@alexcrichton
Copy link
Member

lldb catches the SIGPIPE before the SIG_IGN has a chance to ignore it

Jeez, that is indeed quite annoying!

Out of curiosity, does basically all socket programming on iOS set this option? Surely this would be a major annoyance for anyone doing anything network related on iOS?

In my case, the sockets are actually created and controller by hyper.

Ah that's unfortunate! But also a very good point about how you don't always control all your sockets.

I must say I'm not sure I see any real problem with my approach, and I also kind of think it's better in the long run... Signal are not free, why would we "pay" for this signal to be actually emitted more or less regularly on typical network operations just to get ignored by a process wide handler, when the libc offers a clean way to solve the question at its root ?

I think this approach is fine as well, but to land we'd probably want consistency across all platforms and apply this to Linux as well (e.g. MSG_NOSIGNAL). I would imagine that SIG_IGN does not incur a performance penalty when the socket would otherwise generate a SIGPIPE though, but it's true that we may not always be in a context where SIGPIPE is set to SIG_IGN.

@kali want to prototype what MSG_NOSIGNAL would look like? I'd be in favor of merging w/ that (and wouldn't mind considering still merging w/o it as well)

@alexcrichton alexcrichton added the T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. label Sep 26, 2016
@kali
Copy link
Contributor Author

kali commented Sep 27, 2016

Here is a minimalistic fix setting MSG_NOSIGNAL on linux.

As on apple platforms, I could manually check the issue before the patch, and it is gone with it. That said I'm not sure if it can be unit-tested easily.

We may want to move the constant definition to libc, but I'm not sure about that : it would save the double definition, but on the other hand exposing the constant seems useless.

#[cfg(target_os = "linux")]
const MSG_NOSIGNAL: c_int = 0x4000;
#[cfg(not(target_os = "linux"))]
const MSG_NOSIGNAL: c_int = 0x0; // unused dummy value
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could these be added to liblibc so we can verify their values as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, sure, I'll do that.

@@ -221,11 +226,12 @@ impl TcpStream {

pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
let len = cmp::min(buf.len(), <wrlen_t>::max_value() as usize) as wrlen_t;
let flags = if cfg!(target_os = "linux") { MSG_NOSIGNAL } else { 0 };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could just always be MSG_NOSIGNAL, right, b/c the #[cfg] is above?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes indeed. I was was just trying to emulate https://github.com/rust-lang/rust/blob/master/src/libstd/sys/unix/net.rs#L26 .

You prefer to pass MSG_NOSIGNAL all the time ? I guess we can dispense with the let flags and put it straight in the send call in that case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh passing it there sometimes and not others is actually somewhat unrelated. Here yeah we should always pass MSG_NOSIGNAL

bors added a commit to rust-lang/libc that referenced this pull request Sep 28, 2016
@alexcrichton
Copy link
Member

Looks good to me! Want to rebase and update the libc module with the merge commit?

@kali kali closed this Sep 29, 2016
bors added a commit that referenced this pull request Oct 1, 2016
SO_NOSIGPIPE and MSG_NOSIGNAL (rebased #36426)

I'm not sure what happened when I pushed a rebased branch on #36426 , github closed it...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants