| // Protocol Buffers - Google's data interchange format | |
| // Copyright 2023 Google LLC. All rights reserved. | |
| // | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file or at | |
| // https://developers.google.com/open-source/licenses/bsd | |
| #ifndef PYUPB_FREE_THREADING_LAZY_PTR_H_ | |
| #define PYUPB_FREE_THREADING_LAZY_PTR_H_ | |
| // PyUpb_LazyPtr: Lock-free lazy pointer initialization for CPython / upb. | |
| // | |
| // OVERVIEW: | |
| // This module provides lock-free primitives for lazily initialized C pointers | |
| // (`PyUpb_LazyInitPtr`) and CPython reference-counted objects | |
| // (`PyUpb_LazyInitPyObject`). | |
| // | |
| // Under free-threaded Python (`Py_GIL_DISABLED`), lazy pointers expand to | |
| // atomic pointers (`UPB_ATOMIC(T*)`) and use atomic Compare-And-Swap (CAS) with | |
| // acquire/release memory ordering to guarantee safe thread interaction without | |
| // acquiring global locks. On CAS collisions (races where two threads | |
| // concurrently attempt to initialize the same field), the lost instance is | |
| // automatically freed/decref'd and the winning instance is returned to all | |
| // callers. | |
| // | |
| // Under standard GIL Python builds, `PYUPB_LAZYPTR(T)` degrades to standard C | |
| // pointers (`T*`), executing simple null-checks without atomic instruction | |
| // overhead. | |
| // | |
| // USAGE: | |
| // 1. Declare lazy fields using `PYUPB_LAZYPTR(type)`: | |
| // typedef struct { | |
| // PYUPB_LAZYPTR(PyObject) cached_options; | |
| // } MyDescriptor; | |
| // | |
| // 2. Read pointers lock-free via `PyUpb_LazyGetPtr`: | |
| // PyObject* options = (PyObject*)PyUpb_LazyGetPtr(&desc->cached_options); | |
| // | |
| // 3. Initialize PyObject* fields atomically via `PyUpb_LazyInitPyObject`: | |
| // PyObject* options = PyUpb_LazyInitPyObject( | |
| // &desc->cached_options, CreateOptionsCallback, &context_args); | |
| // clang-format off | |
| #include "Python.h" | |
| // clang-format on | |
| #include "upb/port/atomic.h" | |
| // Must be last. | |
| #include "upb/port/def.inc" | |
| #ifdef Py_GIL_DISABLED | |
| #define PYUPB_LAZYPTR(type) UPB_ATOMIC(type*) | |
| #else | |
| #define PYUPB_LAZYPTR(type) type* | |
| #endif | |
| typedef void* (*PyUpb_PtrInitFunc)(void* ctx); | |
| typedef void (*PyUpb_PtrFreeFunc)(void* ptr); | |
| #ifndef Py_GIL_DISABLED | |
| static inline void* PyUpb_LazyPtr_RawGet(const void* lazy_ptr) { | |
| return *(void* const*)lazy_ptr; | |
| } | |
| static inline void* PyUpb_LazyPtr_LazyInit(void* lazy_ptr, | |
| PyUpb_PtrInitFunc init_fn, | |
| PyUpb_PtrFreeFunc free_fn, | |
| void* ctx) { | |
| void** ptr = (void**)lazy_ptr; | |
| if (*ptr) return *ptr; | |
| *ptr = init_fn(ctx); | |
| return *ptr; | |
| } | |
| #else | |
| static inline void* PyUpb_LazyPtr_RawGet(const void* lazy_ptr) { | |
| return upb_Atomic_Load((UPB_ATOMIC(void*)*)lazy_ptr, memory_order_acquire); | |
| } | |
| // Atomically loads or initializes a lazy C pointer. On a CAS collision race, | |
| // calls `free_fn` on the duplicate pointer and returns the winning instance. | |
| void* PyUpb_LazyPtr_LazyInit(void* lazy_ptr, PyUpb_PtrInitFunc init_fn, | |
| PyUpb_PtrFreeFunc free_fn, void* ctx); | |
| #endif | |
| static inline void PyUpb_PyObject_Decref(void* obj) { | |
| Py_DECREF((PyObject*)obj); | |
| } | |
| typedef PyObject* (*PyUpb_PyObjectInitFunc)(void* ctx); | |
| // Inline wrapper for PyObject* lazy fields. Automatically INCREFs the returned | |
| // reference to provide a new reference to the caller. | |
| static inline PyObject* PyUpb_LazyPtr_LazyInitPyObj( | |
| void* lazy_ptr, PyUpb_PyObjectInitFunc init_fn, void* ctx) { | |
| PyObject* obj = (PyObject*)PyUpb_LazyPtr_LazyInit( | |
| lazy_ptr, (PyUpb_PtrInitFunc)init_fn, PyUpb_PyObject_Decref, ctx); | |
| Py_XINCREF(obj); | |
| return obj; | |
| } | |
| static inline void PyUpb_LazyPtr_Clear(void* lazy_ptr) { | |
| #ifdef Py_GIL_DISABLED | |
| PyObject* old = (PyObject*)upb_Atomic_Exchange((UPB_ATOMIC(void*)*)lazy_ptr, | |
| NULL, memory_order_relaxed); | |
| #else | |
| PyObject* old = *(PyObject**)lazy_ptr; | |
| *(PyObject**)lazy_ptr = NULL; | |
| #endif | |
| Py_XDECREF(old); | |
| } | |
| #include "upb/port/undef.inc" | |
| #endif // PYUPB_FREE_THREADING_LAZY_PTR_H_ |