blob: bf7e16a9a2583136414dd9c5cfe736d937a9b278 [file] [edit]
// 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_