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

Document (in?)ability to exit a program #19245

Closed
zslayton opened this issue Nov 23, 2014 · 18 comments
Closed

Document (in?)ability to exit a program #19245

zslayton opened this issue Nov 23, 2014 · 18 comments

Comments

@zslayton
Copy link
Contributor

Rust doesn't seem to provide a means of cleanly exiting the process apart from programmatically ending all of one's tasks and then allowing the main task to run to completion. Some folks are turning to std::libc::exit as an alternative, which @chris-morgan has pointed out might have undesirable consequences.

This feature is common in many other languages:

  • exit() in C
  • System.exit() in Java
  • sys.exit() in Python
  • os.Exit() in Go

It may be worthwhile to document why it is impractical for Rust to provide it as well.

@ben0x539
Copy link
Contributor

I think Chris Morgan is overstating how problematic libc::exit is and I think it ought to be exposed in std::os or somewhere, not just as a C binding. I don't think we usually consider resource leaks a safety issue and exiting early without doing further cleanup seems fairly common in C/C++ where it's not really any cleaner than it would be in Rust.

@ben0x539
Copy link
Contributor

(I'd bet the lack of exit in the standard library dates back to when people thought Rust was going to be a lot more Erlang-like than it actually ended up being.)

@chris-morgan
Copy link
Member

@ben0x539 I don’t think I overstated it—the fact that buffered writers will not flush and so data loss may ensue is sufficient for me to consider the libc exit approach catastrophic in the general case. Rust values correctness; libc::exit simply makes a mess of that. That function needs massive warnings on it about what it can do; merely marking it unsafe is necessary but not sufficient. (It doesn’t even have docs!)

@ben0x539
Copy link
Contributor

Rust values safety, I don't think it is particularly preoccupied with correctness.

None of the libc bindings really have docs, but then I suppose the manual pages that came with my linux installation don't really spell out that exit does not flush non-libc buffers either.

@reem
Copy link
Contributor

reem commented Nov 24, 2014

I agree with @chris-morgan - libc::exit at least needs to be documented explaining that it can cause significant correctness problems if not used correctly.

@ben0x539 I disagree strongly that Rust doesn't care about correctness - much of the emphasis on safety is to make it easier to write correct programs.

@quantheory
Copy link
Contributor

A few thoughts:

  • I don't have a very clear, strong motivating example for this in my head yet.

    • "Catastrophic" errors (see RFC 236) should generally use panic!.
    • "Obstructions" should be propagated upward toward code that can decide what to do about them.
    • "Normal" exiting, like in the Stack Overflow question, usually only happens in a few places in a program. Because normal termination doesn't happen that often, it shouldn't usually be that difficult to pass information up to main by returning bool or Option from any function that wants to signal a need to exit.

    So panic! is the correct call when exiting due to errors, and I have trouble thinking of a situation where it would be particularly hard or tedious for a program to return up to main if it completes with no errors. (I'm not saying that there's not such a situation, but I can't picture one right now.)

  • Calling libc::exit is potentially more dangerous in Rust than in C/C++.

    • C at least flushes/closes the standard library's streams and runs atexit functions.
    • In C++, you also have destructors run on thread-local and static objects.

    While not as safe as stack unwinding, there is of course a better chance of successful cleanup in both cases compared to just immediately terminating the process. I don't know that any of the above is done by Rust right now. If not, then calling libc::exit in Rust is more like calling std::terminate in C++.

@zslayton
Copy link
Contributor Author

So panic! is the correct call when exiting due to errors ....

A minor gripe I have with panic! as the standard means of exiting in the event of an error is that I don't have control over the output that it produces. It will always take the form:

task '

' panicked at ''

I'd like a way to exit without the boilerplate.

@thestinger
Copy link
Contributor

@chris-morgan: It hardly needs massive warnings. It's not any different than going out-of-stack (abort), out-of-memory (abort or OOM killer), a power outage and so on. The code already needs to be robust against failures like this by making use of transactions. Leaking resources before exit is not a safety issue and is not wrong either.

@chris-morgan
Copy link
Member

@thestinger: I think there’s still a significant difference, one of intent; those are known problems that nothing much can be done about and which are truly exceptional circumstances, though I quite agree with you that making software robust against spontaneous failure is an important thing. But in this case we’re talking about a snippet of code deliberately invoked that, based on people’s experiences in other languages, may not do what they expect. It is the factor of expectation that clinches this one, in my opinion.

@quantheory
Copy link
Contributor

A minor gripe I have with panic! as the standard means of exiting in the event of an error is that I don't have control over the output that it produces.

True. Maybe there should be a more advanced macro that's even more general than the formatted form of panic, e.g. passing the the file/line information to a user-defined method and letting it figure out the final message/logging. (Of course you can just call std::rt::begin_unwind yourself, or do so in your own macro, but I don't know when if ever begin_unwind will be stable.)

At the same time, my understanding is that panic! is mostly for situations where the error is most likely due to a bug in the program (or likely-unrecoverable cases like out-of-memory errors), which ideally shouldn't be seen much by users. Errors having to do with I/O, communication, permissions, etc., which are more likely to be user-visible, and which the user is more likely to be able to actually do something about, are supposed to be propagated upward by returning Result, not by exiting/aborting/panicking from wherever you encounter them.

(Admittedly, I'm not sure how well that will end up. It requires a lot less language complexity and opportunity for mistakes than if an exception mechanism was added, but I imagine that a lot of codes will have a ton of try! and Result all over them.)

It hardly needs massive warnings.

Eh, I guess "massive" is a matter of opinion. If it was moved/reexported outside of libc, it would certainly need some warning just to note that, unlike other ways you can exit, it does no cleanup. But it's a less important point if it isn't moved out of libc, and I still don't see why it should be.

If you really must abort now, you should probably use panic! (or some other thing like unwind_and_die! could be created if panic! turns out not to be general enough). If you really, deliberately want to exit without doing so, you can always go find the function in libc. But I don't know why we would advertise this function, since it isn't really what people will generally want, and it potentially violates invariants that (except in cases like your power outage) could otherwise be enforced by the destructors.

those are known problems that nothing much can be done about [... b]ut in this case we’re talking about a snippet of code deliberately invoked

This.

@thestinger
Copy link
Contributor

extern crate test;

fn abort() -> ! {
    test::black_box(&());
    abort()
}

fn main() {
    abort();
}

@thestinger
Copy link
Contributor

struct Foo;

impl Drop for Foo {
    fn drop(&mut self) {
        panic!()
    }
}

fn abort() -> ! {
    let _x = Foo;
    panic!()
}

fn main() {
    abort();
}

@thestinger
Copy link
Contributor

extern crate test;

fn abort() -> ! {
    let xs = Vec::from_elem(std::uint::MAX, 1u8);
    test::black_box(&xs);
    loop {}
}

fn main() {
    abort();
}

@thestinger
Copy link
Contributor

@chris-morgan: Rust is going to be switching to detached threads for spawn. The program will exit as soon as you fall off main and you'll need to explicitly synchronize if you want it to block until other tasks are done their work. The alternative is the overhead of extra synchronization and a mandatory hosted runtime incompatible with running code on threads managed by the C standard library. Are you against removing the runtime?

@thestinger
Copy link
Contributor

It's also easy to exit by raising a signal. There was some discussion about this, and the reference manual now states that raising / sending signals is not unsafe based on the consensus.

@quantheory
Copy link
Contributor

I see the point about safety; I would say that these cases are not unsafe in the Rust sense, but are still to be avoided in most cases (e.g. panicking in a destructor, or killing the process when panic! would work fine). A program that leaves an "acceptable" state when suddenly terminated, might still exit much more gracefully when it has the chance to unwind the stack. It's not a binary choice.

But I'm worried that we're starting to drift off-topic. I think that the questions to be resolved are:

  • Do we have a need for another standard exit or abort macro/function? (Meaning one that's in some important way different from panic!.)
  • If so, what would be the best function for the job?
  • If not (or it's just not a priority right now), should there be documentation somewhere pointing out that panic! is the only "standard" method for early termination? (Well, that and the other macros that use panic! internally.)

@thestinger
Copy link
Contributor

panic! doesn't terminate the process, it terminates the task and the error isn't automatically bubbled up in any way. It's just completely ignored and the process keeps running by default. It doesn't let you set the exit status and it writes to stderr before it can be handled.

@quantheory
Copy link
Contributor

Oops, missed this reply.

panic! doesn't terminate the process, it terminates the task and the error isn't automatically bubbled up in any way. It's just completely ignored and the process keeps running by default.

OK. There are three ways I can think of to deal with that:

  1. Add a new macro that's similar to panic! except that it kills the process after unwinding.
  2. For this purpose, promote libc::exit, which AFAIK just dies with no cleanup (except if doing I/O in a way that ultimately uses libc as the backend).
  3. Add something like std::exit in C++, where static/thread-local destructors are run, but there is no unwinding. To be frank I'm not sure why C++ picked this particular compromise, so I won't defend it myself, but I figured it worth mentioning.

It doesn't let you set the exit status and it writes to stderr before it can be handled.

It's not clear to me that either of these justifies a separate macro rather than changing/enhancing panic!. panic! in a task other than the master is a recoverable error, so you could argue that it shouldn't unconditionally write to stderr anyway.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants