The graceful shutdown stuff is good, but always piping the output of child processes is not necessarily the right thing to do. Some processes need stdin (what if it's a shell?) and some processes will be checking if stdout is a tty. What you should do (and Rust doesn't make this easy) is allocate a new pty for your child processes if your own stdout is a tty. Some programs default to this (eg: ssh), others make it configurable (eg: docker).
You're also missing the standard techniques for managing and reaping your children, which I don't see mentioned. You don't need to maintain a registry of child processes for example, at least on Linux there are a few things you can do for this without any global state (PR_SET_PDEATHSIG, PR_SET_CHILD_SUBREAPER, PID namespaces). On MacOS you can write a routine to reap children like a linux init() process would. The registry approach is also fragile: what if library code spawns children?
Also, if the terminal is in raw mode then you'll never get ctrl+C. This is really about signal handling. You also can't gracefully shutdown if you get a SIGKILL, which is why PR_SET_PDEATHSIG and PID namespaces are very nice - they guarantee descendants get killed.
>Also, if the terminal is in raw mode then you'll never get ctrl+C.
The process/thread/task won't receive SIGINT, true. But I believe it will see the character ETX (ASCII 3). Programs that use raw mode input need to do their own keystroke processing.
Handling SIGINT (ctrl-c) with child processes is tricky, but a more pervasive problem for Rust CLI programs is handling SIGPIPE. For historical reasons the compiler adds a signal handler before calling main() which ignores SIGPIPE. It means when you pipe your Rust CLI program's output to something like head, instead of being killed by the signal sent when head closes the pipe, you get a write error and usually print an error message instead of silently dying. You can match on the type of write error and skip printing a message when it's from a broken pipe, but a more subtle problem is that the shell sets the exit status of a program killed by a signal to 128 + the signal number, so 141 in the case of a broken pipe. You can emulate this behaviour by checking for a broken pipe and explicitly exiting with a 141 status code, but it's not possible to fully reproduce being killed by a signal. There's been an issue to make this configurable (the latest proposal is via a compiler flag) for years.
I’ve definitely seen all of these problems in Rust programs but they certainly aren’t limited to Rust programs. I do think it would be nice if Rust libraries were a bit more misuse-resistant when it came to preserving a coherent terminal.
I also long for a more misuse-resistant terminal but that seems like a bigger problem.
I believe the real fun is when doing this on Windows, because it doesn't use Unix signals (and generally speaking you only get the equivalent of SIGKILL but not SIGTERM, but you can opt into ~SIGINT). I was hoping this would actually deal with that…
Child processes are created using, generally, 2 syscalls: fork, then exec. When you fork, all file descriptors the main process has open are copied, and are now open in two places. Then, when the child calls exec (to transform itself into the target program), all file descriptors stay open in the new process (unless a specific fd is explicitly configured otherwise, FD_CLOEXEC).
Standard output are just file descriptors with the number 0, 1, and 2, and you can use the dup2 syscall to assign those numbers to some pipes that you originally created before you fork. Now the standard output of your child process is going to those pipes in your parent process. Or you can close those file descriptors, which will prevent the child process from reading/writing them at all. Or you can do nothing, and the copied file descriptors from the parent still apply.
Conceptually, you think of "spawning a child" as something that is in some kind of container (the parent process), but the underlying mechanics are not like this at all, and processes don't actually exist in a "tree", they just happen to keep a record of their "parent process ID" so the OS knows who to notify when the process dies.
I believe I am saying child processes can write to stdout as the main process is shutting down. Also, if the child processes are not shut down properly and are left dangling, and the child processes were set up as 'inherit' to be able to write directly to stdout/stderr then yes.
When you use ctrl+c, you are not killing the program, you are sending it a SIGTERM signal which essentially means « could you please stop yourself ? » so the program have a chance to clean things before exiting.
kill -9 is sending a SIGKILL signal which, well, kills the program immediately.
I hate to be the guy, but I could barely see the code snippets. Is contrast an issue for anyone else? Reader mode improves thins slightly but at the cost of code being unhighlighted and wrapping like crazy.
The highlighting is clearly designed for a dark background but has been given a light background in light mode. Change the bg-neutral-100 to bg-neutral-900 and it’s fine—still not magnificent, but fine.
(But as for barely… if you don’t run JS, then you just don’t see the code snippets, because for some inscrutable reason, unlike the rest of the document, they’re only rendered client-side.)
Yes, the contrast of the code examples is not great. Grey on grey, light pastels and orange does not combine into an easy-to-read color palette for me.
I have never undestood why garden path sentences are interesting. They can always be rewritten to make more obvious sense. Its like saying theres a way to take any sentence and remove some words to make its intended meaning confusing. I dont personally find this interesting, just frustrating that the author didnt take more time to use punctuation or more words to convey explicit meaning.
Fearless concurrency with Rust unless you are worried about lifecycle management, threads/co-operation and general ergonomics. Even modern c++ might be better at this (gasp!) with std::jthread
Are there any languages that provide for or care about lifecycle management across address space boundaries? After fork() you're usually fucked and need explicit controls.
Ok, but I am literally getting filtered by installing C++ libraries. I haven't accomplished anything in the last 5 days other than determine that the previous libraries are not usable.
duped|7 months ago
You're also missing the standard techniques for managing and reaping your children, which I don't see mentioned. You don't need to maintain a registry of child processes for example, at least on Linux there are a few things you can do for this without any global state (PR_SET_PDEATHSIG, PR_SET_CHILD_SUBREAPER, PID namespaces). On MacOS you can write a routine to reap children like a linux init() process would. The registry approach is also fragile: what if library code spawns children?
Also, if the terminal is in raw mode then you'll never get ctrl+C. This is really about signal handling. You also can't gracefully shutdown if you get a SIGKILL, which is why PR_SET_PDEATHSIG and PID namespaces are very nice - they guarantee descendants get killed.
wpollock|7 months ago
The process/thread/task won't receive SIGINT, true. But I believe it will see the character ETX (ASCII 3). Programs that use raw mode input need to do their own keystroke processing.
mmastrac|7 months ago
mprovost|7 months ago
aidenn0|7 months ago
1: https://oils.pub/release/latest/doc/ysh-tour.html
anp|7 months ago
I also long for a more misuse-resistant terminal but that seems like a bigger problem.
vlovich123|7 months ago
stonogo|7 months ago
alt227|7 months ago
mook|7 months ago
koakuma-chan|7 months ago
CGamesPlay|7 months ago
Standard output are just file descriptors with the number 0, 1, and 2, and you can use the dup2 syscall to assign those numbers to some pipes that you originally created before you fork. Now the standard output of your child process is going to those pipes in your parent process. Or you can close those file descriptors, which will prevent the child process from reading/writing them at all. Or you can do nothing, and the copied file descriptors from the parent still apply.
Conceptually, you think of "spawning a child" as something that is in some kind of container (the parent process), but the underlying mechanics are not like this at all, and processes don't actually exist in a "tree", they just happen to keep a record of their "parent process ID" so the OS knows who to notify when the process dies.
duped|7 months ago
Callicles|7 months ago
unknown|7 months ago
[deleted]
silon42|7 months ago
pjerem|7 months ago
kill -9 is sending a SIGKILL signal which, well, kills the program immediately.
rendaw|7 months ago
chrismorgan|7 months ago
(But as for barely… if you don’t run JS, then you just don’t see the code snippets, because for some inscrutable reason, unlike the rest of the document, they’re only rendered client-side.)
dxdm|7 months ago
burnt-resistor|7 months ago
unknown|7 months ago
[deleted]
windowshopping|7 months ago
impish9208|7 months ago
alt227|7 months ago
abnercoimbre|7 months ago
oldpersonintx2|7 months ago
[deleted]
npalli|7 months ago
duped|7 months ago
dwattttt|7 months ago
> Unlike non-scoped threads, scoped threads can borrow non-'static data, as the scope guarantees all threads will be joined at the end of the scope.
> All threads spawned within the scope that haven’t been manually joined will be automatically joined before this function returns.
imtringued|7 months ago
psyclobe|7 months ago
jeffbee|7 months ago