Thread-safe one-time initialization

Feature or enhancement

Some CPython internals require initialization exactly once. Some of these one time initializations are not thread-safe without the GIL or have data races according to the C11 memory model.

We should add a lightweight, thread-safe one-time initialization API similar to C++11's std::call_once 1. The proposed internal-only API follows C++11's std::call_once, but adapted for C (i.e., error returns and function pointers):

typedef struct {
    uint8_t v;
} _PyOnceFlag;

typedef int _Py_once_fn_t(void *arg);

// Calls `fn` once using `flag`. The `arg` is passed to the call to `fn`.
//
// Returns 1 on success and 0 on failure.
//
// If `fn` returns 1 (success), then subsequent calls immediately return 1.
// If `fn` returns 0 (failure), then subsequent calls will retry the call.
int _PyOnceFlag_CallOnce(_PyOnceFlag *flag, _Py_once_fn_t *fn, void *arg);

As an example, the Python-ast.c relies on the GIL and an initialized variable to ensure that it is only initialized once:

static int
init_types(struct ast_state *state)
{
// init_types() must not be called after _PyAST_Fini()
// has been called
assert(state->initialized >= 0);
if (state->initialized) {
return 1;
}

Linked PRs

  1. Also, pthread_once and C11's call_once. std::call_once supports error returns, which is important for CPython's use cases.