LWN: Comments on "This is why we can't have safe cancellation points" https://lwn.net/Articles/683118/ This is a special feed containing comments posted to the individual LWN article titled "This is why we can't have safe cancellation points". en-us Sat, 30 Aug 2025 18:46:56 +0000 Sat, 30 Aug 2025 18:46:56 +0000 https://www.rssboard.org/rss-specification lwn@lwn.net This is why we can't have safe cancellation points https://lwn.net/Articles/684960/ https://lwn.net/Articles/684960/ nix <blockquote> Arguably dup2 could correctly be ignored--I have a hard time imagining a failure condition where dup'ing a descriptor over an already open stdio descriptor could fail, though that does depend on some assumptions and it's not something I would do anyhow. </blockquote> A brief glance at do_dup2() in the kernel (or, for that matter, at a sufficiently recent manpage) reveals that it can fail with -EBUSY if the file descriptor it's being asked to dup over is still being opened, so (just as with -EINTR) a retry loop would be needed for perfect safety in this situation. Mon, 25 Apr 2016 14:39:58 +0000 This is why we can't have safe cancellation points https://lwn.net/Articles/684532/ https://lwn.net/Articles/684532/ wahern <div class="FormattedComment"> You're completely right: posix_spawn is optional (part of the Spawn extension) and fork is not. I even had the standard open but didn't bother confirming what I asserted. Shameful....<br> <p> Regarding error checking: routines like posix_spawn_file_actions_addclose can fail with ENOMEM, and AFAICT both the glibc and musl implementations allocate memory even on the first add--musl for each individual action, glibc for 8 actions. Because allocation can fail even on Linux with OOM (e.g. policy-based resource limits), regardless of allocation size, correct code needs to check for failure on each individual descriptor action added to the queue. So posix_spawn requires the same number of error checks.<br> <p> OTOH, even the most pedantic of developers could choose to ignore errors from close() (or posix_close() if and when <a href="http://austingroupbugs.net/view.php?id=529">http://austingroupbugs.net/view.php?id=529</a> is adopted) in the child process. Apropos this article, you no longer need to worry about close being a thread cancellation point in the child, and blocking all signals is easy, so EINTR won't happen. And EBADF shouldn't happen in correctly written software.[1] Alternatively, you could choose to set FD_CLOEXEC, which doesn't even have an EINTR failure mode. Arguably dup2 could correctly be ignored--I have a hard time imagining a failure condition where dup'ing a descriptor over an already open stdio descriptor could fail, though that does depend on some assumptions and it's not something I would do anyhow.<br> <p> Point being, explicit fork+exec could in some situations take less code than posix_spawn because you could elide some error checks. And I can't imagine a situation where it could take appreciably more code.<br> <p> More importantly, though, is the point that posix_spawn doesn't solve threading race conditions. The only possibly plus in this regard is that posix_spawn will correctly block signals during the operation so that, e.g., a signal handler isn't wrongly called in the child but before exec.[2] Conspicuously missing, on the other hand, is the ability to set the umask in the child process. Setting or even querying the umask simply can't be done in a race-free manner in a threaded application, unless no other thread relies on the umask, or if you fork and report back the umask.<br> <p> While there's nothing intrinsically wrong with using posix_spawn, it shouldn't be used for the wrong reasons. You still have to carefully consider the important stuff.<br> <p> [1] EBADF invariably means you have a bug in your application, often a thread race or in single-threaded non-blocking I/O code an ordering issue. I refuse to ignore EBADF in my event loop and polling libraries (unlike libevent and similar libraries) despite people complaining to me how annoying it is to propagate it. Such a bug could easily lead to stalled network I/O. I'm convinced it's is a very common problem in non-blocking I/O networking daemons, but that its rare enough that people chaulk it up to network hiccups. So I propagate EBADF when manipulating a descriptor event because it's not the library's prerogative to hide such an error, and it can't possibly know whether the error is benign, recoverable, or panic-worthy. Though as with ENOMEM, library state remains consistent after the error so that recovery isn't foreclosed.<br> <p> [2] pthread_sigmask has no failure mode when used correctly, so it's just two lines of condition-less code when using fork+exec. Though I learned a few years ago over on comp.unix.programmer that one should initialize a sigset_t object with sigemptyset before passing as the _output_ argument to pthread_sigmask and similar routines. Some implementations will logical-OR the signal set, rather than writing over the entire sigset_t object. See also <a href="http://pubs.opengroup.org/onlinepubs/9699919799/functions/sigismember.html">http://pubs.opengroup.org/onlinepubs/9699919799/functions...</a>. I admit this is one case where using posix_spawn has a clear benefit over fork+exec. I just don't think that in the grand scheme of things it amounts to much. Descriptor leakages and umask races, for example, are arguably far-and-above the bigger problem, especially from a security perspective, and posix_spawn provides no benefit and in some cases is more limited.<br> <p> </div> Thu, 21 Apr 2016 05:13:10 +0000 This is why we can't have safe cancellation points https://lwn.net/Articles/684464/ https://lwn.net/Articles/684464/ nix <div class="FormattedComment"> Hm, no, but the L2+ caches are unified on many models (e.g. on all the Intel x86-64 CPUs I have access to, Nehalem and later), and getting stuff from L2 cache is still immensely faster than getting it from RAM, fast enough that you can often consider it free for applications like this.<br> </div> Wed, 20 Apr 2016 16:56:04 +0000 This is why we can't have safe cancellation points https://lwn.net/Articles/684307/ https://lwn.net/Articles/684307/ mjthayer <div class="FormattedComment"> Another potentially daft idea: have the signal handler do nothing but set the cancel flag, but also have a flag to say when the thread is in a critical code section, that is, about to start a cancellable system call, already (or nearly already) checked the cancel flag and not yet (or only just) exited the system call. Then pthread_cancel() in the library just keeps resending the signal until the critical section flag is cleared again. Not nice, but not much cost for people who don't need cancellation.<br> </div> Tue, 19 Apr 2016 14:40:54 +0000 This is why we can't have safe cancellation points https://lwn.net/Articles/684244/ https://lwn.net/Articles/684244/ quotemstr <div class="FormattedComment"> Ah, now if only musl supported dlclose, by the same logic. dlclose is also one of those routines that's perfectly safe when you understand how to use it. Felker just doesn't like it.<br> </div> Mon, 18 Apr 2016 17:11:29 +0000 This is why we can't have safe cancellation points https://lwn.net/Articles/684243/ https://lwn.net/Articles/684243/ quotemstr <div class="FormattedComment"> Thank you for dispelling some of the incorrect received wisdom one sees circulating around programming communities. Signals are not magic. Fork is not magic. All of these things are just powerful tools that become useful when understood. It really is a wonder anything works at all when people program with the kind of incorrect models you rail against.<br> </div> Mon, 18 Apr 2016 17:10:25 +0000 This is why we can't have safe cancellation points https://lwn.net/Articles/684178/ https://lwn.net/Articles/684178/ itvirta <div class="FormattedComment"> <font class="QuotedText">&gt; And yes, you do have one sequence, it's extremely stereotyped, and it'll always be in the icache</font><br> <p> Stupid question: Does the instruction cache help if you're reading the instruction bytes as data?<br> <p> </div> Mon, 18 Apr 2016 11:10:01 +0000 This is why we can't have safe cancellation points https://lwn.net/Articles/684171/ https://lwn.net/Articles/684171/ nix <div class="FormattedComment"> Oh, I assumed you were necessarily taking the cost of the cancellable test anyway (the branch is near-zero cost in the common case, because it obviously has a prediction hint). Were you trying to avoid even that?<br> </div> Mon, 18 Apr 2016 10:28:30 +0000 This is why we can't have safe cancellation points https://lwn.net/Articles/684169/ https://lwn.net/Articles/684169/ nix <div class="FormattedComment"> Good point. And yes, you do have one sequence, it's extremely stereotyped, and it'll always be in the icache (at syscall entry, anyway). (On x86-64 -- on x86-32, this is irrelevant, because there, even the 'inlined' syscalls (INTERNAL_SYSCALL users) are still doing a GOT lookup and an indirect jump (on x86-32, anyway) to the vDSO syscall entry point.)<br> <p> If you can get away with scanning for this only when cancellation is actually detected, it seems that the cost would be very low, though the complexity would obviously be higher than a simple address comparison, and it would tie that part of the kernel to these fairly fine and arch-dependent details of glibc's implementation, in a way that would probably not be spotted fast if it broke :(<br> </div> Mon, 18 Apr 2016 10:27:15 +0000 This is why we can't have safe cancellation points https://lwn.net/Articles/684159/ https://lwn.net/Articles/684159/ neilbrown <div class="FormattedComment"> <font class="QuotedText">&gt; you don't have one address to compare to any more</font><br> <p> No, but you probably have one sequence of op-codes to compare.<br> The comparisons might be a little more complex than "memcmp" but could you not test "is this EIP value within a thunk" by comparing surrounding bytes against the standard thunk at each of the (very few) possible offsets?<br> </div> Mon, 18 Apr 2016 04:20:20 +0000 This is why we can't have safe cancellation points https://lwn.net/Articles/684149/ https://lwn.net/Articles/684149/ luto <div class="FormattedComment"> I think you could do it the way the kernel does the exception table -- just make a sorted list of pairs of starts and ends of cancellable regions. You only need to check it when your cancellation signal is delivered, and the data cache impact of *that* is basically irrelevant.<br> <p> But you could do it by flipping the default if you're willing to accept a branch: just test the cancellable flag and jump out of line if needed. This is no worse than the existing musl thing in which each cancellable syscall needs to test the cancallable flag anyway to see if it needs to cancel even without a signal being sent.<br> </div> Mon, 18 Apr 2016 00:21:03 +0000 This is why we can't have safe cancellation points https://lwn.net/Articles/684143/ https://lwn.net/Articles/684143/ nix <div class="FormattedComment"> Hmmm. I see now -- when HJ's sycall inlining does turn up, or anything like it, you don't have one address to compare to any more, you have a great heap of them all across glibc, and there is obviously no way to do any similar comparison (I can think of ridiculously overdesigned ways to do it involving searching a tree of address ranges, but they'd all be *far* too slow and blow the dcache sky-high: just no).<br> <p> Given that syscall inlining isn't something you can possibly turn on and off at runtime -- the inlining is, after all, into glibc, so you'd need multiple copies of glibc via hwcaps, which seems total overkill for this and would totally negate any saving via massive icache bloat -- you'd not be able to fix this by changing a *default*. You'd need to basically give up on fixing this race, or give up on fixing it this way, or break cancellation completely for everyone (a total non-starter).<br> <p> Hmm. Too late at night, but I'll think on this. Either I have a niggling germ of a possible idea for a fix for this at the edge of my brain, or I'm just tired and hallucinating. (Or both!)<br> <p> </div> Sun, 17 Apr 2016 23:12:47 +0000 This is why we can't have safe cancellation points https://lwn.net/Articles/684033/ https://lwn.net/Articles/684033/ ballombe <div class="FormattedComment"> <font class="QuotedText">&gt; This code that supposedly works fine only works fine if it never calls into libraries, since those libraries might use malloc, or open, or mutexes, or anything else that acquires resources.</font><br> <p> Yes, so ? This is a static property of the code.<br> </div> Sat, 16 Apr 2016 13:52:26 +0000 This is why we can't have safe cancellation points https://lwn.net/Articles/684040/ https://lwn.net/Articles/684040/ luto <div class="FormattedComment"> It prevents syscall inlining. The impact is small but nonzero.<br> </div> Fri, 15 Apr 2016 20:34:21 +0000 This is why we can't have safe cancellation points https://lwn.net/Articles/684038/ https://lwn.net/Articles/684038/ nix <div class="FormattedComment"> A performance benefit? What performance cost is there to a single address comparison in a relatively rare path? (And as for complexity cost, well, hell, that sort of backward-compatibility burden is why systems get more complex over time, but that doesn't mean we can cavalierly throw users over the wall. codesearch.debian.net shows quite a lot of users, and yes, many of them are real users. :) )<br> <p> (Now I'd agree that *asynchronous* cancellation is nearly impossible to program to and has an even smaller use case than synchronous cancellation, but even *it* is useful sometimes, particularly as a transient thing; e.g. when a thread that otherwise is synchronously cancellable is doing a long-running computation that it knows does no syscalls and can be safely unwound from the cleanup handler.)<br> </div> Fri, 15 Apr 2016 20:19:16 +0000 This is why we can't have safe cancellation points https://lwn.net/Articles/684032/ https://lwn.net/Articles/684032/ luto <div class="FormattedComment"> Agreed. I'm not saying that changing the default is actually wise. But it might be enough of a simplification and a performance benefit to make it worthwhile.<br> </div> Fri, 15 Apr 2016 19:49:50 +0000 This is why we can't have safe cancellation points https://lwn.net/Articles/684028/ https://lwn.net/Articles/684028/ nix <div class="FormattedComment"> But this breaks all programs that assume it is enabled by default and then proceed to call pthread_cancel() on their own threads.<br> <p> Changing longstanding defaults like this constitutes a break of userspace. You need a new -D flag (which, perhaps, sets a new ELF note, or simply triggers the linking in of a new crt1.o which flips the default) to ensure that this only happens to programs that are prepared for it.<br> </div> Fri, 15 Apr 2016 19:40:28 +0000 This is why we can't have safe cancellation points https://lwn.net/Articles/684025/ https://lwn.net/Articles/684025/ luto <div class="FormattedComment"> This code that supposedly works fine only works fine if it never calls into libraries, since those libraries might use malloc, or open, or mutexes, or anything else that acquires resources.<br> <p> pthread cancellation is very dangerous, is useful only for specialized cases, and IMO should never have been enabled by default.<br> <p> If it were simply disabled by default, then this performance issue would be irrelevant.<br> </div> Fri, 15 Apr 2016 19:16:27 +0000 This is why we can't have safe cancellation points https://lwn.net/Articles/683903/ https://lwn.net/Articles/683903/ ballombe <div class="FormattedComment"> There are lot of programs using cancellation without mixing them with syscall and they work fine.<br> The trick is to have the parent allocate and free all resources in advance, and use robust<br> data structures like stacks. <br> If C++ is broken, do not use it for thread.<br> <p> The fact that something is broken in some corner case does not make is useless in other case.<br> </div> Fri, 15 Apr 2016 19:07:24 +0000 This is why we can't have safe cancellation points https://lwn.net/Articles/684008/ https://lwn.net/Articles/684008/ pm215 <div class="FormattedComment"> A lot of this post sounded remarkably familiar, because it turns out that QEMU's linux-user code (where it emulates a binary for one architecture on a host with a different architecture, passing system calls through to the host) needs to do a very similar trick with a signal handler that has to look at the interrupted PC to see whether it was just before or just after the syscall instruction.<br> <p> (For QEMU the problem that has to be solved is making sure that incoming signals interrupt emulated guest system calls -- if the signal arrives before we execute the host syscall instruction we must abandon emulation of the guest syscall, otherwise we might block forever. There's no way to close the race window completely without having the signal handler check the PC to see "did we actually execute that instruction yet?".)<br> <p> </div> Fri, 15 Apr 2016 17:21:07 +0000 This is why we can't have safe cancellation points https://lwn.net/Articles/684007/ https://lwn.net/Articles/684007/ nix <div class="FormattedComment"> Quite. The program I work on for my day job does precisely that, with subsidiary threads whose primary job is ptrace()ing and waitpid()ing, but which may be commanded to go away by a controlling 'master' thread with many more jobs. All the work of such subsidiary threads is associated with a single structure relating to a specific subprocess under monitoring, and it is easy (and good style) to make sure that this structure is properly freeable at all times (you need that for decent error handling anyway). The cleanup handler then just needs to do the same 'shut down and free everything associated with the process structure' that we have to do when the subprocess dies anyway (indeed, we simply call the cleanup handler by hand in that situation).<br> <p> I've had problems with the multithreading in that code, but they were all races associated with mutexes and condition variables. The nature of synchronous cancellation has caused me zero problems.<br> <p> </div> Fri, 15 Apr 2016 17:17:29 +0000 This is why we can't have safe cancellation points https://lwn.net/Articles/683984/ https://lwn.net/Articles/683984/ khim <blockquote><font class="QuotedText">Many applications never cancel any threads. They are irrelevant.</font></blockquote> <p>99.9% of all programs (and I've picked conservative number) are irrelevant? That's novel idea to me.</p> <blockquote><font class="QuotedText">They need do nothing and they suffer no cost (maybe a couple of instructions per syscall. If you can't afford that, hand-code your systemcalls).</font></blockquote> <p>When proportion is <b>this</b> skewed even these two instructions make no sense: why should 99.9% of all the apps suffer at all if this could be avoided?</p> <p>The natural response would: because I could just take bits and pieces from these 99.9% apps and use these to build these rare few apps which <b>do</b> use cancellation.</p> <p>But as you've shown you couldn't just take random working code from working library, plug it in a program which uses cancellation and hope that the end result would work. <p>ALL code must be carefully designed in such a program. And if ALL code is specifically written for such a program then additional burden of adding couple of pthread_setcancelstate calls here and there wouldn't be large at all!</p> <p>The argument that "hey, I don't know where and how threads are created in this large program" wouldn't fly: if you don't know even that much about your program/library/whatever then how could you be sure that you control is enough to even try to attempt to use cancellation of threads?</p> <blockquote><font class="QuotedText">It just needs a little care - like not logging any messages between the allocation and pushing the cleanup handler.</font></blockquote> <p>Sure. But if that's called "a little care" then "you <b>also</b> need to call pthread_setcancelstate(PTHREAD_CANCEL_ENABLE) in each thread" wouldn't a large problem...</p> Fri, 15 Apr 2016 15:10:54 +0000 This is why we can't have safe cancellation points https://lwn.net/Articles/683997/ https://lwn.net/Articles/683997/ oshepherd Uh, thinko. I meant to say "fork is not optional in POSIX." Indeed, <tt>posix_spawn</tt> is optional, but <tt>fork</tt> is not (that said, if there are any important platforms where POSIX spawn doesn't exist - I'm thinking probably OS X here - it'd be relatively easy for somebody to produce a compatibility shim which implemented it on top of fork/execve/pipe/close/dup/etc) Fri, 15 Apr 2016 15:05:18 +0000 This is why we can't have safe cancellation points https://lwn.net/Articles/683957/ https://lwn.net/Articles/683957/ MrWim <div class="FormattedComment"> <font class="QuotedText">&gt; posix_spawn is not optional in POSIX.</font><br> <p> A minor point: I understood the parent to mean that fork is optional in POSIX, whereas posix_spawn is mandatory.<br> </div> Fri, 15 Apr 2016 14:04:55 +0000 This is why we can't have safe cancellation points https://lwn.net/Articles/683953/ https://lwn.net/Articles/683953/ oshepherd <tt>posix_spawn</tt> is not optional in POSIX. Yes, one of the motivating reasons for it is that it permits <i>profiles</i> of POSIX for no-MMU platforms to spawn new processes, but that is not the only motivation. <tt>posix_spawn</tt> can also be more efficient (because it does not have to go through the completely generic <tt>fork</tt> path, and it additionally makes error handling much easier (Have you ever tried reporting <tt>execve</tt>, <tt>dup</tt> or <tt>close</tt> errors back to the parent process? It's not a trivial matter...) <p> I don't buy the argument that <tt>fork</tt>+<tt>dup</tt>+<tt>close</tt>+<tt>exec</tt> is easier. Not once you actually handle errors properly or do other things that a Robust Application (TM) should. Fri, 15 Apr 2016 12:07:44 +0000 This is why we can't have safe cancellation points https://lwn.net/Articles/683934/ https://lwn.net/Articles/683934/ neilbrown <div class="FormattedComment"> <font class="QuotedText">&gt; I think the problem is simple inefficiency.</font><br> <p> Specifically? The solution used by musl costs almost nothing except on x86_32 and the change to make it work well on x86_32 has zero extra performance cost.<br> <p> <font class="QuotedText">&gt; And even your "simple" scheme includes many steps and couldn't arrive in a random program by accident.</font><br> <p> I'm failing to parse that... Certainly you wouldn't put any code in any program by accident (I hope) ??<br> The random program probably never cancels threads so it wouldn't want these steps anyway.<br> <p> <font class="QuotedText">&gt; Surely if you change a design of your program that much to make it possible to use cancellation you could as well go and create wrapper for pthread_create which will call pthread_setcancelstate(p), too?</font><br> <p> I fail to see how this would solve anything at all.<br> <p> Many applications never cancel any threads. They are irrelevant. They need do nothing and they suffer no cost (maybe a couple of instructions per syscall. If you can't afford that, hand-code your systemcalls).<br> <p> Some applications do find value in the ability to cancel threads. Those threads clearly need to be prepared to be canceled. Being prepared is not zero work, but it is not too onerous.<br> <p> If the thread is not doing any resource allocation, maybe just computing pi to a few million bits, then it can deliberately request async cancellation and go about its business.<br> If the thread is allocating resources then it naturally needs to make sure they get de-allocated. Any code already needs to worry about this. Code that can be canceled needs to do maybe 10% more work.<br> It can disable cancellation over a short allocate/use/deallocate sequence that won't block. Or it can register a cleanup helper and record the allocation in some array or something.<br> If your allocations follow a strict LIFO discipline you can even<br> - alloc<br> - push cleanup handler<br> - use the allocation<br> - pop the cleanup handler<br> <p> Which makes for nice clean code with the certainty that the cleanup handler will run even if the thread is canceled.<br> The point of deferred cancellation is that this can be done with no locking, no extra system calls. It just needs a little care - like not logging any messages between the allocation and pushing the cleanup handler.<br> <p> </div> Fri, 15 Apr 2016 08:00:02 +0000 This is why we can't have safe cancellation points https://lwn.net/Articles/683930/ https://lwn.net/Articles/683930/ khim <blockquote><font class="QuotedText">What specific risks do you see if cancellation is mostly enabled?</font></blockquote><p>I think the problem is simple inefficiency.</p> <p>Cancellation support is not free - even if it's not used.</p> <p>And even your "simple" scheme includes many steps and couldn't arrive in a random program by accident.</p> <p>Surely if you change a design of your program that much to make it possible to use cancellation you could as well go and create wrapper for pthread_create which will call pthread_setcancelstate(p), too?</p> Fri, 15 Apr 2016 07:21:43 +0000 This is why we can't have safe cancellation points https://lwn.net/Articles/683916/ https://lwn.net/Articles/683916/ neilbrown <div class="FormattedComment"> This is assertion without substance.<br> <p> Surely I can:<br> 1/ create a data structure that contains a list of all resources I might hold (file descriptor, byte range locks).<br> 2/ register a cleanup handler which walks that data structure and frees everything.<br> 3/ write simple wrappers for open/accept/whatever which record the results in the data structure<br> 4/ just call those wrappers, never the bare API.<br> <p> Then if I ever get canceled, everything will be cleaned up nicely.<br> <p> I would need to disable cancellation while manipulating a data structure shared with other threads, but I see cancellation more as being appropriate for largely independent threads.<br> <p> What specific risks do you see if cancellation is mostly enabled?<br> <p> </div> Fri, 15 Apr 2016 00:38:34 +0000 This is why we can't have safe cancellation points https://lwn.net/Articles/683914/ https://lwn.net/Articles/683914/ luto <div class="FormattedComment"> Using it correctly is a real pain. Using it correctly in C++ is even worse.<br> <p> AFAICT the only way to use it safely is to have cancellation off *except* at very carefully selected points and to turn it on at those points. Every cancellation point then needs to be aware that the thread can go away without unwinding.<br> <p> ISTM any code that actually does this would be better off using ppoll, etc.<br> </div> Fri, 15 Apr 2016 00:11:01 +0000 This is why we can't have safe cancellation points https://lwn.net/Articles/683909/ https://lwn.net/Articles/683909/ neilbrown <div class="FormattedComment"> <font class="QuotedText">&gt; After all, no sensible program uses cancellation, so why make them pay the price?</font><br> <p> This is the part of the story that didn't make much sense to me. Why do you think cancellation is such a bad idea?<br> I appreciate that it only gets about a 3 or 4 on Rusty's API Design scale but they provide an extremely light-weight mechanism to protect threads from dying at awkward moments.<br> The only alternative I can see is for an application to use an ad-hoc signaling mechanism and for threads to only use non-blocking versions of 'accept' and other interfaces that allocate resources.<br> Apart from wheel-reinvention, that would be an interface that libraries couldn't share.<br> You and Rich have made it clear that cancellation can be implemented correctly and efficiently. So there isn't really any price to be paid. Let's just do it and move on ???<br> <p> <p> </div> Fri, 15 Apr 2016 00:04:33 +0000 This is why we can't have safe cancellation points https://lwn.net/Articles/683908/ https://lwn.net/Articles/683908/ wahern <div class="FormattedComment"> posix_spawn exists solely because POSIX makes implementing fork optional so that systems without virtual memory can still meet the letter of the standard.<br> <p> posix_spawn doesn't magically solve thread race problems. Using fork+exec+dup2 can be just as safe posix_spawn and be significantly more clear. For example, descriptors without the FD_CLOEXEC descriptor flag set will still leak into the new process instance unless you explicitly close them. But which is easier: tediously filling out a posix_spawn_file_actions object, or simply calling close? Often the latter.<br> <p> Implementations generally implement posix_spawn by using vfork+exec. That does mean that they bypass pthread_atfork handlers. But that's not intrinsically safer: it can cut both ways--maybe one was going to close a descriptor. Anyhow, pthread_atfork handlers are fundamentally broken wrt the original intention, and the specification admits this.<br> <p> <p> </div> Thu, 14 Apr 2016 23:51:43 +0000 This is why we can't have safe cancellation points https://lwn.net/Articles/683904/ https://lwn.net/Articles/683904/ wahern <p> Forking a helper process from main just to execute other programs sounds like cargo cult advice. </p> <p> Calling fork from a thread is just as safe or unsafe as another common asynchronous context: signals. Pthread mutexes aren't async-signal safe, either. So you _can_ use fork from a threaded application, just make sure that between fork and exec you only call async-safe routines--typically syscalls. It's perfectly safe to call routines like close, dup2, mmap, etc. Definitely do not use FILE handles, malloc, etc. </p> <p> Threading in general is incompatible with any code which doesn't pay attention to global state and other race conditions. But if you use a third-party library which doesn't internally implement locking, but which is very carefully designed not to touch global state and which behaves correctly as long the caller synchronizes on an instance object before calling its methods, that's not something I would call "incompatible" with threading even though safe behavior it's not automatic. The POSIX standard and implementations are likewise very carefully designed to permit mixed-use, but likewise require you to heed certain well-specified constraints. It's not magic. </p> <p> The lesson here isn't that it's impossible to mix these things; it's that you really need to pay attention. If you can't be bothered to think these things through; if you can't be bothered to read the freely available and relatively concise standards, if only to confirm what you've read on Stack Overflow or elsewhere; if you can't be bothered to otherwise catalog and verify your assumptions; then not only should you not mix forking and threading, you probably shouldn't be doing any kind of threaded programming, period. </p> <p> That advice applies to everybody, regardless of skill, myself included, because you devise solutions to problems based on your resources. If you don't have the time (presuming you have the knowledge and faculties) to implement something correctly using certain tools, you should use safer tools. This is why even the most expert of C lovers regularly use scripting languages, and why people using so-called "safe" languages use even safer languages on occasion. </p> <p> FWIW, forking and threading is well-defined by POSIX. From the specification for fork in IEEE Std 1003.1, 2013 Edition, </p> <tt><blockquote> <p> A process shall be created with a single thread. If a multi-threaded process calls fork(), the new process shall contain a replica of the calling thread and its entire address space, possibly including the states of mutexes and other resources. Consequently, to avoid errors, the child process may only execute async-signal-safe operations until such time as one of the exec functions is called. Fork handlers may be established by means of the pthread_atfork() function in order to maintain application invariants across fork() calls. </p> <p> When the application calls fork() from a signal handler and any of the fork handlers registered by pthread_atfork() calls a function that is not async-signal-safe, the behavior is undefined. </p> </blockquote></tt> <p> Likewise, the advice that "the only safe thing to do from a signal handler is to set a flag" is also not true. It's intended to scare junior programmers (or lazy programmers) away from interfaces that are difficult to use. They require enough experience and knowledge to be able verify correct usage. But IMO, if you're programming any non-trivial application in C or C++, you should at least understand how to _disprove_ correct usage. Which is to say, you should be able to explain _why_ code implementing a signal handler is unsafe, even if you're not sure if or how it could be made safe, and even if it requires consulting a specification or other primary source. </p> <p> A corollary is that good C programmers should be well practiced at reading the C, POSIX, or other relevant standard. Manual pages, especially on Linux, are definitely not up to snuff, FWIW. They're woefully incomplete, and too often resort to scare tactics. Whereas the POSIX specification is more complete, well laid-out, and has useful overviews and discussions relevant to subsystems and specific routines. It's the best starting point, though for many situations there's no substitute for verifying behavior by consulting the source code. </p> Thu, 14 Apr 2016 23:30:09 +0000 This is why we can't have safe cancellation points https://lwn.net/Articles/683905/ https://lwn.net/Articles/683905/ pikhq <div class="FormattedComment"> The actually preferred POSIX solution is to use the posix_spawn function, which works correctly in the presence of threads. I admit it's a bit of a little-known function, but it's a handy one.<br> </div> Thu, 14 Apr 2016 22:46:02 +0000 This is why we can't have safe cancellation points https://lwn.net/Articles/683902/ https://lwn.net/Articles/683902/ khim <p>POSIX had that requirement <a href="http://pubs.opengroup.org/onlinepubs/007908775/xsh/pthread_setcancelstate.html">for the last 20 years or so</a>, I'm afraid it's too late to change it.</p> <p>If someone wants to introduce drastic, potentially disruptive, change to POSIX then it would be significantly more sane to just make them optional in POSIX and remove them from libraries like Musl and GLibc, don't you think?</p> Thu, 14 Apr 2016 22:30:35 +0000 This is why we can't have safe cancellation points https://lwn.net/Articles/683897/ https://lwn.net/Articles/683897/ luto <div class="FormattedComment"> IMO that's simply daft.<br> <p> Perhaps someone should attempt to change the standard to work the other way (default is PTHREAD_CANCEL_DISABLE and PTHREAD_CANCEL_DEFERRED). After all, no sensible program uses cancellation, so why make them pay the price?<br> </div> Thu, 14 Apr 2016 21:28:49 +0000 This is why we can't have safe cancellation points https://lwn.net/Articles/683895/ https://lwn.net/Articles/683895/ khim Uhm. Perhaps I'm misreading something but AFAICS <a href="http://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_setcancelstate.html">the cancelability state and type of any newly created threads, including the thread in which main() was first invoked, shall be PTHREAD_CANCEL_ENABLE and PTHREAD_CANCEL_DEFERRED respectively</a> means exactly what I wrote: you don't enable cancellation, you just use it. You can disable it, sure - but that's not default. Thu, 14 Apr 2016 21:18:18 +0000 This is why we can't have safe cancellation points https://lwn.net/Articles/683884/ https://lwn.net/Articles/683884/ luto <div class="FormattedComment"> You do enable cancellation, though -- by default, system calls are not cancellable AFAIK.<br> <p> So musl could (and, AFAICT, does) use int $0x80 only for cancellable syscalls.<br> <p> (FWIW, the actual meat of the patch I wrote was fine, I think. The issue was that building vdso code at all is a giant mess and I broke the build system.)<br> </div> Thu, 14 Apr 2016 19:40:24 +0000 This is why we can't have safe cancellation points https://lwn.net/Articles/683863/ https://lwn.net/Articles/683863/ jake <div class="FormattedComment"> <font class="QuotedText">&gt; Editor: Musl's lead developer is called Rich Felker, not Rick.</font><br> <p> Indeed. Fixed now. Thanks for the report.<br> <p> (I will take this opportunity to remind folks that typo reports and such should go to lwn@lwn.net)<br> <p> jake<br> </div> Thu, 14 Apr 2016 15:45:20 +0000 This is why we can't have safe cancellation points https://lwn.net/Articles/683862/ https://lwn.net/Articles/683862/ karkhaz <div class="FormattedComment"> Editor: Musl's lead developer is called Rich Felker, not Rick.<br> </div> Thu, 14 Apr 2016 15:41:23 +0000 This is why we can't have safe cancellation points https://lwn.net/Articles/683804/ https://lwn.net/Articles/683804/ gutschke <div class="FormattedComment"> fork() and threads are just mutually incompatible. Period.<br> <p> The preferred solution in the POSIX world is to fork() a helper process as the first thing in main(), before any threads get created. And this helper is then used whenever another process needs to be created. Unfortunately, this is not always possible in existing code bases. It also breaks, if libraries decide to start threads before main() executes. And it can make reaping of child processes more difficult.<br> <p> If the above concerns pose an insurmountable problem, there are ways to make forking possible; but they require direct system calls, intimate knowledge of the underlying libc implementation and of the kernel, and also knowledge of how the compiler and the dynamic linker interact. It's amazingly difficult to get right.<br> <p> For many years, I have maintained commercial code that had to implement this hack. It generally worked fine, but the amount of engineering effort to get there was insanely high, and every couple of years new bugs popped up, when the tool chain gradually changed.<br> <p> </div> Thu, 14 Apr 2016 08:52:19 +0000