Skip to content

Thread-safe one-time initialization #111956

Closed
@colesbury

Description

@colesbury

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:

cpython/Python/Python-ast.c

Lines 1126 to 1135 in d61313b

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

Footnotes

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

Metadata

Metadata

Assignees

Labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions