Doc. No.: WG21/P0270R3
Date: 2017-3-3
Author: Hans-J. Boehm
Email: hboehm@google.com
Audience: CWG, LWG

P0270R3: Removing C dependencies from signal handler wording

This topic was briefly addressed in P0063R0, but the authors subsequently decided to address it separately. This version reflects P0270R0 discussion in Jacksonville, draft revisions in Oulu, as well as P0270R1 and draft revision discussions by SG1 and CWG in Issaquah.

The current C++ working draft restricts signal handlers to functions in the intersection of C and C++. We believe this is dubious for at least three reasons:

We propose to remedy this by replacing the notion of a "POF", "plain old function" with a "signal-safe evaluation" defined entirely in C++ terms. The definition we use largely excludes use of the library, thus avoiding any need to reimplement that in a signal-safe manner. This makes the definition rather restrictive. But we believe it remains at least theoretically possible for the user to write very sophisticated signal handlers through the use of atomics.

We expect that, although these changes are clearly substantive, there is no real practical impact. Existing implementations and user code should continue to work unchanged (or remain as broken as they always were.)

We do not exclude lambda expressions from signal handlers, on the assumption that the lambda expression itself should not require memory allocation. We do not say anything about non-function-local statics, since it should not be possible to trigger construction or destruction from within a signal handler.

As far as I can tell, the mem... and str... functions are not guaranteed to be async-signal-safe in Posix (or C), so it seems presumptive of us to declare them signal-safe here. I thus decided against it.

We somewhat separably propose to make the behavior of non-signal-safe functions in signal handlers undefined, instead of implementation-defined. We are not aware of any implementations that actually attempt to define this.

The restriction on static initialization warrants discussion. We currently allow even the call of a POF that doesn't access any globals to trigger static initialization (3.6.2p4 [basic.start.init]). It's not clear how to make that work if a thread already part way through the initialization receives the signal. This appears to me to be a preexisting problem that this exposes, rather than a problem introduced by this change. It is partially addressed by P0250, which makes this implementation-defined.

It may make sense to eventually relax these restrictions invoked through an explicit synchronous raise() call. It's unclear that this is important enough to bother.

These changes are arguably not essential for the adoption of the P0063. But we believe they are highly desirable and should follow P0063 in fairly short order.

Reaction to CWG discussion in Issaquah

The original discussion is reflected in the CWG notes. Specific comments:

Proposed wording changes:

Thanks to help from the editors, these are roughly relative to the C++17 CD. Note that the underlying text changed substantially in Oulu, in ways that are orthogonal to our goals here.

Update 18.10.5 [csignal.syn] as follows.

In the synopsis, modify the signal declaration as follows:

extern "C" void (*signal(int sig, void (*func)(int)))(int);

Insert a new paragraph before paragraph 3:

A plain lock-free atomic operation is an invocation of a function f from Clause 29 [atomics], such that:

Modify the former paragraph 3 as follows.

The common subset of the C and C ++ languages consists of all declarations, definitions, and expressions that may appear in a well formed C++ program and also in a conforming C program. A POF (“plain old function”) is a function that uses only features from this common subset, and that does not directly or indirectly use any function that is not a POF, An evaluation is signal-safe unless it includes one of the following:

The behavior of any function other than a POF used as a signal handler in a C++ program is implementation-defined. A signal handler invocation has undefined behavior if it includes an evaluation that is not signal-safe.

Append a new paragraph.

The function signal() is signal-safe if it is invoked with the first argument equal to the signal number corresponding to the signal that caused the invocation of the handler.

The following functions need to be callable from handlers. We expect that a few other functions should also eventually be declared signal-safe, but these are among the more blatant cases. abort() and quick_exit() are treated as safe by C.

Add a new paragraph at the beginning of section 18.3.2.4 [numeric.limits.members]:

Each member function defined in this subclause is signal-safe (18.10.5 [csignal.syn]).

Add a sentence at the end of the remarks for _Exit() in 18.5p3 [support.start.term]:

The function _Exit() is signal-safe (18.10.5 [csignal.syn]).

Add a sentence at the end of the remarks for abort() in 18.5p5 [support.start.term]:

The function abort() is signal-safe (18.10.5 [csignal.syn]).

Add at new element to the end of 18.5p13 (quick_exit()) [support.start.term]. Note that it is not clear that this is actually correct for current implementations that may hold a lock while running handlers. However this requirement is already present in the C11 standard, and it doesn't seem to make much sense to weaken their specifications for their functions. An LWG issue on this has been requested.

Remarks: The function quick_exit() is signal-safe (18.10.5 [csignal.syn]) when the functions registered with at_quick_exit() are.

Modify 18.9p1 [support.initlist] as follows:

The header <initializer_list> defines a class template and several support functions related to list-initialization (see 8.5.4). All functions specified in this subclause are signal-safe (18.10.5 [csignal.syn]).

Modify 20.2.4p1 [forward] as follows:

The library provides templated helper functions to simplify applying move semantics to an lvalue and to simplify the implementation of forwarding functions. All functions specified in this subclause are signal-safe (18.10.5 [csignal.syn]).

20.14 [meta] provides a few actual functions that return constant values. These are also clearly signal-safe. Add a new paragraph at the end of section 20.14 [meta], before 20.14.1:

All functions specified in this subclause are signal-safe (18.10.5 [csignal.syn]).

Add after paragraph 2 of 21.5.3 [cstring.syn]:

The memcpy and memmove functions are signal-safe (18.10.5 [csignal.syn]).

Acknowledgment

Clark Nelson provided many useful suggestions, but not necessarily agreement. This version reflects contributions by many other members of CWG and LWG, including Jens Maurer, Thomas Koeppe and JF Bastien, who helped shepherd it through CWG and LWG and provided additional feedback.