Xref: utzoo comp.arch:4313 comp.lang.c:9163 comp.unix.wizards:7710 Path: utzoo!mnetor!uunet!husc6!mit-eddie!apollo!mishkin From: mishkin@apollo.uucp (Nathaniel Mishkin) Newsgroups: comp.arch,comp.lang.c,comp.unix.wizards Subject: Re: longjmp() out of nested signal handlers Message-ID: <3b65e8b3.13422@apollo.uucp> Date: 11 Apr 88 13:26:00 GMT References: <4609@june.cs.washington.edu> <122@csanta.UUCP> Reply-To: mishkin@apollo.UUCP (Nathaniel Mishkin) Organization: Apollo Computer, Chelmsford, MA Lines: 98 In article <122@csanta.UUCP> greg@csanta.UUCP (Greg Comeau) writes: >In article <4609@june.cs.washington.edu> pardo@uw-june.UUCP (David Keppel) writes: >The problem that occurs with longjmp() is not within the function itself, >but with any side-effects that may occur because of the longjmp(). >(This BTW, only happens to be spelled out in dpANSI, but has been true >even without that being said since its first implementations). For instance, >within library functions that you don't have source code to (or even your own >routines that you do have source code to), a given routine may be setting an >external variable for later use or maybe let's say as a semaphore. If a >longjump occurs after the setting of the variable but before it's put to any >use, then you're in trouble. > >Another quite obtuse reason is that the corresponding setjmp() may be >called in a line of code where it was part of a subexpression. Yickie >poo for that one! Apollo defines a package called PFM (Process Fault Manager) to deal with this sort of problem. The two primitives relevant to this discussion are "pfm_$cleanup" and "pfm_$signal". They are analogous to "setjmp" and "longjmp" except they are "stacked". Basically, you use them like: boolean SomeImportantStateVariable = false; foo() { pfm_$cleanup_rec crec; status = pfm_$cleanup(crec); if (status.all != pfm_$cleanup_set) { /* first time? */ SomeImportantStateVariable = false; /* No, restore state */ pfm_$signal(status); /* resignal */ } else { SomeImportantStateVariable = true; /* ... Do some important stuff ... */ SomeImportantStateVariable = false; pfm_$rls_cleanup(crec); } } (Lisp people should recognize this as something like UNWIND-PROTECT.) "pfm_$cleanup" returns the first time with the constant value "cleanup set" (ala "setjmp" returning 0). It returns the second time with the integer value "thrown" by a "pfm_$signal". "pfm_$signal" causes a long jump to the site of the most recent "pfm_$cleanup". "pfm_$signal" can be called explicitly (like "setjmp"). Also, The various Unix signals are automatically turned into calls to "pfm_$signal" if no signal handler exists. Cleanup handlers (the term for the "then" clause of the above "if" statement) can either choose to resignal by calling "pfm_$signal" or eat the signal and continue process as that level. Generally, you're supposed to resignal unless you recognize the signal that was thrown. I won't make any argument for this being syntactically "pretty", but it is at least conceptually the right thing. (I'd like language support for exception handling, but I don't get to pick these things.) As part of making Apollo's Network Computing System (NCS) portable, I had to deal with making a portable subset of PFM. NCS supports a remote procedure call (RPC) facility and depends on the above cleanup mechanism. When you make a call to a remote procedure, if the target of the call doesn't respond, NCS raises an exception using PFM. The remote call looks syntactically like a local call (you're calling a local stub) so even if we thought it was the right thing to indicate call failure by returning some "error status" (we don't), we can't since we don't get to pick the signature of the remote procedure. Checking global status variables (like "errno") for failure indications is also forbidden if you ever want your software to work in an environment where there are multiple threads of control per address space (we do). It turns out that implementing the necessary parts of PFM on vanilla Unix systems via "setjmp/longjmp" was pretty trivial. (We're talking 300 lines of code here.) Really, all PFM amounts to is a consistent and disciplined use of "setjmp/lonjmp". I think I can post the source (to some appropriate group) if anyone expresses interest. By the way, the vanilla Unix implementation of PFM does in fact depend on the "yicky poo" use of a "setjmp" inside an expression. "pfm_$cleanup" is: #define pfm_$cleanup(crec) \ pfm_$_cleanup(setjmp(crec.buf), &crec) So far, I've found two compilers/runtimes that don't handle this right. For them, I write "pfm_$cleanup" as: #define pfm_$cleanup(crec) ( \ pfm_$global_setjmp_value = setjmp(crec.buf), \ pfm_$_cleanup(pfm_$global_setjmp_value, &crec) \ ) at the cost of introducing a yicky poo global variable. (Hey, I can't fix *all* of Unix's problems at once!) -- -- Nat Mishkin Apollo Computer Inc. Chelmsford, MA {decvax,mit-eddie,umix}!apollo!mishkin