| // 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 = 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_ |