diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index 7424e99f22..de3ffdc9fd 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -27,7 +27,7 @@ on: type: string description: Valid JSON list of Python tags to build the client for required: false - default: '["cp39", "cp310", "cp311", "cp312", "cp313"]' + default: '["cp39", "cp310", "cp311", "cp312", "cp313", "cp313t"]' platform-tag: description: Platform to build the client for. type: choice @@ -80,7 +80,7 @@ on: python-tags: type: string required: false - default: '["cp39", "cp310", "cp311", "cp312", "cp313"]' + default: '["cp39", "cp310", "cp311", "cp312", "cp313", "cp313t"]' platform-tag: type: string required: true @@ -278,7 +278,7 @@ jobs: run: echo CIBW_ENVIRONMENT_MACOS="LDFLAGS='-headerpad_max_install_names'" >> $GITHUB_ENV - name: Build wheel - uses: pypa/cibuildwheel@v2.21.3 + uses: pypa/cibuildwheel@v2.22.0 env: # manylinux_2_28 x64 image doesn't search in this directory for shared libraries CIBW_ENVIRONMENT_LINUX: LD_LIBRARY_PATH=/usr/local/lib64 @@ -289,6 +289,7 @@ jobs: yum install libyaml-devel -y # delvewheel is not enabled by default but we do need to repair the wheel CIBW_BEFORE_BUILD_WINDOWS: "pip install delvewheel==1.*" + CIBW_ENABLE: ${{ matrix.python-tag == 'cp313t' && 'cpython-freethreading' || '' }} # We want to check that our wheel links to the new openssl 3 install, not the system default # This assumes that ldd prints out the "soname" for the libraries # We can also manually verify the repair worked by checking the repaired wheel's compatibility tag diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index cef8d86b96..24763e9c93 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -59,7 +59,7 @@ jobs: submodules: recursive fetch-depth: 0 - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v4 with: python-version: ${{ matrix.py-version }} architecture: 'x64' @@ -324,7 +324,7 @@ jobs: with: submodules: recursive - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v4 with: python-version: ${{ matrix.py-version }} architecture: 'x64' diff --git a/src/include/log.h b/src/include/log.h index fa87b13cd7..dd303e9be2 100644 --- a/src/include/log.h +++ b/src/include/log.h @@ -19,13 +19,6 @@ #include #include -/* - * Structure to hold user's log_callback object - */ -typedef struct Aerospike_log_callback { - PyObject *callback; -} AerospikeLogCallback; - /** * Set log level for C-SDK * aerospike.set_log_level( aerospike.LOG_LEVEL_WARN ) diff --git a/src/include/pythoncapi_compat.h b/src/include/pythoncapi_compat.h new file mode 100644 index 0000000000..12aac27f11 --- /dev/null +++ b/src/include/pythoncapi_compat.h @@ -0,0 +1,1659 @@ +// Header file providing new C API functions to old Python versions. +// +// File distributed under the Zero Clause BSD (0BSD) license. +// Copyright Contributors to the pythoncapi_compat project. +// +// Homepage: +// https://github.com/python/pythoncapi_compat +// +// Latest version: +// https://raw.githubusercontent.com/python/pythoncapi-compat/main/pythoncapi_compat.h +// +// SPDX-License-Identifier: 0BSD + +#ifndef PYTHONCAPI_COMPAT +#define PYTHONCAPI_COMPAT + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +// Python 3.11.0b4 added PyFrame_Back() to Python.h +#if PY_VERSION_HEX < 0x030b00B4 && !defined(PYPY_VERSION) + #include "frameobject.h" // PyFrameObject, PyFrame_GetBack() +#endif + +#ifndef _Py_CAST + #define _Py_CAST(type, expr) ((type)(expr)) +#endif + +// Static inline functions should use _Py_NULL rather than using directly NULL +// to prevent C++ compiler warnings. On C23 and newer and on C++11 and newer, +// _Py_NULL is defined as nullptr. +#if (defined(__STDC_VERSION__) && __STDC_VERSION__ > 201710L) || \ + (defined(__cplusplus) && __cplusplus >= 201103) + #define _Py_NULL nullptr +#else + #define _Py_NULL NULL +#endif + +// Cast argument to PyObject* type. +#ifndef _PyObject_CAST + #define _PyObject_CAST(op) _Py_CAST(PyObject *, op) +#endif + +#ifndef Py_BUILD_ASSERT + #define Py_BUILD_ASSERT(cond) \ + do { \ + (void)sizeof(char[1 - 2 * !(cond)]); \ + } while (0) +#endif + +// bpo-42262 added Py_NewRef() to Python 3.10.0a3 +#if PY_VERSION_HEX < 0x030A00A3 && !defined(Py_NewRef) +static inline PyObject *_Py_NewRef(PyObject *obj) +{ + Py_INCREF(obj); + return obj; +} + #define Py_NewRef(obj) _Py_NewRef(_PyObject_CAST(obj)) +#endif + +// bpo-42262 added Py_XNewRef() to Python 3.10.0a3 +#if PY_VERSION_HEX < 0x030A00A3 && !defined(Py_XNewRef) +static inline PyObject *_Py_XNewRef(PyObject *obj) +{ + Py_XINCREF(obj); + return obj; +} + #define Py_XNewRef(obj) _Py_XNewRef(_PyObject_CAST(obj)) +#endif + +// bpo-39573 added Py_SET_REFCNT() to Python 3.9.0a4 +#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_SET_REFCNT) +static inline void _Py_SET_REFCNT(PyObject *ob, Py_ssize_t refcnt) +{ + ob->ob_refcnt = refcnt; +} + #define Py_SET_REFCNT(ob, refcnt) _Py_SET_REFCNT(_PyObject_CAST(ob), refcnt) +#endif + +// Py_SETREF() and Py_XSETREF() were added to Python 3.5.2. +// It is excluded from the limited C API. +#if (PY_VERSION_HEX < 0x03050200 && !defined(Py_SETREF)) && \ + !defined(Py_LIMITED_API) + #define Py_SETREF(dst, src) \ + do { \ + PyObject **_tmp_dst_ptr = _Py_CAST(PyObject **, &(dst)); \ + PyObject *_tmp_dst = (*_tmp_dst_ptr); \ + *_tmp_dst_ptr = _PyObject_CAST(src); \ + Py_DECREF(_tmp_dst); \ + } while (0) + + #define Py_XSETREF(dst, src) \ + do { \ + PyObject **_tmp_dst_ptr = _Py_CAST(PyObject **, &(dst)); \ + PyObject *_tmp_dst = (*_tmp_dst_ptr); \ + *_tmp_dst_ptr = _PyObject_CAST(src); \ + Py_XDECREF(_tmp_dst); \ + } while (0) +#endif + +// bpo-43753 added Py_Is(), Py_IsNone(), Py_IsTrue() and Py_IsFalse() +// to Python 3.10.0b1. +#if PY_VERSION_HEX < 0x030A00B1 && !defined(Py_Is) + #define Py_Is(x, y) ((x) == (y)) +#endif +#if PY_VERSION_HEX < 0x030A00B1 && !defined(Py_IsNone) + #define Py_IsNone(x) Py_Is(x, Py_None) +#endif +#if (PY_VERSION_HEX < 0x030A00B1 || defined(PYPY_VERSION)) && \ + !defined(Py_IsTrue) + #define Py_IsTrue(x) Py_Is(x, Py_True) +#endif +#if (PY_VERSION_HEX < 0x030A00B1 || defined(PYPY_VERSION)) && \ + !defined(Py_IsFalse) + #define Py_IsFalse(x) Py_Is(x, Py_False) +#endif + +// bpo-39573 added Py_SET_TYPE() to Python 3.9.0a4 +#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_SET_TYPE) +static inline void _Py_SET_TYPE(PyObject *ob, PyTypeObject *type) +{ + ob->ob_type = type; +} + #define Py_SET_TYPE(ob, type) _Py_SET_TYPE(_PyObject_CAST(ob), type) +#endif + +// bpo-39573 added Py_SET_SIZE() to Python 3.9.0a4 +#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_SET_SIZE) +static inline void _Py_SET_SIZE(PyVarObject *ob, Py_ssize_t size) +{ + ob->ob_size = size; +} + #define Py_SET_SIZE(ob, size) _Py_SET_SIZE((PyVarObject *)(ob), size) +#endif + +// bpo-40421 added PyFrame_GetCode() to Python 3.9.0b1 +#if PY_VERSION_HEX < 0x030900B1 || defined(PYPY_VERSION) +static inline PyCodeObject *PyFrame_GetCode(PyFrameObject *frame) +{ + assert(frame != _Py_NULL); + assert(frame->f_code != _Py_NULL); + return _Py_CAST(PyCodeObject *, Py_NewRef(frame->f_code)); +} +#endif + +static inline PyCodeObject *_PyFrame_GetCodeBorrow(PyFrameObject *frame) +{ + PyCodeObject *code = PyFrame_GetCode(frame); + Py_DECREF(code); + return code; +} + +// bpo-40421 added PyFrame_GetBack() to Python 3.9.0b1 +#if PY_VERSION_HEX < 0x030900B1 && !defined(PYPY_VERSION) +static inline PyFrameObject *PyFrame_GetBack(PyFrameObject *frame) +{ + assert(frame != _Py_NULL); + return _Py_CAST(PyFrameObject *, Py_XNewRef(frame->f_back)); +} +#endif + +#if !defined(PYPY_VERSION) +static inline PyFrameObject *_PyFrame_GetBackBorrow(PyFrameObject *frame) +{ + PyFrameObject *back = PyFrame_GetBack(frame); + Py_XDECREF(back); + return back; +} +#endif + +// bpo-40421 added PyFrame_GetLocals() to Python 3.11.0a7 +#if PY_VERSION_HEX < 0x030B00A7 && !defined(PYPY_VERSION) +static inline PyObject *PyFrame_GetLocals(PyFrameObject *frame) +{ + #if PY_VERSION_HEX >= 0x030400B1 + if (PyFrame_FastToLocalsWithError(frame) < 0) { + return NULL; + } + #else + PyFrame_FastToLocals(frame); + #endif + return Py_NewRef(frame->f_locals); +} +#endif + +// bpo-40421 added PyFrame_GetGlobals() to Python 3.11.0a7 +#if PY_VERSION_HEX < 0x030B00A7 && !defined(PYPY_VERSION) +static inline PyObject *PyFrame_GetGlobals(PyFrameObject *frame) +{ + return Py_NewRef(frame->f_globals); +} +#endif + +// bpo-40421 added PyFrame_GetBuiltins() to Python 3.11.0a7 +#if PY_VERSION_HEX < 0x030B00A7 && !defined(PYPY_VERSION) +static inline PyObject *PyFrame_GetBuiltins(PyFrameObject *frame) +{ + return Py_NewRef(frame->f_builtins); +} +#endif + +// bpo-40421 added PyFrame_GetLasti() to Python 3.11.0b1 +#if PY_VERSION_HEX < 0x030B00B1 && !defined(PYPY_VERSION) +static inline int PyFrame_GetLasti(PyFrameObject *frame) +{ + #if PY_VERSION_HEX >= 0x030A00A7 + // bpo-27129: Since Python 3.10.0a7, f_lasti is an instruction offset, + // not a bytes offset anymore. Python uses 16-bit "wordcode" (2 bytes) + // instructions. + if (frame->f_lasti < 0) { + return -1; + } + return frame->f_lasti * 2; + #else + return frame->f_lasti; + #endif +} +#endif + +// gh-91248 added PyFrame_GetVar() to Python 3.12.0a2 +#if PY_VERSION_HEX < 0x030C00A2 && !defined(PYPY_VERSION) +static inline PyObject *PyFrame_GetVar(PyFrameObject *frame, PyObject *name) +{ + PyObject *locals, *value; + + locals = PyFrame_GetLocals(frame); + if (locals == NULL) { + return NULL; + } + #if PY_VERSION_HEX >= 0x03000000 + value = PyDict_GetItemWithError(locals, name); + #else + value = _PyDict_GetItemWithError(locals, name); + #endif + Py_DECREF(locals); + + if (value == NULL) { + if (PyErr_Occurred()) { + return NULL; + } + #if PY_VERSION_HEX >= 0x03000000 + PyErr_Format(PyExc_NameError, "variable %R does not exist", name); + #else + PyErr_SetString(PyExc_NameError, "variable does not exist"); + #endif + return NULL; + } + return Py_NewRef(value); +} +#endif + +// gh-91248 added PyFrame_GetVarString() to Python 3.12.0a2 +#if PY_VERSION_HEX < 0x030C00A2 && !defined(PYPY_VERSION) +static inline PyObject *PyFrame_GetVarString(PyFrameObject *frame, + const char *name) +{ + PyObject *name_obj, *value; + #if PY_VERSION_HEX >= 0x03000000 + name_obj = PyUnicode_FromString(name); + #else + name_obj = PyString_FromString(name); + #endif + if (name_obj == NULL) { + return NULL; + } + value = PyFrame_GetVar(frame, name_obj); + Py_DECREF(name_obj); + return value; +} +#endif + +// bpo-39947 added PyThreadState_GetInterpreter() to Python 3.9.0a5 +#if PY_VERSION_HEX < 0x030900A5 || defined(PYPY_VERSION) +static inline PyInterpreterState * +PyThreadState_GetInterpreter(PyThreadState *tstate) +{ + assert(tstate != _Py_NULL); + return tstate->interp; +} +#endif + +// bpo-40429 added PyThreadState_GetFrame() to Python 3.9.0b1 +#if PY_VERSION_HEX < 0x030900B1 && !defined(PYPY_VERSION) +static inline PyFrameObject *PyThreadState_GetFrame(PyThreadState *tstate) +{ + assert(tstate != _Py_NULL); + return _Py_CAST(PyFrameObject *, Py_XNewRef(tstate->frame)); +} +#endif + +#if !defined(PYPY_VERSION) +static inline PyFrameObject * +_PyThreadState_GetFrameBorrow(PyThreadState *tstate) +{ + PyFrameObject *frame = PyThreadState_GetFrame(tstate); + Py_XDECREF(frame); + return frame; +} +#endif + +// bpo-39947 added PyInterpreterState_Get() to Python 3.9.0a5 +#if PY_VERSION_HEX < 0x030900A5 || defined(PYPY_VERSION) +static inline PyInterpreterState *PyInterpreterState_Get(void) +{ + PyThreadState *tstate; + PyInterpreterState *interp; + + tstate = PyThreadState_GET(); + if (tstate == _Py_NULL) { + Py_FatalError("GIL released (tstate is NULL)"); + } + interp = tstate->interp; + if (interp == _Py_NULL) { + Py_FatalError("no current interpreter"); + } + return interp; +} +#endif + +// bpo-39947 added PyInterpreterState_Get() to Python 3.9.0a6 +#if 0x030700A1 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x030900A6 && \ + !defined(PYPY_VERSION) +static inline uint64_t PyThreadState_GetID(PyThreadState *tstate) +{ + assert(tstate != _Py_NULL); + return tstate->id; +} +#endif + +// bpo-43760 added PyThreadState_EnterTracing() to Python 3.11.0a2 +#if PY_VERSION_HEX < 0x030B00A2 && !defined(PYPY_VERSION) +static inline void PyThreadState_EnterTracing(PyThreadState *tstate) +{ + tstate->tracing++; + #if PY_VERSION_HEX >= 0x030A00A1 + tstate->cframe->use_tracing = 0; + #else + tstate->use_tracing = 0; + #endif +} +#endif + +// bpo-43760 added PyThreadState_LeaveTracing() to Python 3.11.0a2 +#if PY_VERSION_HEX < 0x030B00A2 && !defined(PYPY_VERSION) +static inline void PyThreadState_LeaveTracing(PyThreadState *tstate) +{ + int use_tracing = + (tstate->c_tracefunc != _Py_NULL || tstate->c_profilefunc != _Py_NULL); + tstate->tracing--; + #if PY_VERSION_HEX >= 0x030A00A1 + tstate->cframe->use_tracing = use_tracing; + #else + tstate->use_tracing = use_tracing; + #endif +} +#endif + +// bpo-37194 added PyObject_CallNoArgs() to Python 3.9.0a1 +// PyObject_CallNoArgs() added to PyPy 3.9.16-v7.3.11 +#if !defined(PyObject_CallNoArgs) && PY_VERSION_HEX < 0x030900A1 +static inline PyObject *PyObject_CallNoArgs(PyObject *func) +{ + return PyObject_CallFunctionObjArgs(func, NULL); +} +#endif + +// bpo-39245 made PyObject_CallOneArg() public (previously called +// _PyObject_CallOneArg) in Python 3.9.0a4 +// PyObject_CallOneArg() added to PyPy 3.9.16-v7.3.11 +#if !defined(PyObject_CallOneArg) && PY_VERSION_HEX < 0x030900A4 +static inline PyObject *PyObject_CallOneArg(PyObject *func, PyObject *arg) +{ + return PyObject_CallFunctionObjArgs(func, arg, NULL); +} +#endif + +// bpo-1635741 added PyModule_AddObjectRef() to Python 3.10.0a3 +#if PY_VERSION_HEX < 0x030A00A3 +static inline int PyModule_AddObjectRef(PyObject *module, const char *name, + PyObject *value) +{ + int res; + + if (!value && !PyErr_Occurred()) { + // PyModule_AddObject() raises TypeError in this case + PyErr_SetString(PyExc_SystemError, + "PyModule_AddObjectRef() must be called " + "with an exception raised if value is NULL"); + return -1; + } + + Py_XINCREF(value); + res = PyModule_AddObject(module, name, value); + if (res < 0) { + Py_XDECREF(value); + } + return res; +} +#endif + +// bpo-40024 added PyModule_AddType() to Python 3.9.0a5 +#if PY_VERSION_HEX < 0x030900A5 +static inline int PyModule_AddType(PyObject *module, PyTypeObject *type) +{ + const char *name, *dot; + + if (PyType_Ready(type) < 0) { + return -1; + } + + // inline _PyType_Name() + name = type->tp_name; + assert(name != _Py_NULL); + dot = strrchr(name, '.'); + if (dot != _Py_NULL) { + name = dot + 1; + } + + return PyModule_AddObjectRef(module, name, _PyObject_CAST(type)); +} +#endif + +// bpo-40241 added PyObject_GC_IsTracked() to Python 3.9.0a6. +// bpo-4688 added _PyObject_GC_IS_TRACKED() to Python 2.7.0a2. +#if PY_VERSION_HEX < 0x030900A6 && !defined(PYPY_VERSION) +static inline int PyObject_GC_IsTracked(PyObject *obj) +{ + return (PyObject_IS_GC(obj) && _PyObject_GC_IS_TRACKED(obj)); +} +#endif + +// bpo-40241 added PyObject_GC_IsFinalized() to Python 3.9.0a6. +// bpo-18112 added _PyGCHead_FINALIZED() to Python 3.4.0 final. +#if PY_VERSION_HEX < 0x030900A6 && PY_VERSION_HEX >= 0x030400F0 && \ + !defined(PYPY_VERSION) +static inline int PyObject_GC_IsFinalized(PyObject *obj) +{ + PyGC_Head *gc = _Py_CAST(PyGC_Head *, obj) - 1; + return (PyObject_IS_GC(obj) && _PyGCHead_FINALIZED(gc)); +} +#endif + +// bpo-39573 added Py_IS_TYPE() to Python 3.9.0a4 +#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_IS_TYPE) +static inline int _Py_IS_TYPE(PyObject *ob, PyTypeObject *type) +{ + return Py_TYPE(ob) == type; +} + #define Py_IS_TYPE(ob, type) _Py_IS_TYPE(_PyObject_CAST(ob), type) +#endif + +// bpo-46906 added PyFloat_Pack2() and PyFloat_Unpack2() to Python 3.11a7. +// bpo-11734 added _PyFloat_Pack2() and _PyFloat_Unpack2() to Python 3.6.0b1. +// Python 3.11a2 moved _PyFloat_Pack2() and _PyFloat_Unpack2() to the internal +// C API: Python 3.11a2-3.11a6 versions are not supported. +#if 0x030600B1 <= PY_VERSION_HEX && PY_VERSION_HEX <= 0x030B00A1 && \ + !defined(PYPY_VERSION) +static inline int PyFloat_Pack2(double x, char *p, int le) +{ + return _PyFloat_Pack2(x, (unsigned char *)p, le); +} + +static inline double PyFloat_Unpack2(const char *p, int le) +{ + return _PyFloat_Unpack2((const unsigned char *)p, le); +} +#endif + +// bpo-46906 added PyFloat_Pack4(), PyFloat_Pack8(), PyFloat_Unpack4() and +// PyFloat_Unpack8() to Python 3.11a7. +// Python 3.11a2 moved _PyFloat_Pack4(), _PyFloat_Pack8(), _PyFloat_Unpack4() +// and _PyFloat_Unpack8() to the internal C API: Python 3.11a2-3.11a6 versions +// are not supported. +#if PY_VERSION_HEX <= 0x030B00A1 && !defined(PYPY_VERSION) +static inline int PyFloat_Pack4(double x, char *p, int le) +{ + return _PyFloat_Pack4(x, (unsigned char *)p, le); +} + +static inline int PyFloat_Pack8(double x, char *p, int le) +{ + return _PyFloat_Pack8(x, (unsigned char *)p, le); +} + +static inline double PyFloat_Unpack4(const char *p, int le) +{ + return _PyFloat_Unpack4((const unsigned char *)p, le); +} + +static inline double PyFloat_Unpack8(const char *p, int le) +{ + return _PyFloat_Unpack8((const unsigned char *)p, le); +} +#endif + +// gh-92154 added PyCode_GetCode() to Python 3.11.0b1 +#if PY_VERSION_HEX < 0x030B00B1 && !defined(PYPY_VERSION) +static inline PyObject *PyCode_GetCode(PyCodeObject *code) +{ + return Py_NewRef(code->co_code); +} +#endif + +// gh-95008 added PyCode_GetVarnames() to Python 3.11.0rc1 +#if PY_VERSION_HEX < 0x030B00C1 && !defined(PYPY_VERSION) +static inline PyObject *PyCode_GetVarnames(PyCodeObject *code) +{ + return Py_NewRef(code->co_varnames); +} +#endif + +// gh-95008 added PyCode_GetFreevars() to Python 3.11.0rc1 +#if PY_VERSION_HEX < 0x030B00C1 && !defined(PYPY_VERSION) +static inline PyObject *PyCode_GetFreevars(PyCodeObject *code) +{ + return Py_NewRef(code->co_freevars); +} +#endif + +// gh-95008 added PyCode_GetCellvars() to Python 3.11.0rc1 +#if PY_VERSION_HEX < 0x030B00C1 && !defined(PYPY_VERSION) +static inline PyObject *PyCode_GetCellvars(PyCodeObject *code) +{ + return Py_NewRef(code->co_cellvars); +} +#endif + +// Py_UNUSED() was added to Python 3.4.0b2. +#if PY_VERSION_HEX < 0x030400B2 && !defined(Py_UNUSED) + #if defined(__GNUC__) || defined(__clang__) + #define Py_UNUSED(name) _unused_##name __attribute__((unused)) + #else + #define Py_UNUSED(name) _unused_##name + #endif +#endif + +// gh-105922 added PyImport_AddModuleRef() to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A0 +static inline PyObject *PyImport_AddModuleRef(const char *name) +{ + return Py_XNewRef(PyImport_AddModule(name)); +} +#endif + +// gh-105927 added PyWeakref_GetRef() to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D0000 +static inline int PyWeakref_GetRef(PyObject *ref, PyObject **pobj) +{ + PyObject *obj; + if (ref != NULL && !PyWeakref_Check(ref)) { + *pobj = NULL; + PyErr_SetString(PyExc_TypeError, "expected a weakref"); + return -1; + } + obj = PyWeakref_GetObject(ref); + if (obj == NULL) { + // SystemError if ref is NULL + *pobj = NULL; + return -1; + } + if (obj == Py_None) { + *pobj = NULL; + return 0; + } + *pobj = Py_NewRef(obj); + return (*pobj != NULL); +} +#endif + +// bpo-36974 added PY_VECTORCALL_ARGUMENTS_OFFSET to Python 3.8b1 +#ifndef PY_VECTORCALL_ARGUMENTS_OFFSET + #define PY_VECTORCALL_ARGUMENTS_OFFSET \ + (_Py_CAST(size_t, 1) << (8 * sizeof(size_t) - 1)) +#endif + +// bpo-36974 added PyVectorcall_NARGS() to Python 3.8b1 +#if PY_VERSION_HEX < 0x030800B1 +static inline Py_ssize_t PyVectorcall_NARGS(size_t n) +{ + return n & ~PY_VECTORCALL_ARGUMENTS_OFFSET; +} +#endif + +// gh-105922 added PyObject_Vectorcall() to Python 3.9.0a4 +#if PY_VERSION_HEX < 0x030900A4 +static inline PyObject *PyObject_Vectorcall(PyObject *callable, + PyObject *const *args, + size_t nargsf, PyObject *kwnames) +{ + #if PY_VERSION_HEX >= 0x030800B1 && !defined(PYPY_VERSION) + // bpo-36974 added _PyObject_Vectorcall() to Python 3.8.0b1 + return _PyObject_Vectorcall(callable, args, nargsf, kwnames); + #else + PyObject *posargs = NULL, *kwargs = NULL; + PyObject *res; + Py_ssize_t nposargs, nkwargs, i; + + if (nargsf != 0 && args == NULL) { + PyErr_BadInternalCall(); + goto error; + } + if (kwnames != NULL && !PyTuple_Check(kwnames)) { + PyErr_BadInternalCall(); + goto error; + } + + nposargs = (Py_ssize_t)PyVectorcall_NARGS(nargsf); + if (kwnames) { + nkwargs = PyTuple_GET_SIZE(kwnames); + } + else { + nkwargs = 0; + } + + posargs = PyTuple_New(nposargs); + if (posargs == NULL) { + goto error; + } + if (nposargs) { + for (i = 0; i < nposargs; i++) { + PyTuple_SET_ITEM(posargs, i, Py_NewRef(*args)); + args++; + } + } + + if (nkwargs) { + kwargs = PyDict_New(); + if (kwargs == NULL) { + goto error; + } + + for (i = 0; i < nkwargs; i++) { + PyObject *key = PyTuple_GET_ITEM(kwnames, i); + PyObject *value = *args; + args++; + if (PyDict_SetItem(kwargs, key, value) < 0) { + goto error; + } + } + } + else { + kwargs = NULL; + } + + res = PyObject_Call(callable, posargs, kwargs); + Py_DECREF(posargs); + Py_XDECREF(kwargs); + return res; + +error: + Py_DECREF(posargs); + Py_XDECREF(kwargs); + return NULL; + #endif +} +#endif + +// gh-106521 added PyObject_GetOptionalAttr() and +// PyObject_GetOptionalAttrString() to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A1 +static inline int PyObject_GetOptionalAttr(PyObject *obj, PyObject *attr_name, + PyObject **result) +{ + // bpo-32571 added _PyObject_LookupAttr() to Python 3.7.0b1 + #if PY_VERSION_HEX >= 0x030700B1 && !defined(PYPY_VERSION) + return _PyObject_LookupAttr(obj, attr_name, result); + #else + *result = PyObject_GetAttr(obj, attr_name); + if (*result != NULL) { + return 1; + } + if (!PyErr_Occurred()) { + return 0; + } + if (PyErr_ExceptionMatches(PyExc_AttributeError)) { + PyErr_Clear(); + return 0; + } + return -1; + #endif +} + +static inline int PyObject_GetOptionalAttrString(PyObject *obj, + const char *attr_name, + PyObject **result) +{ + PyObject *name_obj; + int rc; + #if PY_VERSION_HEX >= 0x03000000 + name_obj = PyUnicode_FromString(attr_name); + #else + name_obj = PyString_FromString(attr_name); + #endif + if (name_obj == NULL) { + *result = NULL; + return -1; + } + rc = PyObject_GetOptionalAttr(obj, name_obj, result); + Py_DECREF(name_obj); + return rc; +} +#endif + +// gh-106307 added PyObject_GetOptionalAttr() and +// PyMapping_GetOptionalItemString() to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A1 +static inline int PyMapping_GetOptionalItem(PyObject *obj, PyObject *key, + PyObject **result) +{ + *result = PyObject_GetItem(obj, key); + if (*result) { + return 1; + } + if (!PyErr_ExceptionMatches(PyExc_KeyError)) { + return -1; + } + PyErr_Clear(); + return 0; +} + +static inline int PyMapping_GetOptionalItemString(PyObject *obj, + const char *key, + PyObject **result) +{ + PyObject *key_obj; + int rc; + #if PY_VERSION_HEX >= 0x03000000 + key_obj = PyUnicode_FromString(key); + #else + key_obj = PyString_FromString(key); + #endif + if (key_obj == NULL) { + *result = NULL; + return -1; + } + rc = PyMapping_GetOptionalItem(obj, key_obj, result); + Py_DECREF(key_obj); + return rc; +} +#endif + +// gh-108511 added PyMapping_HasKeyWithError() and +// PyMapping_HasKeyStringWithError() to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A1 +static inline int PyMapping_HasKeyWithError(PyObject *obj, PyObject *key) +{ + PyObject *res; + int rc = PyMapping_GetOptionalItem(obj, key, &res); + Py_XDECREF(res); + return rc; +} + +static inline int PyMapping_HasKeyStringWithError(PyObject *obj, + const char *key) +{ + PyObject *res; + int rc = PyMapping_GetOptionalItemString(obj, key, &res); + Py_XDECREF(res); + return rc; +} +#endif + +// gh-108511 added PyObject_HasAttrWithError() and +// PyObject_HasAttrStringWithError() to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A1 +static inline int PyObject_HasAttrWithError(PyObject *obj, PyObject *attr) +{ + PyObject *res; + int rc = PyObject_GetOptionalAttr(obj, attr, &res); + Py_XDECREF(res); + return rc; +} + +static inline int PyObject_HasAttrStringWithError(PyObject *obj, + const char *attr) +{ + PyObject *res; + int rc = PyObject_GetOptionalAttrString(obj, attr, &res); + Py_XDECREF(res); + return rc; +} +#endif + +// gh-106004 added PyDict_GetItemRef() and PyDict_GetItemStringRef() +// to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A1 +static inline int PyDict_GetItemRef(PyObject *mp, PyObject *key, + PyObject **result) +{ + #if PY_VERSION_HEX >= 0x03000000 + PyObject *item = PyDict_GetItemWithError(mp, key); + #else + PyObject *item = _PyDict_GetItemWithError(mp, key); + #endif + if (item != NULL) { + *result = Py_NewRef(item); + return 1; // found + } + if (!PyErr_Occurred()) { + *result = NULL; + return 0; // not found + } + *result = NULL; + return -1; +} + +static inline int PyDict_GetItemStringRef(PyObject *mp, const char *key, + PyObject **result) +{ + int res; + #if PY_VERSION_HEX >= 0x03000000 + PyObject *key_obj = PyUnicode_FromString(key); + #else + PyObject *key_obj = PyString_FromString(key); + #endif + if (key_obj == NULL) { + *result = NULL; + return -1; + } + res = PyDict_GetItemRef(mp, key_obj, result); + Py_DECREF(key_obj); + return res; +} +#endif + +// gh-106307 added PyModule_Add() to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A1 +static inline int PyModule_Add(PyObject *mod, const char *name, PyObject *value) +{ + int res = PyModule_AddObjectRef(mod, name, value); + Py_XDECREF(value); + return res; +} +#endif + +// gh-108014 added Py_IsFinalizing() to Python 3.13.0a1 +// bpo-1856 added _Py_Finalizing to Python 3.2.1b1. +// _Py_IsFinalizing() was added to PyPy 7.3.0. +#if (0x030201B1 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x030D00A1) && \ + (!defined(PYPY_VERSION_NUM) || PYPY_VERSION_NUM >= 0x7030000) +static inline int Py_IsFinalizing(void) +{ + #if PY_VERSION_HEX >= 0x030700A1 + // _Py_IsFinalizing() was added to Python 3.7.0a1. + return _Py_IsFinalizing(); + #else + return (_Py_Finalizing != NULL); + #endif +} +#endif + +// gh-108323 added PyDict_ContainsString() to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A1 +static inline int PyDict_ContainsString(PyObject *op, const char *key) +{ + PyObject *key_obj = PyUnicode_FromString(key); + if (key_obj == NULL) { + return -1; + } + int res = PyDict_Contains(op, key_obj); + Py_DECREF(key_obj); + return res; +} +#endif + +// gh-108445 added PyLong_AsInt() to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A1 +static inline int PyLong_AsInt(PyObject *obj) +{ + #ifdef PYPY_VERSION + long value = PyLong_AsLong(obj); + if (value == -1 && PyErr_Occurred()) { + return -1; + } + if (value < (long)INT_MIN || (long)INT_MAX < value) { + PyErr_SetString(PyExc_OverflowError, + "Python int too large to convert to C int"); + return -1; + } + return (int)value; + #else + return _PyLong_AsInt(obj); + #endif +} +#endif + +// gh-107073 added PyObject_VisitManagedDict() to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A1 +static inline int PyObject_VisitManagedDict(PyObject *obj, visitproc visit, + void *arg) +{ + PyObject **dict = _PyObject_GetDictPtr(obj); + if (*dict == NULL) { + return -1; + } + Py_VISIT(*dict); + return 0; +} + +static inline void PyObject_ClearManagedDict(PyObject *obj) +{ + PyObject **dict = _PyObject_GetDictPtr(obj); + if (*dict == NULL) { + return; + } + Py_CLEAR(*dict); +} +#endif + +// gh-108867 added PyThreadState_GetUnchecked() to Python 3.13.0a1 +// Python 3.5.2 added _PyThreadState_UncheckedGet(). +#if PY_VERSION_HEX >= 0x03050200 && PY_VERSION_HEX < 0x030D00A1 +static inline PyThreadState *PyThreadState_GetUnchecked(void) +{ + return _PyThreadState_UncheckedGet(); +} +#endif + +// gh-110289 added PyUnicode_EqualToUTF8() and PyUnicode_EqualToUTF8AndSize() +// to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A1 +static inline int PyUnicode_EqualToUTF8AndSize(PyObject *unicode, + const char *str, + Py_ssize_t str_len) +{ + Py_ssize_t len; + const void *utf8; + PyObject *exc_type, *exc_value, *exc_tb; + int res; + + // API cannot report errors so save/restore the exception + PyErr_Fetch(&exc_type, &exc_value, &exc_tb); + + // Python 3.3.0a1 added PyUnicode_AsUTF8AndSize() + #if PY_VERSION_HEX >= 0x030300A1 + if (PyUnicode_IS_ASCII(unicode)) { + utf8 = PyUnicode_DATA(unicode); + len = PyUnicode_GET_LENGTH(unicode); + } + else { + utf8 = PyUnicode_AsUTF8AndSize(unicode, &len); + if (utf8 == NULL) { + // Memory allocation failure. The API cannot report error, + // so ignore the exception and return 0. + res = 0; + goto done; + } + } + + if (len != str_len) { + res = 0; + goto done; + } + res = (memcmp(utf8, str, (size_t)len) == 0); + #else + PyObject *bytes = PyUnicode_AsUTF8String(unicode); + if (bytes == NULL) { + // Memory allocation failure. The API cannot report error, + // so ignore the exception and return 0. + res = 0; + goto done; + } + + #if PY_VERSION_HEX >= 0x03000000 + len = PyBytes_GET_SIZE(bytes); + utf8 = PyBytes_AS_STRING(bytes); + #else + len = PyString_GET_SIZE(bytes); + utf8 = PyString_AS_STRING(bytes); + #endif + if (len != str_len) { + Py_DECREF(bytes); + res = 0; + goto done; + } + + res = (memcmp(utf8, str, (size_t)len) == 0); + Py_DECREF(bytes); + #endif + +done: + PyErr_Restore(exc_type, exc_value, exc_tb); + return res; +} + +static inline int PyUnicode_EqualToUTF8(PyObject *unicode, const char *str) +{ + return PyUnicode_EqualToUTF8AndSize(unicode, str, (Py_ssize_t)strlen(str)); +} +#endif + +// gh-111138 added PyList_Extend() and PyList_Clear() to Python 3.13.0a2 +#if PY_VERSION_HEX < 0x030D00A2 +static inline int PyList_Extend(PyObject *list, PyObject *iterable) +{ + return PyList_SetSlice(list, PY_SSIZE_T_MAX, PY_SSIZE_T_MAX, iterable); +} + +static inline int PyList_Clear(PyObject *list) +{ + return PyList_SetSlice(list, 0, PY_SSIZE_T_MAX, NULL); +} +#endif + +// gh-111262 added PyDict_Pop() and PyDict_PopString() to Python 3.13.0a2 +#if PY_VERSION_HEX < 0x030D00A2 +static inline int PyDict_Pop(PyObject *dict, PyObject *key, PyObject **result) +{ + PyObject *value; + + if (!PyDict_Check(dict)) { + PyErr_BadInternalCall(); + if (result) { + *result = NULL; + } + return -1; + } + + // bpo-16991 added _PyDict_Pop() to Python 3.5.0b2. + // Python 3.6.0b3 changed _PyDict_Pop() first argument type to PyObject*. + // Python 3.13.0a1 removed _PyDict_Pop(). + #if defined(PYPY_VERSION) || PY_VERSION_HEX < 0x030500b2 || \ + PY_VERSION_HEX >= 0x030D0000 + value = PyObject_CallMethod(dict, "pop", "O", key); + #elif PY_VERSION_HEX < 0x030600b3 + value = _PyDict_Pop(_Py_CAST(PyDictObject *, dict), key, NULL); + #else + value = _PyDict_Pop(dict, key, NULL); + #endif + if (value == NULL) { + if (result) { + *result = NULL; + } + if (PyErr_Occurred() && !PyErr_ExceptionMatches(PyExc_KeyError)) { + return -1; + } + PyErr_Clear(); + return 0; + } + if (result) { + *result = value; + } + else { + Py_DECREF(value); + } + return 1; +} + +static inline int PyDict_PopString(PyObject *dict, const char *key, + PyObject **result) +{ + PyObject *key_obj = PyUnicode_FromString(key); + if (key_obj == NULL) { + if (result != NULL) { + *result = NULL; + } + return -1; + } + + int res = PyDict_Pop(dict, key_obj, result); + Py_DECREF(key_obj); + return res; +} +#endif + +#if PY_VERSION_HEX < 0x030200A4 +// Python 3.2.0a4 added Py_hash_t type +typedef Py_ssize_t Py_hash_t; +#endif + +// gh-111545 added Py_HashPointer() to Python 3.13.0a3 +#if PY_VERSION_HEX < 0x030D00A3 +static inline Py_hash_t Py_HashPointer(const void *ptr) +{ + #if PY_VERSION_HEX >= 0x030900A4 && !defined(PYPY_VERSION) + return _Py_HashPointer(ptr); + #else + return _Py_HashPointer(_Py_CAST(void *, ptr)); + #endif +} +#endif + +// Python 3.13a4 added a PyTime API. +// Use the private API added to Python 3.5. +#if PY_VERSION_HEX < 0x030D00A4 && PY_VERSION_HEX >= 0x03050000 +typedef _PyTime_t PyTime_t; + #define PyTime_MIN _PyTime_MIN + #define PyTime_MAX _PyTime_MAX + +static inline double PyTime_AsSecondsDouble(PyTime_t t) +{ + return _PyTime_AsSecondsDouble(t); +} + +static inline int PyTime_Monotonic(PyTime_t *result) +{ + return _PyTime_GetMonotonicClockWithInfo(result, NULL); +} + +static inline int PyTime_Time(PyTime_t *result) +{ + return _PyTime_GetSystemClockWithInfo(result, NULL); +} + +static inline int PyTime_PerfCounter(PyTime_t *result) +{ + #if PY_VERSION_HEX >= 0x03070000 && !defined(PYPY_VERSION) + return _PyTime_GetPerfCounterWithInfo(result, NULL); + #elif PY_VERSION_HEX >= 0x03070000 + // Call time.perf_counter_ns() and convert Python int object to PyTime_t. + // Cache time.perf_counter_ns() function for best performance. + static PyObject *func = NULL; + if (func == NULL) { + PyObject *mod = PyImport_ImportModule("time"); + if (mod == NULL) { + return -1; + } + + func = PyObject_GetAttrString(mod, "perf_counter_ns"); + Py_DECREF(mod); + if (func == NULL) { + return -1; + } + } + + PyObject *res = PyObject_CallNoArgs(func); + if (res == NULL) { + return -1; + } + long long value = PyLong_AsLongLong(res); + Py_DECREF(res); + + if (value == -1 && PyErr_Occurred()) { + return -1; + } + + Py_BUILD_ASSERT(sizeof(value) >= sizeof(PyTime_t)); + *result = (PyTime_t)value; + return 0; + #else + // Call time.perf_counter() and convert C double to PyTime_t. + // Cache time.perf_counter() function for best performance. + static PyObject *func = NULL; + if (func == NULL) { + PyObject *mod = PyImport_ImportModule("time"); + if (mod == NULL) { + return -1; + } + + func = PyObject_GetAttrString(mod, "perf_counter"); + Py_DECREF(mod); + if (func == NULL) { + return -1; + } + } + + PyObject *res = PyObject_CallNoArgs(func); + if (res == NULL) { + return -1; + } + double d = PyFloat_AsDouble(res); + Py_DECREF(res); + + if (d == -1.0 && PyErr_Occurred()) { + return -1; + } + + // Avoid floor() to avoid having to link to libm + *result = (PyTime_t)(d * 1e9); + return 0; + #endif +} + +#endif + +// gh-111389 added hash constants to Python 3.13.0a5. These constants were +// added first as private macros to Python 3.4.0b1 and PyPy 7.3.9. +#if (!defined(PyHASH_BITS) && \ + ((!defined(PYPY_VERSION) && PY_VERSION_HEX >= 0x030400B1) || \ + (defined(PYPY_VERSION) && PY_VERSION_HEX >= 0x03070000 && \ + PYPY_VERSION_NUM >= 0x07090000))) + #define PyHASH_BITS _PyHASH_BITS + #define PyHASH_MODULUS _PyHASH_MODULUS + #define PyHASH_INF _PyHASH_INF + #define PyHASH_IMAG _PyHASH_IMAG +#endif + +// gh-111545 added Py_GetConstant() and Py_GetConstantBorrowed() +// to Python 3.13.0a6 +#if PY_VERSION_HEX < 0x030D00A6 && !defined(Py_CONSTANT_NONE) + + #define Py_CONSTANT_NONE 0 + #define Py_CONSTANT_FALSE 1 + #define Py_CONSTANT_TRUE 2 + #define Py_CONSTANT_ELLIPSIS 3 + #define Py_CONSTANT_NOT_IMPLEMENTED 4 + #define Py_CONSTANT_ZERO 5 + #define Py_CONSTANT_ONE 6 + #define Py_CONSTANT_EMPTY_STR 7 + #define Py_CONSTANT_EMPTY_BYTES 8 + #define Py_CONSTANT_EMPTY_TUPLE 9 + +static inline PyObject *Py_GetConstant(unsigned int constant_id) +{ + static PyObject *constants[Py_CONSTANT_EMPTY_TUPLE + 1] = {NULL}; + + if (constants[Py_CONSTANT_NONE] == NULL) { + constants[Py_CONSTANT_NONE] = Py_None; + constants[Py_CONSTANT_FALSE] = Py_False; + constants[Py_CONSTANT_TRUE] = Py_True; + constants[Py_CONSTANT_ELLIPSIS] = Py_Ellipsis; + constants[Py_CONSTANT_NOT_IMPLEMENTED] = Py_NotImplemented; + + constants[Py_CONSTANT_ZERO] = PyLong_FromLong(0); + if (constants[Py_CONSTANT_ZERO] == NULL) { + goto fatal_error; + } + + constants[Py_CONSTANT_ONE] = PyLong_FromLong(1); + if (constants[Py_CONSTANT_ONE] == NULL) { + goto fatal_error; + } + + constants[Py_CONSTANT_EMPTY_STR] = PyUnicode_FromStringAndSize("", 0); + if (constants[Py_CONSTANT_EMPTY_STR] == NULL) { + goto fatal_error; + } + + constants[Py_CONSTANT_EMPTY_BYTES] = PyBytes_FromStringAndSize("", 0); + if (constants[Py_CONSTANT_EMPTY_BYTES] == NULL) { + goto fatal_error; + } + + constants[Py_CONSTANT_EMPTY_TUPLE] = PyTuple_New(0); + if (constants[Py_CONSTANT_EMPTY_TUPLE] == NULL) { + goto fatal_error; + } + // goto dance to avoid compiler warnings about Py_FatalError() + goto init_done; + + fatal_error: + // This case should never happen + Py_FatalError("Py_GetConstant() failed to get constants"); + } + +init_done: + if (constant_id <= Py_CONSTANT_EMPTY_TUPLE) { + return Py_NewRef(constants[constant_id]); + } + else { + PyErr_BadInternalCall(); + return NULL; + } +} + +static inline PyObject *Py_GetConstantBorrowed(unsigned int constant_id) +{ + PyObject *obj = Py_GetConstant(constant_id); + Py_XDECREF(obj); + return obj; +} +#endif + +// gh-114329 added PyList_GetItemRef() to Python 3.13.0a4 +#if PY_VERSION_HEX < 0x030D00A4 +static inline PyObject *PyList_GetItemRef(PyObject *op, Py_ssize_t index) +{ + PyObject *item = PyList_GetItem(op, index); + Py_XINCREF(item); + return item; +} +#endif + +// gh-114329 added PyList_GetItemRef() to Python 3.13.0a4 +#if PY_VERSION_HEX < 0x030D00A4 +static inline int PyDict_SetDefaultRef(PyObject *d, PyObject *key, + PyObject *default_value, + PyObject **result) +{ + PyObject *value; + if (PyDict_GetItemRef(d, key, &value) < 0) { + // get error + if (result) { + *result = NULL; + } + return -1; + } + if (value != NULL) { + // present + if (result) { + *result = value; + } + else { + Py_DECREF(value); + } + return 1; + } + + // missing: set the item + if (PyDict_SetItem(d, key, default_value) < 0) { + // set error + if (result) { + *result = NULL; + } + return -1; + } + if (result) { + *result = Py_NewRef(default_value); + } + return 0; +} +#endif + +#if PY_VERSION_HEX < 0x030D00B3 + #define Py_BEGIN_CRITICAL_SECTION(op) { + #define Py_END_CRITICAL_SECTION() } + #define Py_BEGIN_CRITICAL_SECTION2(a, b) { + #define Py_END_CRITICAL_SECTION2() } +#endif + +#if PY_VERSION_HEX < 0x030E0000 && PY_VERSION_HEX >= 0x03060000 && \ + !defined(PYPY_VERSION) +typedef struct PyUnicodeWriter PyUnicodeWriter; + +static inline void PyUnicodeWriter_Discard(PyUnicodeWriter *writer) +{ + _PyUnicodeWriter_Dealloc((_PyUnicodeWriter *)writer); + PyMem_Free(writer); +} + +static inline PyUnicodeWriter *PyUnicodeWriter_Create(Py_ssize_t length) +{ + if (length < 0) { + PyErr_SetString(PyExc_ValueError, "length must be positive"); + return NULL; + } + + const size_t size = sizeof(_PyUnicodeWriter); + PyUnicodeWriter *pub_writer = (PyUnicodeWriter *)PyMem_Malloc(size); + if (pub_writer == _Py_NULL) { + PyErr_NoMemory(); + return _Py_NULL; + } + _PyUnicodeWriter *writer = (_PyUnicodeWriter *)pub_writer; + + _PyUnicodeWriter_Init(writer); + if (_PyUnicodeWriter_Prepare(writer, length, 127) < 0) { + PyUnicodeWriter_Discard(pub_writer); + return NULL; + } + writer->overallocate = 1; + return pub_writer; +} + +static inline PyObject *PyUnicodeWriter_Finish(PyUnicodeWriter *writer) +{ + PyObject *str = _PyUnicodeWriter_Finish((_PyUnicodeWriter *)writer); + assert(((_PyUnicodeWriter *)writer)->buffer == NULL); + PyMem_Free(writer); + return str; +} + +static inline int PyUnicodeWriter_WriteChar(PyUnicodeWriter *writer, Py_UCS4 ch) +{ + if (ch > 0x10ffff) { + PyErr_SetString(PyExc_ValueError, + "character must be in range(0x110000)"); + return -1; + } + + return _PyUnicodeWriter_WriteChar((_PyUnicodeWriter *)writer, ch); +} + +static inline int PyUnicodeWriter_WriteStr(PyUnicodeWriter *writer, + PyObject *obj) +{ + PyObject *str = PyObject_Str(obj); + if (str == NULL) { + return -1; + } + + int res = _PyUnicodeWriter_WriteStr((_PyUnicodeWriter *)writer, str); + Py_DECREF(str); + return res; +} + +static inline int PyUnicodeWriter_WriteRepr(PyUnicodeWriter *writer, + PyObject *obj) +{ + PyObject *str = PyObject_Repr(obj); + if (str == NULL) { + return -1; + } + + int res = _PyUnicodeWriter_WriteStr((_PyUnicodeWriter *)writer, str); + Py_DECREF(str); + return res; +} + +static inline int PyUnicodeWriter_WriteUTF8(PyUnicodeWriter *writer, + const char *str, Py_ssize_t size) +{ + if (size < 0) { + size = (Py_ssize_t)strlen(str); + } + + PyObject *str_obj = PyUnicode_FromStringAndSize(str, size); + if (str_obj == _Py_NULL) { + return -1; + } + + int res = _PyUnicodeWriter_WriteStr((_PyUnicodeWriter *)writer, str_obj); + Py_DECREF(str_obj); + return res; +} + +static inline int PyUnicodeWriter_WriteWideChar(PyUnicodeWriter *writer, + const wchar_t *str, + Py_ssize_t size) +{ + if (size < 0) { + size = (Py_ssize_t)wcslen(str); + } + + PyObject *str_obj = PyUnicode_FromWideChar(str, size); + if (str_obj == _Py_NULL) { + return -1; + } + + int res = _PyUnicodeWriter_WriteStr((_PyUnicodeWriter *)writer, str_obj); + Py_DECREF(str_obj); + return res; +} + +static inline int PyUnicodeWriter_WriteSubstring(PyUnicodeWriter *writer, + PyObject *str, + Py_ssize_t start, + Py_ssize_t end) +{ + if (!PyUnicode_Check(str)) { + PyErr_Format(PyExc_TypeError, "expect str, not %T", str); + return -1; + } + if (start < 0 || start > end) { + PyErr_Format(PyExc_ValueError, "invalid start argument"); + return -1; + } + if (end > PyUnicode_GET_LENGTH(str)) { + PyErr_Format(PyExc_ValueError, "invalid end argument"); + return -1; + } + + return _PyUnicodeWriter_WriteSubstring((_PyUnicodeWriter *)writer, str, + start, end); +} + +static inline int PyUnicodeWriter_Format(PyUnicodeWriter *writer, + const char *format, ...) +{ + va_list vargs; + va_start(vargs, format); + PyObject *str = PyUnicode_FromFormatV(format, vargs); + va_end(vargs); + if (str == _Py_NULL) { + return -1; + } + + int res = _PyUnicodeWriter_WriteStr((_PyUnicodeWriter *)writer, str); + Py_DECREF(str); + return res; +} +#endif // PY_VERSION_HEX < 0x030E0000 + +// gh-116560 added PyLong_GetSign() to Python 3.14.0a0 +#if PY_VERSION_HEX < 0x030E00A0 +static inline int PyLong_GetSign(PyObject *obj, int *sign) +{ + if (!PyLong_Check(obj)) { + PyErr_Format(PyExc_TypeError, "expect int, got %s", + Py_TYPE(obj)->tp_name); + return -1; + } + + *sign = _PyLong_Sign(obj); + return 0; +} +#endif + +// gh-124502 added PyUnicode_Equal() to Python 3.14.0a0 +#if PY_VERSION_HEX < 0x030E00A0 +static inline int PyUnicode_Equal(PyObject *str1, PyObject *str2) +{ + if (!PyUnicode_Check(str1)) { + PyErr_Format(PyExc_TypeError, "first argument must be str, not %s", + Py_TYPE(str1)->tp_name); + return -1; + } + if (!PyUnicode_Check(str2)) { + PyErr_Format(PyExc_TypeError, "second argument must be str, not %s", + Py_TYPE(str2)->tp_name); + return -1; + } + + #if PY_VERSION_HEX >= 0x030d0000 && !defined(PYPY_VERSION) + PyAPI_FUNC(int) _PyUnicode_Equal(PyObject * str1, PyObject * str2); + + return _PyUnicode_Equal(str1, str2); + #elif PY_VERSION_HEX >= 0x03060000 && !defined(PYPY_VERSION) + return _PyUnicode_EQ(str1, str2); + #elif PY_VERSION_HEX >= 0x03090000 && defined(PYPY_VERSION) + return _PyUnicode_EQ(str1, str2); + #else + return (PyUnicode_Compare(str1, str2) == 0); + #endif +} +#endif + +// gh-121645 added PyBytes_Join() to Python 3.14.0a0 +#if PY_VERSION_HEX < 0x030E00A0 +static inline PyObject *PyBytes_Join(PyObject *sep, PyObject *iterable) +{ + return _PyBytes_Join(sep, iterable); +} +#endif + +#if PY_VERSION_HEX < 0x030E00A0 +static inline Py_hash_t Py_HashBuffer(const void *ptr, Py_ssize_t len) +{ + #if PY_VERSION_HEX >= 0x03000000 && !defined(PYPY_VERSION) + PyAPI_FUNC(Py_hash_t) _Py_HashBytes(const void *src, Py_ssize_t len); + + return _Py_HashBytes(ptr, len); + #else + Py_hash_t hash; + PyObject *bytes = PyBytes_FromStringAndSize((const char *)ptr, len); + if (bytes == NULL) { + return -1; + } + hash = PyObject_Hash(bytes); + Py_DECREF(bytes); + return hash; + #endif +} +#endif + +#if PY_VERSION_HEX < 0x030E00A0 +static inline int PyIter_NextItem(PyObject *iter, PyObject **item) +{ + iternextfunc tp_iternext; + + assert(iter != NULL); + assert(item != NULL); + + tp_iternext = Py_TYPE(iter)->tp_iternext; + if (tp_iternext == NULL) { + *item = NULL; + PyErr_Format(PyExc_TypeError, "expected an iterator, got '%s'", + Py_TYPE(iter)->tp_name); + return -1; + } + + if ((*item = tp_iternext(iter))) { + return 1; + } + if (!PyErr_Occurred()) { + return 0; + } + if (PyErr_ExceptionMatches(PyExc_StopIteration)) { + PyErr_Clear(); + return 0; + } + return -1; +} +#endif + +#if PY_VERSION_HEX < 0x030E00A0 +static inline PyObject *PyLong_FromInt32(int32_t value) +{ + Py_BUILD_ASSERT(sizeof(long) >= 4); + return PyLong_FromLong(value); +} + +static inline PyObject *PyLong_FromInt64(int64_t value) +{ + Py_BUILD_ASSERT(sizeof(long long) >= 8); + return PyLong_FromLongLong(value); +} + +static inline PyObject *PyLong_FromUInt32(uint32_t value) +{ + Py_BUILD_ASSERT(sizeof(unsigned long) >= 4); + return PyLong_FromUnsignedLong(value); +} + +static inline PyObject *PyLong_FromUInt64(uint64_t value) +{ + Py_BUILD_ASSERT(sizeof(unsigned long long) >= 8); + return PyLong_FromUnsignedLongLong(value); +} + +static inline int PyLong_AsInt32(PyObject *obj, int32_t *pvalue) +{ + Py_BUILD_ASSERT(sizeof(int) == 4); + int value = PyLong_AsInt(obj); + if (value == -1 && PyErr_Occurred()) { + return -1; + } + *pvalue = (int32_t)value; + return 0; +} + +static inline int PyLong_AsInt64(PyObject *obj, int64_t *pvalue) +{ + Py_BUILD_ASSERT(sizeof(long long) == 8); + long long value = PyLong_AsLongLong(obj); + if (value == -1 && PyErr_Occurred()) { + return -1; + } + *pvalue = (int64_t)value; + return 0; +} + +static inline int PyLong_AsUInt32(PyObject *obj, uint32_t *pvalue) +{ + Py_BUILD_ASSERT(sizeof(long) >= 4); + unsigned long value = PyLong_AsUnsignedLong(obj); + if (value == (unsigned long)-1 && PyErr_Occurred()) { + return -1; + } + #if SIZEOF_LONG > 4 + if ((unsigned long)UINT32_MAX < value) { + PyErr_SetString(PyExc_OverflowError, + "Python int too large to convert to C uint32_t"); + return -1; + } + #endif + *pvalue = (uint32_t)value; + return 0; +} + +static inline int PyLong_AsUInt64(PyObject *obj, uint64_t *pvalue) +{ + Py_BUILD_ASSERT(sizeof(long long) == 8); + unsigned long long value = PyLong_AsUnsignedLongLong(obj); + if (value == (unsigned long long)-1 && PyErr_Occurred()) { + return -1; + } + *pvalue = (uint64_t)value; + return 0; +} +#endif + +#ifdef __cplusplus +} +#endif +#endif // PYTHONCAPI_COMPAT diff --git a/src/main/aerospike.c b/src/main/aerospike.c index e0d3376a1b..273a63c793 100644 --- a/src/main/aerospike.c +++ b/src/main/aerospike.c @@ -43,7 +43,9 @@ #include #include -PyObject *py_global_hosts; +PyObject *py_global_hosts = NULL; + +// The following are only used when config["use_shm"] is True int counter = 0xA8000000; bool user_shm_key = false; @@ -577,10 +579,12 @@ PyMODINIT_FUNC PyInit_aerospike(void) Aerospike_Enable_Default_Logging(); +#ifndef Py_GIL_DISABLED py_global_hosts = PyDict_New(); if (py_global_hosts == NULL) { goto MODULE_CLEANUP_ON_ERROR; } +#endif unsigned long i = 0; int retval; @@ -622,6 +626,10 @@ PyMODINIT_FUNC PyInit_aerospike(void) } } +#ifdef Py_GIL_DISABLED + PyUnstable_Module_SetGIL(py_aerospike_module, Py_MOD_GIL_NOT_USED); +#endif + // Allows submodules to be imported using "import aerospike." // https://github.com/python/cpython/issues/87533#issuecomment-2373119452 PyObject *py_sys = PyImport_ImportModule("sys"); diff --git a/src/main/client/admin.c b/src/main/client/admin.c index 201e537b44..9db1ac21d7 100644 --- a/src/main/client/admin.c +++ b/src/main/client/admin.c @@ -1,3 +1,5 @@ +#include "pythoncapi_compat.h" + /******************************************************************************* * Copyright 2013-2021 Aerospike, Inc. * @@ -220,6 +222,8 @@ PyObject *AerospikeClient_Admin_Drop_User(AerospikeClient *self, PyObject *args, aerospike_drop_user(self->as, &err, admin_policy_p, user); Py_END_ALLOW_THREADS + // Assuming this is only used for deleting item in global hosts +#ifndef Py_GIL_DISABLED char *alias_to_search = NULL; alias_to_search = return_search_string(self->as); PyObject *py_persistent_item = NULL; @@ -231,6 +235,7 @@ PyObject *AerospikeClient_Admin_Drop_User(AerospikeClient *self, PyObject *args, } PyMem_Free(alias_to_search); alias_to_search = NULL; +#endif CLEANUP: @@ -414,6 +419,7 @@ PyObject *AerospikeClient_Admin_Change_Password(AerospikeClient *self, aerospike_change_password(self->as, &err, admin_policy_p, user, password); Py_END_ALLOW_THREADS +#ifndef Py_GIL_DISABLED char *alias_to_search = NULL; alias_to_search = return_search_string(self->as); PyObject *py_persistent_item = NULL; @@ -425,6 +431,7 @@ PyObject *AerospikeClient_Admin_Change_Password(AerospikeClient *self, } PyMem_Free(alias_to_search); alias_to_search = NULL; +#endif CLEANUP: @@ -610,7 +617,7 @@ PyObject *AerospikeClient_Admin_Revoke_Roles(AerospikeClient *self, goto CLEANUP; } - if (py_policy == Py_None) { + if (Py_IsNone(py_policy)) { py_policy = PyDict_New(); } @@ -1071,7 +1078,7 @@ PyObject *AerospikeClient_Admin_Set_Whitelist(AerospikeClient *self, goto CLEANUP; } } - else if (py_whitelist == Py_None) { + else if (Py_IsNone(py_whitelist)) { whitelist_size = 0; } else { diff --git a/src/main/client/batch_apply.c b/src/main/client/batch_apply.c index dcbfde5cde..ef96975bda 100644 --- a/src/main/client/batch_apply.c +++ b/src/main/client/batch_apply.c @@ -14,6 +14,7 @@ * limitations under the License. ******************************************************************************/ +#include "pythoncapi_compat.h" #include #include @@ -155,7 +156,13 @@ static PyObject *AerospikeClient_Batch_Apply_Invoke( } for (int i = 0; i < keys_size; i++) { - PyObject *py_key = PyList_GetItem(py_keys, i); + PyObject *py_key = PyList_GetItemRef(py_keys, i); + if (!py_key) { + PyErr_Clear(); + as_error_update(err, AEROSPIKE_ERR_CLIENT, + "Unable to get key at index %d", i); + goto CLEANUP; + } as_key *tmp_key = (as_key *)as_vector_get(&tmp_keys, i); if (!PyTuple_Check(py_key)) { diff --git a/src/main/client/batch_operate.c b/src/main/client/batch_operate.c index fe9e2539bf..3bcd3f9634 100644 --- a/src/main/client/batch_operate.c +++ b/src/main/client/batch_operate.c @@ -1,3 +1,5 @@ +#include "pythoncapi_compat.h" + /******************************************************************************* * Copyright 2013-2022 Aerospike, Inc. * @@ -161,7 +163,13 @@ static PyObject *AerospikeClient_Batch_Operate_Invoke( } for (int i = 0; i < ops_size; i++) { - PyObject *py_val = PyList_GetItem(py_ops, i); + PyObject *py_val = PyList_GetItemRef(py_ops, i); + if (!py_val) { + PyErr_Clear(); + as_error_update(err, AEROSPIKE_ERR_CLIENT, + "Unable to get operation at index %d", i); + goto CLEANUP; + } if (!PyDict_Check(py_val)) { as_error_update(err, AEROSPIKE_ERR_PARAM, @@ -183,7 +191,13 @@ static PyObject *AerospikeClient_Batch_Operate_Invoke( uint64_t processed_key_count = 0; for (int i = 0; i < keys_size; i++) { - PyObject *py_key = PyList_GetItem(py_keys, i); + PyObject *py_key = PyList_GetItemRef(py_keys, i); + if (!py_key) { + PyErr_Clear(); + as_error_update(err, AEROSPIKE_ERR_CLIENT, + "Unable to get key at index %d", i); + goto CLEANUP; + } as_key *tmp_key = (as_key *)as_vector_get(&tmp_keys, i); if (!PyTuple_Check(py_key)) { @@ -224,7 +238,7 @@ static PyObject *AerospikeClient_Batch_Operate_Invoke( } } - if (py_ttl == NULL || py_ttl == Py_None) { + if (py_ttl == NULL || Py_IsNone(py_ttl)) { // If ttl in this transaction's batch write policy isn't set, use the client config's default batch write // policy ttl ops.ttl = AS_RECORD_CLIENT_DEFAULT_TTL; @@ -373,7 +387,7 @@ PyObject *AerospikeClient_Batch_Operate(AerospikeClient *self, PyObject *args, goto error; } - if (py_policy_batch == Py_None) { + if (Py_IsNone(py_policy_batch)) { // Let C client choose the client config policy to use py_policy_batch = NULL; } diff --git a/src/main/client/batch_read.c b/src/main/client/batch_read.c index 2784a7fc4f..61ff748ddc 100644 --- a/src/main/client/batch_read.c +++ b/src/main/client/batch_read.c @@ -1,3 +1,4 @@ +#include "pythoncapi_compat.h" #include #include #include @@ -121,10 +122,15 @@ PyObject *AerospikeClient_BatchRead(AerospikeClient *self, PyObject *args, uint64_t processed_key_count = 0; for (int i = 0; i < keys_size; i++) { - PyObject *py_key = PyList_GetItem(py_keys, i); + PyObject *py_key = PyList_GetItemRef(py_keys, i); + if (!py_key) { + PyErr_Clear(); + as_error_update(&err, AEROSPIKE_ERR_CLIENT, + "Unable to get key at index %d", i); + goto CLEANUP2; + } as_key *tmp_key = (as_key *)as_vector_get(&tmp_keys, i); - Py_INCREF(py_key); if (!PyTuple_Check(py_key)) { as_error_update(&err, AEROSPIKE_ERR_PARAM, "key should be an aerospike key tuple"); @@ -235,7 +241,14 @@ PyObject *AerospikeClient_BatchRead(AerospikeClient *self, PyObject *args, filter_bins = (const char **)malloc(sizeof(char *) * bin_count); for (Py_ssize_t i = 0; i < bin_count; i++) { - PyObject *py_bin = PyList_GetItem(py_bins, i); + PyObject *py_bin = PyList_GetItemRef(py_bins, i); + if (!py_bin) { + PyErr_Clear(); + as_error_update(&err, AEROSPIKE_ERR_CLIENT, + "Unable to get python object at index %d.", + i); + goto CLEANUP5; + } if (PyUnicode_Check(py_bin)) { filter_bins[i] = PyUnicode_AsUTF8(py_bin); } diff --git a/src/main/client/batch_write.c b/src/main/client/batch_write.c index 96d1855d77..0bcef3fd4d 100644 --- a/src/main/client/batch_write.c +++ b/src/main/client/batch_write.c @@ -1,3 +1,5 @@ +#include "pythoncapi_compat.h" + /******************************************************************************* * Copyright 2013-2020 Aerospike, Inc. * @@ -39,7 +41,7 @@ { \ PyObject *py___policy = \ PyObject_GetAttrString(py_batch_record, FIELD_NAME_BATCH_POLICY); \ - if (py___policy != Py_None) { \ + if (!Py_IsNone(py___policy)) { \ as_exp *expr = NULL; \ as_exp *expr_p = expr; \ if (py___policy != NULL) { \ @@ -253,7 +255,7 @@ static PyObject *AerospikeClient_BatchWriteInvoke(AerospikeClient *self, !PyList_Size(py_ops_list)) { // batch Read can have None ops if it is using read_all_bins - if ((batch_type == BATCH_TYPE_READ && py_ops_list != Py_None) || + if ((batch_type == BATCH_TYPE_READ && !Py_IsNone(py_ops_list)) || batch_type == BATCH_TYPE_WRITE) { as_error_update(err, AEROSPIKE_ERR_PARAM, "py_ops_list is NULL or not a list, %s must be " @@ -278,7 +280,7 @@ static PyObject *AerospikeClient_BatchWriteInvoke(AerospikeClient *self, } Py_ssize_t py_ops_size = 0; - if (py_ops_list != NULL && py_ops_list != Py_None) { + if (py_ops_list != NULL && !Py_IsNone(py_ops_list)) { py_ops_size = PyList_Size(py_ops_list); } @@ -287,7 +289,7 @@ static PyObject *AerospikeClient_BatchWriteInvoke(AerospikeClient *self, as_operations *ops = NULL; if ((batch_type == AS_BATCH_READ || batch_type == AS_BATCH_WRITE) && - (py_ops_size || (py_meta != NULL && py_meta != Py_None))) { + (py_ops_size || (py_meta != NULL && !Py_IsNone(py_meta)))) { ops = as_operations_new(py_ops_size); garb->ops_to_free = ops; diff --git a/src/main/client/cdt_operation_utils.c b/src/main/client/cdt_operation_utils.c index b105db2875..e503e940f9 100644 --- a/src/main/client/cdt_operation_utils.c +++ b/src/main/client/cdt_operation_utils.c @@ -1,3 +1,5 @@ +#include "pythoncapi_compat.h" + #include #include "cdt_operation_utils.h" @@ -83,7 +85,7 @@ as_status get_asval(AerospikeClient *self, as_error *err, char *key, } /* If the value isn't required, None indicates that it isn't provided */ - if (py_val == Py_None && !required) { + if (Py_IsNone(py_val) && !required) { *val = NULL; return AEROSPIKE_OK; } diff --git a/src/main/client/close.c b/src/main/client/close.c index 2d1daadd16..7cb8bcf7b8 100644 --- a/src/main/client/close.c +++ b/src/main/client/close.c @@ -60,6 +60,7 @@ PyObject *AerospikeClient_Close(AerospikeClient *self, PyObject *args, goto CLEANUP; } + // This should always be disabled in no-GIL mode if (self->use_shared_connection) { alias_to_search = return_search_string(self->as); py_persistent_item = diff --git a/src/main/client/connect.c b/src/main/client/connect.c index 54f0e0cb26..e683412d1f 100644 --- a/src/main/client/connect.c +++ b/src/main/client/connect.c @@ -19,6 +19,7 @@ #include #include +#include "pythoncapi_compat.h" #include "client.h" #include "conversions.h" #include "global_hosts.h" diff --git a/src/main/client/operate.c b/src/main/client/operate.c index 2083a0f835..3a8b2abbea 100644 --- a/src/main/client/operate.c +++ b/src/main/client/operate.c @@ -36,6 +36,7 @@ #include "bit_operations.h" #include "hll_operations.h" #include "expression_operations.h" +#include "pythoncapi_compat.h" #include #include @@ -108,7 +109,7 @@ static inline bool isExprOp(int op); #define CONVERT_PY_CTX_TO_AS_CTX() \ if (get_cdt_ctx(self, err, &ctx, py_val, &ctx_in_use, static_pool, \ SERIALIZER_PYTHON) != AEROSPIKE_OK) { \ - return err->code; \ + break; \ } #define CONVERT_RANGE_TO_AS_VAL() \ @@ -164,7 +165,7 @@ PyObject *create_pylist(PyObject *py_list, long operation, PyObject *py_bin, int check_type(AerospikeClient *self, PyObject *py_value, int op, as_error *err) { if ((!PyLong_Check(py_value) && - strcmp(py_value->ob_type->tp_name, "aerospike.null")) && + strcmp(Py_TYPE(py_value)->tp_name, "aerospike.null")) && (op == AS_OPERATOR_TOUCH)) { as_error_update( err, AEROSPIKE_ERR_PARAM, @@ -172,7 +173,7 @@ int check_type(AerospikeClient *self, PyObject *py_value, int op, as_error *err) return 1; } else if ((!PyLong_Check(py_value) && (!PyFloat_Check(py_value)) && - strcmp(py_value->ob_type->tp_name, "aerospike.null")) && + strcmp(Py_TYPE(py_value)->tp_name, "aerospike.null")) && op == AS_OPERATOR_INCR) { as_error_update( err, AEROSPIKE_ERR_PARAM, @@ -181,7 +182,7 @@ int check_type(AerospikeClient *self, PyObject *py_value, int op, as_error *err) } else if ((!PyUnicode_Check(py_value) && !PyByteArray_Check(py_value) && !PyBytes_Check(py_value) && - strcmp(py_value->ob_type->tp_name, "aerospike.null")) && + strcmp(Py_TYPE(py_value)->tp_name, "aerospike.null")) && (op == AS_OPERATOR_APPEND || op == AS_OPERATOR_PREPEND)) { as_error_update(err, AEROSPIKE_ERR_PARAM, "Cannot concatenate 'str' and 'non-str' objects"); @@ -376,10 +377,12 @@ as_status add_op(AerospikeClient *self, as_error *err, PyObject *py_val, operation, SERIALIZER_PYTHON); } + Py_BEGIN_CRITICAL_SECTION(py_val); while (PyDict_Next(py_val, &pos, &key_op, &value)) { if (!PyUnicode_Check(key_op)) { - return as_error_update(err, AEROSPIKE_ERR_CLIENT, - "An operation key must be a string."); + as_error_update(err, AEROSPIKE_ERR_CLIENT, + "An operation key must be a string."); + break; } else { char *name = (char *)PyUnicode_AsUTF8(key_op); @@ -425,10 +428,14 @@ as_status add_op(AerospikeClient *self, as_error *err, PyObject *py_val, err, AEROSPIKE_ERR_PARAM, "Operation can contain only op, bin, index, key, val, " "return_type and map_policy keys"); - goto CLEANUP; + break; } } } + Py_END_CRITICAL_SECTION(); + if (err->code != AEROSPIKE_OK) { + goto CLEANUP; + } *op = operation; @@ -566,7 +573,7 @@ as_status add_op(AerospikeClient *self, as_error *err, PyObject *py_val, } else { if (!self->strict_types || - !strcmp(py_value->ob_type->tp_name, "aerospike.null")) { + !strcmp(Py_TYPE(py_value)->tp_name, "aerospike.null")) { as_operations *pointer_ops = ops; as_binop *binop = &pointer_ops->binops.entries[pointer_ops->binops.size++]; @@ -599,7 +606,7 @@ as_status add_op(AerospikeClient *self, as_error *err, PyObject *py_val, } else { if (!self->strict_types || - !strcmp(py_value->ob_type->tp_name, "aerospike.null")) { + !strcmp(Py_TYPE(py_value)->tp_name, "aerospike.null")) { as_operations *pointer_ops = ops; as_binop *binop = &pointer_ops->binops.entries[pointer_ops->binops.size++]; @@ -627,7 +634,7 @@ as_status add_op(AerospikeClient *self, as_error *err, PyObject *py_val, } else { if (!self->strict_types || - !strcmp(py_value->ob_type->tp_name, "aerospike.null")) { + !strcmp(Py_TYPE(py_value)->tp_name, "aerospike.null")) { as_operations *pointer_ops = ops; as_binop *binop = &pointer_ops->binops.entries[pointer_ops->binops.size++]; diff --git a/src/main/client/query.c b/src/main/client/query.c index fcf69ea8fe..7efbf6dcb4 100644 --- a/src/main/client/query.c +++ b/src/main/client/query.c @@ -1,3 +1,5 @@ +#include "pythoncapi_compat.h" + /******************************************************************************* * Copyright 2013-2021 Aerospike, Inc. * @@ -171,7 +173,7 @@ static int query_where_add(as_query **query, as_predicate_type predicate, return 1; } - if (py_val1 == Py_None || py_val2 == Py_None) { + if (Py_IsNone(py_val1) || Py_IsNone(py_val2)) { Py_XDECREF(py_ubin); as_error_update( err, AEROSPIKE_ERR_PARAM, diff --git a/src/main/client/sec_index.c b/src/main/client/sec_index.c index b3103d9354..98bd40114e 100644 --- a/src/main/client/sec_index.c +++ b/src/main/client/sec_index.c @@ -1,3 +1,5 @@ +#include "pythoncapi_compat.h" + /******************************************************************************* * Copyright 2013-2021 Aerospike, Inc. * @@ -566,7 +568,7 @@ static PyObject *createIndexWithDataAndCollectionType( py_ustr_set = PyUnicode_AsUTF8String(py_set); set_ptr = PyBytes_AsString(py_ustr_set); } - else if (py_set != Py_None) { + else if (!Py_IsNone(py_set)) { as_error_update(&err, AEROSPIKE_ERR_PARAM, "Set should be string, unicode or None"); goto CLEANUP; diff --git a/src/main/client/set_xdr_filter.c b/src/main/client/set_xdr_filter.c index 9c5723889b..d0bb06ba1d 100644 --- a/src/main/client/set_xdr_filter.c +++ b/src/main/client/set_xdr_filter.c @@ -1,3 +1,5 @@ +#include "pythoncapi_compat.h" + /******************************************************************************* * Copyright 2013-2021 Aerospike, Inc. * @@ -91,7 +93,7 @@ PyObject *AerospikeClient_SetXDRFilter(AerospikeClient *self, PyObject *args, } //convert filter to base64 - if (py_expression_filter == Py_None) { + if (Py_IsNone(py_expression_filter)) { base64_filter = (char *)DELETE_CURRENT_XDR_FILTER; } else { diff --git a/src/main/client/truncate.c b/src/main/client/truncate.c index 486e486361..7768e657b6 100644 --- a/src/main/client/truncate.c +++ b/src/main/client/truncate.c @@ -1,3 +1,5 @@ +#include "pythoncapi_compat.h" + /******************************************************************************* * Copyright 2013-2021 Aerospike, Inc. * @@ -118,7 +120,7 @@ PyObject *AerospikeClient_Truncate(AerospikeClient *self, PyObject *args, goto CLEANUP; } } - else if (py_set != Py_None) { + else if (!Py_IsNone(py_set)) { // If the set is none, this is fine as_error_update(&err, AEROSPIKE_ERR_PARAM, "Set must be None, or unicode or string type"); diff --git a/src/main/client/type.c b/src/main/client/type.c index 289d52cbd4..430838a388 100644 --- a/src/main/client/type.c +++ b/src/main/client/type.c @@ -1,3 +1,5 @@ +#include "pythoncapi_compat.h" + /******************************************************************************* * Copyright 2013-2021 Aerospike, Inc. * @@ -50,6 +52,7 @@ enum { INIT_COMPRESSION_ERR, INIT_POLICY_PARAM_ERR, INIT_INVALID_AUTHMODE_ERR, + INIT_USING_SHARED_MEMORY_WITH_NOGIL_ERR }; /******************************************************************************* @@ -745,7 +748,7 @@ static int AerospikeClient_Type_Init(AerospikeClient *self, PyObject *args, PyDict_GetItemString(py_config, "serialization"); if (py_serializer_option && PyTuple_Check(py_serializer_option)) { PyObject *py_serializer = PyTuple_GetItem(py_serializer_option, 0); - if (py_serializer && py_serializer != Py_None) { + if (py_serializer && !Py_IsNone(py_serializer)) { if (!PyCallable_Check(py_serializer)) { error_code = INIT_SERIALIZE_ERR; goto CONSTRUCTOR_ERROR; @@ -755,7 +758,7 @@ static int AerospikeClient_Type_Init(AerospikeClient *self, PyObject *args, self->user_serializer_call_info.callback = py_serializer; } PyObject *py_deserializer = PyTuple_GetItem(py_serializer_option, 1); - if (py_deserializer && py_deserializer != Py_None) { + if (py_deserializer && !Py_IsNone(py_deserializer)) { if (!PyCallable_Check(py_deserializer)) { error_code = INIT_DESERIALIZE_ERR; goto CONSTRUCTOR_ERROR; @@ -1027,7 +1030,13 @@ static int AerospikeClient_Type_Init(AerospikeClient *self, PyObject *args, PyObject *py_share_connect = PyDict_GetItemString(py_config, "use_shared_connection"); if (py_share_connect) { +#ifdef Py_GIL_DISABLED + /* code that only runs in the free-threaded build */ + error_code = INIT_USING_SHARED_MEMORY_WITH_NOGIL_ERR; + goto CONSTRUCTOR_ERROR; +#else self->use_shared_connection = PyObject_IsTrue(py_share_connect); +#endif } PyObject *py_send_bool_as = PyDict_GetItemString(py_config, "send_bool_as"); @@ -1183,6 +1192,12 @@ static int AerospikeClient_Type_Init(AerospikeClient *self, PyObject *args, "Specify valid auth_mode"); break; } + case INIT_USING_SHARED_MEMORY_WITH_NOGIL_ERR: { + as_error_update( + &constructor_err, AEROSPIKE_ERR_PARAM, + "Shared connection cannot be enabled with free threading mode"); + break; + } default: // If a generic error was caught during init, use this message as_error_update(&constructor_err, AEROSPIKE_ERR_PARAM, @@ -1337,7 +1352,7 @@ static void AerospikeClient_Type_Dealloc(PyObject *self) } } } - self->ob_type->tp_free((PyObject *)self); + Py_TYPE(self)->tp_free((PyObject *)self); } /******************************************************************************* diff --git a/src/main/conversions.c b/src/main/conversions.c index 071f78d8c6..f2203dce03 100644 --- a/src/main/conversions.c +++ b/src/main/conversions.c @@ -1,3 +1,5 @@ +#include "pythoncapi_compat.h" + /******************************************************************************* * Copyright 2013-2021 Aerospike, Inc. * @@ -223,8 +225,14 @@ as_status pyobject_to_as_privileges(as_error *err, PyObject *py_privileges, { as_error_reset(err); for (int i = 0; i < privileges_size; i++) { - PyObject *py_val = PyList_GetItem(py_privileges, i); - if (PyDict_Check(py_val)) { + PyObject *py_val = PyList_GetItemRef(py_privileges, i); + if (py_val == NULL) { + PyErr_Clear(); + as_error_update(err, AEROSPIKE_ERR_CLIENT, + "Unable to get privilege dictionary at index %d", + i); + } + else if (PyDict_Check(py_val)) { PyObject *py_dict_key = PyUnicode_FromString("code"); if (PyDict_Contains(py_val, py_dict_key)) { PyObject *py_code = NULL; @@ -257,6 +265,7 @@ as_status pyobject_to_as_privileges(as_error *err, PyObject *py_privileges, } Py_DECREF(py_dict_key); } + Py_XDECREF(py_val); } return err->code; } @@ -621,7 +630,12 @@ as_status pyobject_to_strArray(as_error *err, PyObject *py_list, char **arr, char *s; for (int i = 0; i < size; i++) { - PyObject *py_val = PyList_GetItem(py_list, i); + PyObject *py_val = PyList_GetItemRef(py_list, i); + if (!py_val) { + PyErr_Clear(); + as_error_update(err, AEROSPIKE_ERR_CLIENT, + "Unable to get python object at index %d", i); + } if (PyUnicode_Check(py_val)) { s = (char *)PyUnicode_AsUTF8(py_val); @@ -639,6 +653,7 @@ as_status pyobject_to_strArray(as_error *err, PyObject *py_list, char **arr, as_error_update(err, AEROSPIKE_ERR_CLIENT, "Item is not a string"); return err->code; } + Py_XDECREF(py_val); } return err->code; @@ -657,10 +672,17 @@ as_status pyobject_to_list(AerospikeClient *self, as_error *err, } for (int i = 0; i < size; i++) { - PyObject *py_val = PyList_GetItem(py_list, i); + PyObject *py_val = PyList_GetItemRef(py_list, i); + if (!py_val) { + PyErr_Clear(); + as_error_update(err, AEROSPIKE_ERR_CLIENT, + "Unable to get python object at index %d", i); + break; + } as_val *val = NULL; as_val_new_from_pyobject(self, err, py_val, &val, static_pool, serializer_type); + Py_DECREF(py_val); if (err->code != AEROSPIKE_OK) { break; } @@ -703,6 +725,7 @@ as_status pyobject_to_map(AerospikeClient *self, as_error *err, } } + Py_BEGIN_CRITICAL_SECTION(py_dict); while (PyDict_Next(py_dict, &pos, &py_key, &py_val)) { as_val *key = NULL; as_val *val = NULL; @@ -721,6 +744,7 @@ as_status pyobject_to_map(AerospikeClient *self, as_error *err, } as_map_set(*map, key, val); } + Py_END_CRITICAL_SECTION(); if (err->code != AEROSPIKE_OK) { as_map_destroy(*map); @@ -1099,6 +1123,11 @@ bool is_pyobj_correct_as_helpers_type(PyObject *obj, const char *expected_type_name, bool is_subclass_instance) { + if (strcmp(Py_TYPE(obj)->tp_name, expected_type_name)) { + // Expected class name does not match object's class name + return false; + } + if (obj->ob_type->tp_dict == NULL) { // Unable to get type's __module__ attribute. // In Python 3.12+, this would happen if obj was a native Python type @@ -1108,7 +1137,7 @@ bool is_pyobj_correct_as_helpers_type(PyObject *obj, } PyObject *py_module_name = - PyDict_GetItemString(obj->ob_type->tp_dict, "__module__"); + PyDict_GetItemString(Py_TYPE(obj)->tp_dict, "__module__"); if (!py_module_name) { // Class does not belong to any module return false; @@ -1253,7 +1282,7 @@ as_status as_val_new_from_pyobject(AerospikeClient *self, as_error *err, bytes->type = AS_BYTES_HLL; } } - else if (!strcmp(py_obj->ob_type->tp_name, "aerospike.Geospatial")) { + else if (!strcmp(Py_TYPE(py_obj)->tp_name, "aerospike.Geospatial")) { PyObject *py_parameter = PyUnicode_FromString("geo_data"); PyObject *py_data = PyObject_GenericGetAttr(py_obj, py_parameter); Py_DECREF(py_parameter); @@ -1294,7 +1323,7 @@ as_status as_val_new_from_pyobject(AerospikeClient *self, as_error *err, else if (Py_None == py_obj) { *val = as_val_reserve(&as_nil); } - else if (!strcmp(py_obj->ob_type->tp_name, "aerospike.null")) { + else if (!strcmp(Py_TYPE(py_obj)->tp_name, "aerospike.null")) { *val = (as_val *)as_val_reserve(&as_nil); } else if (AS_Matches_Classname(py_obj, AS_CDT_WILDCARD_NAME)) { @@ -1348,6 +1377,7 @@ as_status as_record_init_from_pyobject(AerospikeClient *self, as_error *err, const char *name; as_record_init(rec, size); + Py_BEGIN_CRITICAL_SECTION(py_rec); while (PyDict_Next(py_bins_dict, &pos, &py_bin_name, &py_bin_value)) { @@ -1355,27 +1385,29 @@ as_status as_record_init_from_pyobject(AerospikeClient *self, as_error *err, return as_error_update( err, AEROSPIKE_ERR_CLIENT, "A bin name must be a string or unicode string."); + break; } name = PyUnicode_AsUTF8(py_bin_name); if (!name) { - return as_error_update( - err, AEROSPIKE_ERR_CLIENT, - "Unable to convert unicode object to C string"); + as_error_update(err, AEROSPIKE_ERR_CLIENT, + "Unable to convert unicode object to C string"); + break; } if (self->strict_types) { if (strlen(name) > AS_BIN_NAME_MAX_LEN) { - return as_error_update( + as_error_update( err, AEROSPIKE_ERR_BIN_NAME, "A bin name should not exceed 15 characters limit"); + break; } } if (!py_bin_value) { // this should never happen, but if it did... - return as_error_update(err, AEROSPIKE_ERR_CLIENT, - "record is null"); + as_error_update(err, AEROSPIKE_ERR_CLIENT, "record is null"); + break; } as_val *val = NULL; @@ -1391,8 +1423,12 @@ as_status as_record_init_from_pyobject(AerospikeClient *self, as_error *err, "Unable to set key-value pair"); } } + Py_END_CRITICAL_SECTION(); + if (err->code != AEROSPIKE_OK) { + return err->code; + } - if (py_meta && py_meta != Py_None) { + if (py_meta && !Py_IsNone(py_meta)) { if (!PyDict_Check(py_meta)) { as_error_update(err, AEROSPIKE_ERR_PARAM, "meta must be a dictionary"); @@ -1512,7 +1548,7 @@ as_status pyobject_to_key(as_error *err, PyObject *py_keytuple, as_key *key) ns = (char *)PyUnicode_AsUTF8(py_ns); } - if (py_set && py_set != Py_None) { + if (py_set && !Py_IsNone(py_set)) { if (PyUnicode_Check(py_set)) { set = (char *)PyUnicode_AsUTF8(py_set); } @@ -1524,7 +1560,7 @@ as_status pyobject_to_key(as_error *err, PyObject *py_keytuple, as_key *key) as_key *returnResult = key; - if (py_key && py_key != Py_None) { + if (py_key && !Py_IsNone(py_key)) { // TODO: refactor using as_val_new_from_pyobject if (PyUnicode_Check(py_key)) { PyObject *py_ustr = PyUnicode_AsUTF8String(py_key); @@ -1565,7 +1601,7 @@ as_status pyobject_to_key(as_error *err, PyObject *py_keytuple, as_key *key) as_error_update(err, AEROSPIKE_ERR_PARAM, "key is invalid"); } } - else if (py_digest && py_digest != Py_None) { + else if (py_digest && !Py_IsNone(py_digest)) { if (PyByteArray_Check(py_digest)) { uint32_t sz = (uint32_t)PyByteArray_Size(py_digest); @@ -2389,7 +2425,7 @@ void initialize_bin_for_strictypes(AerospikeClient *self, as_error *err, ((as_val *)&binop_bin->value)->type = AS_UNKNOWN; binop_bin->valuep = (as_bin_value *)map; } - else if (!strcmp(py_value->ob_type->tp_name, "aerospike.Geospatial")) { + else if (!strcmp(Py_TYPE(py_value)->tp_name, "aerospike.Geospatial")) { PyObject *geo_data = PyObject_GetAttrString(py_value, "geo_data"); PyObject *geo_data_py_str = AerospikeGeospatial_DoDumps(geo_data, err); const char *geo_data_str = PyUnicode_AsUTF8(geo_data_py_str); @@ -2406,7 +2442,7 @@ void initialize_bin_for_strictypes(AerospikeClient *self, as_error *err, Py_XDECREF(geo_data_py_str); Py_XDECREF(geo_data); } - else if (!strcmp(py_value->ob_type->tp_name, "aerospike.null")) { + else if (!strcmp(Py_TYPE(py_value)->tp_name, "aerospike.null")) { ((as_val *)&binop_bin->value)->type = AS_UNKNOWN; binop_bin->valuep = (as_bin_value *)&as_nil; } @@ -2526,7 +2562,7 @@ as_status check_and_set_meta(PyObject *py_meta, as_operations *ops, ops->gen = gen; } } - else if (py_meta && (py_meta != Py_None)) { + else if (py_meta && (!Py_IsNone(py_meta))) { return as_error_update(err, AEROSPIKE_ERR_PARAM, "Metadata should be of type dictionary"); } @@ -2711,11 +2747,19 @@ as_status get_cdt_ctx(AerospikeClient *self, as_error *err, as_cdt_ctx *cdt_ctx, as_cdt_ctx_init(cdt_ctx, (int)py_list_size); for (int i = 0; i < py_list_size; i++) { - PyObject *py_val = PyList_GetItem(py_ctx, (Py_ssize_t)i); + PyObject *py_val = PyList_GetItemRef(py_ctx, (Py_ssize_t)i); + if (!py_val) { + PyErr_Clear(); + as_cdt_ctx_destroy(cdt_ctx); + return as_error_update( + err, AEROSPIKE_ERR_CLIENT, + "Unable to get python object at index %d", i); + } PyObject *id_temp = PyObject_GetAttrString(py_val, "id"); if (PyErr_Occurred()) { as_cdt_ctx_destroy(cdt_ctx); + Py_DECREF(py_val); return as_error_update(err, AEROSPIKE_ERR_PARAM, "Failed to convert %s, id", CTX_KEY); } @@ -2723,6 +2767,7 @@ as_status get_cdt_ctx(AerospikeClient *self, as_error *err, as_cdt_ctx *cdt_ctx, PyObject *value_temp = PyObject_GetAttrString(py_val, "value"); if (PyErr_Occurred()) { as_cdt_ctx_destroy(cdt_ctx); + Py_DECREF(py_val); return as_error_update(err, AEROSPIKE_ERR_PARAM, "Failed to convert %s, value", CTX_KEY); } @@ -2731,10 +2776,12 @@ as_status get_cdt_ctx(AerospikeClient *self, as_error *err, as_cdt_ctx *cdt_ctx, PyObject_GetAttrString(py_val, "extra_args"); if (PyErr_Occurred()) { as_cdt_ctx_destroy(cdt_ctx); + Py_DECREF(py_val); return as_error_update(err, AEROSPIKE_ERR_PARAM, "Failed to convert %s", CTX_KEY); } + Py_DECREF(py_val); uint64_t item_type = PyLong_AsUnsignedLongLong(id_temp); if (PyErr_Occurred()) { as_cdt_ctx_destroy(cdt_ctx); diff --git a/src/main/convert_expressions.c b/src/main/convert_expressions.c index b713ef8663..6486f485f5 100644 --- a/src/main/convert_expressions.c +++ b/src/main/convert_expressions.c @@ -1,3 +1,5 @@ +#include "pythoncapi_compat.h" + /******************************************************************************* * Copyright 2013-2020 Aerospike, Inc. * @@ -500,7 +502,7 @@ get_exp_val_from_pyval(AerospikeClient *self, as_static_pool *static_pool, as_exp_entry tmp_entry = as_exp_bytes(b, b_len); *new_entry = tmp_entry; } - else if (!strcmp(py_obj->ob_type->tp_name, "aerospike.Geospatial")) { + else if (!strcmp(Py_TYPE(py_obj)->tp_name, "aerospike.Geospatial")) { PyObject *py_parameter = PyUnicode_FromString("geo_data"); PyObject *py_data = PyObject_GenericGetAttr(py_obj, py_parameter); Py_DECREF(py_parameter); @@ -550,7 +552,7 @@ get_exp_val_from_pyval(AerospikeClient *self, as_static_pool *static_pool, as_exp_entry tmp_entry = as_exp_nil(); *new_entry = tmp_entry; } - else if (!strcmp(py_obj->ob_type->tp_name, "aerospike.null")) { + else if (!strcmp(Py_TYPE(py_obj)->tp_name, "aerospike.null")) { as_exp_entry tmp_entry = as_exp_nil(); *new_entry = tmp_entry; } @@ -1673,7 +1675,13 @@ as_status convert_exp_list(AerospikeClient *self, PyObject *py_exp_list, // Reset flag for next temp expr being built is_ctx_initialized = false; - py_expr_tuple = PyList_GetItem(py_exp_list, (Py_ssize_t)i); + py_expr_tuple = PyList_GetItemRef(py_exp_list, (Py_ssize_t)i); + if (!py_expr_tuple) { + PyErr_Clear(); + as_error_update(err, AEROSPIKE_ERR_CLIENT, + "Unable to get expression at index %d", i); + goto CLEANUP; + } if (!PyTuple_Check(py_expr_tuple) || PyTuple_Size(py_expr_tuple) != 4) { as_error_update( err, AEROSPIKE_ERR_PARAM, @@ -1692,7 +1700,7 @@ as_status convert_exp_list(AerospikeClient *self, PyObject *py_exp_list, } PyObject *rt_tmp = PyTuple_GetItem(py_expr_tuple, 1); - if (rt_tmp != Py_None) { + if (!Py_IsNone(rt_tmp)) { temp_expr.result_type = PyLong_AsLongLong(rt_tmp); if (temp_expr.result_type == -1 && PyErr_Occurred()) { as_error_update(err, AEROSPIKE_ERR_PARAM, @@ -1703,7 +1711,7 @@ as_status convert_exp_list(AerospikeClient *self, PyObject *py_exp_list, } temp_expr.pydict = PyTuple_GetItem(py_expr_tuple, 2); - if (temp_expr.pydict != Py_None) { + if (!Py_IsNone(temp_expr.pydict)) { if (!PyDict_Check(temp_expr.pydict)) { as_error_update(err, AEROSPIKE_ERR_PARAM, "Failed to get fixed dictionary from " diff --git a/src/main/exception.c b/src/main/exception.c index d39edf0411..fe661e43c8 100644 --- a/src/main/exception.c +++ b/src/main/exception.c @@ -1,3 +1,5 @@ +#include "pythoncapi_compat.h" + /******************************************************************************* * Copyright 2013-2021 Aerospike, Inc. * @@ -371,9 +373,11 @@ void remove_exception(as_error *err) Py_ssize_t pos = 0; PyObject *py_module_dict = PyModule_GetDict(py_exc_module); + Py_BEGIN_CRITICAL_SECTION(py_module_dict); while (PyDict_Next(py_module_dict, &pos, &py_key, &py_value)) { Py_DECREF(py_value); } + Py_END_CRITICAL_SECTION(); } // We have this as a separate method because both raise_exception and raise_exception_old need to use it @@ -418,10 +422,12 @@ void raise_exception_base(as_error *err, PyObject *py_as_key, PyObject *py_bin, PyObject *py_module_dict = PyModule_GetDict(py_exc_module); bool found = false; + // TODO: this is not atomic enough + Py_BEGIN_CRITICAL_SECTION(py_module_dict); while (PyDict_Next(py_module_dict, &pos, &py_unused, &py_exc_class)) { if (PyObject_HasAttrString(py_exc_class, "code")) { PyObject *py_code = PyObject_GetAttrString(py_exc_class, "code"); - if (py_code == Py_None) { + if (Py_IsNone(py_code)) { continue; } if (err->code == PyLong_AsLong(py_code)) { @@ -430,6 +436,8 @@ void raise_exception_base(as_error *err, PyObject *py_as_key, PyObject *py_bin, } } } + Py_END_CRITICAL_SECTION(); + // We haven't found the right exception, just use AerospikeError if (!found) { PyObject *base_exception = diff --git a/src/main/geospatial/type.c b/src/main/geospatial/type.c index e2ebb0616c..4be0948204 100644 --- a/src/main/geospatial/type.c +++ b/src/main/geospatial/type.c @@ -133,8 +133,7 @@ static int AerospikeGeospatial_Type_Init(AerospikeGeospatial *self, return 0; } -PyObject *AerospikeGeospatial_Type_Repr(self) -AerospikeGeospatial *self; +PyObject *AerospikeGeospatial_Type_Repr(PyObject *self) { PyObject *initresult = NULL, *py_return = NULL; char *new_repr_str = NULL; @@ -148,7 +147,8 @@ AerospikeGeospatial *self; goto CLEANUP; } - initresult = AerospikeGeospatial_DoDumps(self->geo_data, &err); + initresult = AerospikeGeospatial_DoDumps( + ((AerospikeGeospatial *)self)->geo_data, &err); if (!initresult) { as_error_update(&err, AEROSPIKE_ERR_CLIENT, "Unable to call get data in str format"); @@ -177,8 +177,7 @@ AerospikeGeospatial *self; return py_return; } -PyObject *AerospikeGeospatial_Type_Str(self) -AerospikeGeospatial *self; +PyObject *AerospikeGeospatial_Type_Str(PyObject *self) { PyObject *initresult = NULL; // Aerospike error object @@ -191,7 +190,8 @@ AerospikeGeospatial *self; goto CLEANUP; } - initresult = AerospikeGeospatial_DoDumps(self->geo_data, &err); + initresult = AerospikeGeospatial_DoDumps( + ((AerospikeGeospatial *)self)->geo_data, &err); if (!initresult) { as_error_update(&err, AEROSPIKE_ERR_CLIENT, "Unable to call get data in str format"); diff --git a/src/main/global_hosts/type.c b/src/main/global_hosts/type.c index 63594d9c50..44e4e90511 100644 --- a/src/main/global_hosts/type.c +++ b/src/main/global_hosts/type.c @@ -42,7 +42,7 @@ static PyObject *AerospikeGlobalHosts_Type_New(PyTypeObject *type, static void AerospikeGlobalHosts_Type_Dealloc(PyObject *self) { - PyObject_Del(self); + PyObject_Free(self); } /******************************************************************************* diff --git a/src/main/log.c b/src/main/log.c index e131b4695a..a52092a33b 100644 --- a/src/main/log.c +++ b/src/main/log.c @@ -29,8 +29,6 @@ #define __sync_fetch_and_add InterlockedExchangeAdd64 #endif -static AerospikeLogCallback user_callback; - PyObject *Aerospike_Set_Log_Level(PyObject *parent, PyObject *args, PyObject *kwds) { @@ -113,7 +111,8 @@ static bool log_cb(as_log_level level, const char *func, const char *file, va_end(ap); // Extract pyhton user callback - PyObject *py_callback = user_callback.callback; + // TODO: need aerospike module to access callback + PyObject *py_callback = user_callback; // User callback's argument list PyObject *py_arglist = NULL; @@ -164,9 +163,8 @@ PyObject *Aerospike_Set_Log_Handler(PyObject *parent, PyObject *args, &py_callback); if (py_callback && PyCallable_Check(py_callback)) { - // Store user callback - Py_INCREF(py_callback); - user_callback.callback = py_callback; + // Store user callback in aerospike module + PyObject_SetAttrString(parent, "__callback", py_callback); // Register callback to C-SDK as_log_set_callback((as_log_callback)log_cb); diff --git a/src/main/nullobject/type.c b/src/main/nullobject/type.c index 9a290ed65d..c16966c0aa 100644 --- a/src/main/nullobject/type.c +++ b/src/main/nullobject/type.c @@ -25,7 +25,7 @@ static PyObject *AerospikeNullObject_Type_New(PyTypeObject *parent, static void AerospikeNullObject_Type_Dealloc(AerospikeNullObject *self) { - PyObject_Del(self); + PyObject_Free(self); } /******************************************************************************* diff --git a/src/main/policy.c b/src/main/policy.c index 8a25d099d7..104ef18a3e 100644 --- a/src/main/policy.c +++ b/src/main/policy.c @@ -1,3 +1,5 @@ +#include "pythoncapi_compat.h" + /******************************************************************************* * Copyright 2013-2021 Aerospike, Inc. * @@ -41,7 +43,7 @@ #define POLICY_INIT(__policy) \ as_error_reset(err); \ - if (!py_policy || py_policy == Py_None) { \ + if (!py_policy || Py_IsNone(py_policy)) { \ return err->code; \ } \ if (!PyDict_Check(py_policy)) { \ @@ -67,9 +69,9 @@ return as_error_update(err, AEROSPIKE_ERR_CLIENT, \ "Unable to create Python unicode object"); \ } \ - PyObject *py_field = \ - PyDict_GetItemWithError(py_policy, py_field_name); \ - if (py_field == NULL && PyErr_Occurred()) { \ + PyObject *py_field = NULL; \ + int retval = PyDict_GetItemRef(py_policy, py_field_name, &py_field); \ + if (retval == -1) { \ PyErr_Clear(); \ Py_DECREF(py_field_name); \ return as_error_update( \ @@ -94,6 +96,7 @@ "%s is invalid", #__field); \ } \ } \ + Py_XDECREF(py_field); \ } #define POLICY_SET_EXPRESSIONS_FIELD() \ @@ -106,9 +109,10 @@ err, AEROSPIKE_ERR_CLIENT, \ "Unable to create Python unicode object"); \ } \ - PyObject *py_exp_list = \ - PyDict_GetItemWithError(py_policy, py_field_name); \ - if (py_exp_list == NULL && PyErr_Occurred()) { \ + PyObject *py_exp_list = NULL; \ + int retval = \ + PyDict_GetItemRef(py_policy, py_field_name, &py_exp_list); \ + if (retval == -1) { \ PyErr_Clear(); \ Py_DECREF(py_field_name); \ return as_error_update(err, AEROSPIKE_ERR_CLIENT, \ @@ -116,15 +120,17 @@ "from policy dictionary"); \ } \ Py_DECREF(py_field_name); \ - if (py_exp_list) { \ + if (retval == 1) { \ if (convert_exp_list(self, py_exp_list, &exp_list, err) == \ AEROSPIKE_OK) { \ policy->filter_exp = exp_list; \ *exp_list_p = exp_list; \ } \ else { \ + Py_DECREF(py_exp_list); \ return err->code; \ } \ + Py_DECREF(py_exp_list); \ } \ } \ } @@ -163,6 +169,7 @@ void set_scan_options(as_error *err, as_scan *scan_p, PyObject *py_options) PyObject *key = NULL, *value = NULL; Py_ssize_t pos = 0; int64_t val = 0; + Py_BEGIN_CRITICAL_SECTION(py_options); while (PyDict_Next(py_options, &pos, &key, &value)) { char *key_name = (char *)PyUnicode_AsUTF8(key); if (!PyUnicode_Check(key)) { @@ -203,6 +210,7 @@ void set_scan_options(as_error *err, as_scan *scan_p, PyObject *py_options) break; } } + Py_END_CRITICAL_SECTION(); } else { as_error_update(err, AEROSPIKE_ERR_PARAM, "Invalid option(type)"); @@ -213,7 +221,7 @@ as_status set_query_options(as_error *err, PyObject *query_options, as_query *query) { PyObject *no_bins_val = NULL; - if (!query_options || query_options == Py_None) { + if (!query_options || Py_IsNone(query_options)) { return AEROSPIKE_OK; } @@ -245,14 +253,14 @@ as_status pyobject_to_policy_admin(AerospikeClient *self, as_error *err, as_policy_admin *config_admin_policy) { - if (py_policy && py_policy != Py_None) { + if (py_policy && !Py_IsNone(py_policy)) { // Initialize Policy POLICY_INIT(as_policy_admin); } //Initialize policy with global defaults as_policy_admin_copy(config_admin_policy, policy); - if (py_policy && py_policy != Py_None) { + if (py_policy && !Py_IsNone(py_policy)) { // Set policy fields POLICY_SET_FIELD(timeout, uint32_t); } @@ -272,11 +280,11 @@ static inline void check_and_set_txn_field(as_error *err, "Unable to create Python string \"txn\""); return; } - PyObject *py_obj_txn = - PyDict_GetItemWithError(py_policy, py_txn_field_name); + PyObject *py_obj_txn = NULL; + int retval = PyDict_GetItemRef(py_policy, py_txn_field_name, &py_obj_txn); Py_DECREF(py_txn_field_name); if (py_obj_txn == NULL) { - if (PyErr_Occurred()) { + if (retval == -1) { PyErr_Clear(); as_error_update(err, AEROSPIKE_ERR_CLIENT, "Getting the transaction field from Python policy " @@ -333,14 +341,14 @@ as_status pyobject_to_policy_apply(AerospikeClient *self, as_error *err, as_policy_apply *config_apply_policy, as_exp *exp_list, as_exp **exp_list_p) { - if (py_policy && py_policy != Py_None) { + if (py_policy && !Py_IsNone(py_policy)) { // Initialize Policy POLICY_INIT(as_policy_apply); } //Initialize policy with global defaults as_policy_apply_copy(config_apply_policy, policy); - if (py_policy && py_policy != Py_None) { + if (py_policy && !Py_IsNone(py_policy)) { // Set policy fields as_status retval = pyobject_to_policy_base( self, err, py_policy, &policy->base, exp_list, exp_list_p); @@ -374,14 +382,14 @@ as_status pyobject_to_policy_info(as_error *err, PyObject *py_policy, as_policy_info **policy_p, as_policy_info *config_info_policy) { - if (py_policy && py_policy != Py_None) { + if (py_policy && !Py_IsNone(py_policy)) { // Initialize Policy POLICY_INIT(as_policy_info); } //Initialize policy with global defaults as_policy_info_copy(config_info_policy, policy); - if (py_policy && py_policy != Py_None) { + if (py_policy && !Py_IsNone(py_policy)) { // Set policy fields POLICY_SET_FIELD(timeout, uint32_t); POLICY_SET_FIELD(send_as_is, bool); @@ -406,14 +414,14 @@ as_status pyobject_to_policy_query(AerospikeClient *self, as_error *err, as_policy_query *config_query_policy, as_exp *exp_list, as_exp **exp_list_p) { - if (py_policy && py_policy != Py_None) { + if (py_policy && !Py_IsNone(py_policy)) { // Initialize Policy POLICY_INIT(as_policy_query); } //Initialize policy with global defaults as_policy_query_copy(config_query_policy, policy); - if (py_policy && py_policy != Py_None) { + if (py_policy && !Py_IsNone(py_policy)) { as_status retval = pyobject_to_policy_base( self, err, py_policy, &policy->base, exp_list, exp_list_p); if (retval != AEROSPIKE_OK) { @@ -446,7 +454,7 @@ as_status pyobject_to_policy_read(AerospikeClient *self, as_error *err, as_policy_read *config_read_policy, as_exp *exp_list, as_exp **exp_list_p) { - if (py_policy && py_policy != Py_None) { + if (py_policy && !Py_IsNone(py_policy)) { // Initialize Policy POLICY_INIT(as_policy_read); } @@ -454,7 +462,7 @@ as_status pyobject_to_policy_read(AerospikeClient *self, as_error *err, //Initialize policy with global defaults as_policy_read_copy(config_read_policy, policy); - if (py_policy && py_policy != Py_None) { + if (py_policy && !Py_IsNone(py_policy)) { // Set policy fields as_status retval = pyobject_to_policy_base( self, err, py_policy, &policy->base, exp_list, exp_list_p); @@ -491,14 +499,14 @@ as_status pyobject_to_policy_remove(AerospikeClient *self, as_error *err, as_policy_remove *config_remove_policy, as_exp *exp_list, as_exp **exp_list_p) { - if (py_policy && py_policy != Py_None) { + if (py_policy && !Py_IsNone(py_policy)) { // Initialize Policy POLICY_INIT(as_policy_remove); } //Initialize policy with global defaults as_policy_remove_copy(config_remove_policy, policy); - if (py_policy && py_policy != Py_None) { + if (py_policy && !Py_IsNone(py_policy)) { // Set policy fields as_status retval = pyobject_to_policy_base( self, err, py_policy, &policy->base, exp_list, exp_list_p); @@ -533,14 +541,14 @@ as_status pyobject_to_policy_scan(AerospikeClient *self, as_error *err, as_policy_scan *config_scan_policy, as_exp *exp_list, as_exp **exp_list_p) { - if (py_policy && py_policy != Py_None) { + if (py_policy && !Py_IsNone(py_policy)) { // Initialize Policy POLICY_INIT(as_policy_scan); } //Initialize policy with global defaults as_policy_scan_copy(config_scan_policy, policy); - if (py_policy && py_policy != Py_None) { + if (py_policy && !Py_IsNone(py_policy)) { // Set policy fields as_status retval = pyobject_to_policy_base( self, err, py_policy, &policy->base, exp_list, exp_list_p); @@ -572,14 +580,14 @@ as_status pyobject_to_policy_write(AerospikeClient *self, as_error *err, as_policy_write *config_write_policy, as_exp *exp_list, as_exp **exp_list_p) { - if (py_policy && py_policy != Py_None) { + if (py_policy && !Py_IsNone(py_policy)) { // Initialize Policy POLICY_INIT(as_policy_write); } //Initialize policy with global defaults as_policy_write_copy(config_write_policy, policy); - if (py_policy && py_policy != Py_None) { + if (py_policy && !Py_IsNone(py_policy)) { // Set policy fields as_status retval = pyobject_to_policy_base( self, err, py_policy, &policy->base, exp_list, exp_list_p); @@ -616,14 +624,14 @@ as_status pyobject_to_policy_operate(AerospikeClient *self, as_error *err, as_policy_operate *config_operate_policy, as_exp *exp_list, as_exp **exp_list_p) { - if (py_policy && py_policy != Py_None) { + if (py_policy && !Py_IsNone(py_policy)) { // Initialize Policy POLICY_INIT(as_policy_operate); } //Initialize policy with global defaults as_policy_operate_copy(config_operate_policy, policy); - if (py_policy && py_policy != Py_None) { + if (py_policy && !Py_IsNone(py_policy)) { // Set policy fields as_status retval = pyobject_to_policy_base( self, err, py_policy, &policy->base, exp_list, exp_list_p); @@ -664,14 +672,14 @@ as_status pyobject_to_policy_batch(AerospikeClient *self, as_error *err, as_policy_batch *config_batch_policy, as_exp *exp_list, as_exp **exp_list_p) { - if (py_policy && py_policy != Py_None) { + if (py_policy && !Py_IsNone(py_policy)) { // Initialize Policy POLICY_INIT(as_policy_batch); } //Initialize policy with global defaults as_policy_batch_copy(config_batch_policy, policy); - if (py_policy && py_policy != Py_None) { + if (py_policy && !Py_IsNone(py_policy)) { // Set policy fields as_status retval = pyobject_to_policy_base( self, err, py_policy, &policy->base, exp_list, exp_list_p); @@ -863,7 +871,7 @@ as_status pyobject_to_list_policy(as_error *err, PyObject *py_policy, long list_order = AS_LIST_UNORDERED; long flags = AS_LIST_WRITE_DEFAULT; - if (!py_policy || py_policy == Py_None) { + if (!py_policy || Py_IsNone(py_policy)) { return AEROSPIKE_OK; } @@ -873,7 +881,7 @@ as_status pyobject_to_list_policy(as_error *err, PyObject *py_policy, } py_val = PyDict_GetItemString(py_policy, "list_order"); - if (py_val && py_val != Py_None) { + if (py_val && !Py_IsNone(py_val)) { if (PyLong_Check(py_val)) { list_order = PyLong_AsLong(py_val); if (PyErr_Occurred()) { @@ -888,7 +896,7 @@ as_status pyobject_to_list_policy(as_error *err, PyObject *py_policy, } py_val = PyDict_GetItemString(py_policy, "write_flags"); - if (py_val && py_val != Py_None) { + if (py_val && !Py_IsNone(py_val)) { if (PyLong_Check(py_val)) { flags = PyLong_AsLong(py_val); if (PyErr_Occurred()) { @@ -915,7 +923,7 @@ as_status pyobject_to_hll_policy(as_error *err, PyObject *py_policy, as_hll_policy_init(hll_policy); PyObject *py_val = NULL; - if (!py_policy || py_policy == Py_None) { + if (!py_policy || Py_IsNone(py_policy)) { return AEROSPIKE_OK; } @@ -925,7 +933,7 @@ as_status pyobject_to_hll_policy(as_error *err, PyObject *py_policy, } py_val = PyDict_GetItemString(py_policy, "flags"); - if (py_val && py_val != Py_None) { + if (py_val && !Py_IsNone(py_val)) { if (PyLong_Check(py_val)) { flags = (int64_t)PyLong_AsLongLong(py_val); if (PyErr_Occurred()) { @@ -1132,7 +1140,7 @@ set_as_metrics_listeners_using_pyobject(as_error *err, PyObject *py_metricslisteners, as_metrics_listeners *listeners) { - if (!py_metricslisteners || py_metricslisteners == Py_None) { + if (!py_metricslisteners || Py_IsNone(py_metricslisteners)) { // Use default metrics writer callbacks that were set when initializing metrics policy return AEROSPIKE_OK; } @@ -1380,7 +1388,7 @@ int set_as_metrics_policy_using_pyobject(as_error *err, error: // udata would've been allocated if MetricsListener was successfully converted to C code - if (py_metrics_listeners && py_metrics_listeners != Py_None) { + if (py_metrics_listeners && !Py_IsNone(py_metrics_listeners)) { free_py_listener_data( (PyListenerData *)metrics_policy->metrics_listeners.udata); } diff --git a/src/main/policy_config.c b/src/main/policy_config.c index a1423e2f7a..ff095d14c8 100644 --- a/src/main/policy_config.c +++ b/src/main/policy_config.c @@ -1,3 +1,5 @@ +#include "pythoncapi_compat.h" + /******************************************************************************* * Copyright 2017-2021 Aerospike, Inc. * @@ -928,7 +930,7 @@ as_status set_optional_key(as_policy_key *target_ptr, PyObject *py_policy, } py_policy_val = PyDict_GetItemString(py_policy, name); - if (!py_policy_val || py_policy_val == Py_None) { + if (!py_policy_val || Py_IsNone(py_policy_val)) { return AEROSPIKE_OK; } @@ -950,7 +952,7 @@ as_status set_optional_replica(as_policy_replica *target_ptr, } py_policy_val = PyDict_GetItemString(py_policy, name); - if (!py_policy_val || py_policy_val == Py_None) { + if (!py_policy_val || Py_IsNone(py_policy_val)) { return AEROSPIKE_OK; } @@ -972,7 +974,7 @@ as_status set_optional_commit_level(as_policy_commit_level *target_ptr, } py_policy_val = PyDict_GetItemString(py_policy, name); - if (!py_policy_val || py_policy_val == Py_None) { + if (!py_policy_val || Py_IsNone(py_policy_val)) { return AEROSPIKE_OK; } @@ -994,7 +996,7 @@ as_status set_optional_ap_read_mode(as_policy_read_mode_ap *target_ptr, } py_policy_val = PyDict_GetItemString(py_policy, name); - if (!py_policy_val || py_policy_val == Py_None) { + if (!py_policy_val || Py_IsNone(py_policy_val)) { return AEROSPIKE_OK; } @@ -1016,7 +1018,7 @@ as_status set_optional_sc_read_mode(as_policy_read_mode_sc *target_ptr, } py_policy_val = PyDict_GetItemString(py_policy, name); - if (!py_policy_val || py_policy_val == Py_None) { + if (!py_policy_val || Py_IsNone(py_policy_val)) { return AEROSPIKE_OK; } @@ -1038,7 +1040,7 @@ as_status set_optional_gen(as_policy_gen *target_ptr, PyObject *py_policy, } py_policy_val = PyDict_GetItemString(py_policy, name); - if (!py_policy_val || py_policy_val == Py_None) { + if (!py_policy_val || Py_IsNone(py_policy_val)) { return AEROSPIKE_OK; } @@ -1060,7 +1062,7 @@ as_status set_optional_exists(as_policy_exists *target_ptr, PyObject *py_policy, } py_policy_val = PyDict_GetItemString(py_policy, name); - if (!py_policy_val || py_policy_val == Py_None) { + if (!py_policy_val || Py_IsNone(py_policy_val)) { return AEROSPIKE_OK; } diff --git a/src/main/query/apply.c b/src/main/query/apply.c index 7c5176a4ce..e5a919eac2 100644 --- a/src/main/query/apply.c +++ b/src/main/query/apply.c @@ -181,9 +181,9 @@ bool Illegal_UDF_Args_Check(PyObject *py_args) } else if (!(PyLong_Check(py_val) || PyFloat_Check(py_val) || PyBool_Check(py_val) || PyUnicode_Check(py_val) || - !strcmp(py_val->ob_type->tp_name, "aerospike.Geospatial") || + !strcmp(Py_TYPE(py_val)->tp_name, "aerospike.Geospatial") || PyByteArray_Check(py_val) || (Py_None == py_val) || - (!strcmp(py_val->ob_type->tp_name, "aerospike.null")) || + (!strcmp(Py_TYPE(py_val)->tp_name, "aerospike.null")) || AS_Matches_Classname(py_val, AS_CDT_WILDCARD_NAME) || AS_Matches_Classname(py_val, AS_CDT_INFINITE_NAME) || PyBytes_Check(py_val))) { diff --git a/src/main/query/type.c b/src/main/query/type.c index 6c4758ddca..95c64eedf9 100644 --- a/src/main/query/type.c +++ b/src/main/query/type.c @@ -1,3 +1,5 @@ +#include "pythoncapi_compat.h" + /******************************************************************************* * Copyright 2013-2021 Aerospike, Inc. * @@ -200,7 +202,7 @@ static int AerospikeQuery_Type_Init(AerospikeQuery *self, PyObject *args, if (PyUnicode_Check(py_set)) { set = (char *)PyUnicode_AsUTF8(py_set); } - else if (py_set != Py_None) { + else if (!Py_IsNone(py_set)) { as_error_update(&err, AEROSPIKE_ERR_PARAM, "Set should be string, unicode or None"); goto CLEANUP; diff --git a/src/main/scan/apply.c b/src/main/scan/apply.c index 0a8cb4fa74..5cd147f9e8 100644 --- a/src/main/scan/apply.c +++ b/src/main/scan/apply.c @@ -179,9 +179,9 @@ bool Scan_Illegal_UDF_Args_Check(PyObject *py_args) } else if (!(PyLong_Check(py_val) || PyFloat_Check(py_val) || PyUnicode_Check(py_val) || PyBool_Check(py_val) || - !strcmp(py_val->ob_type->tp_name, "aerospike.Geospatial") || + !strcmp(Py_TYPE(py_val)->tp_name, "aerospike.Geospatial") || PyByteArray_Check(py_val) || (Py_None == py_val) || - (!strcmp(py_val->ob_type->tp_name, "aerospike.null")) || + (!strcmp(Py_TYPE(py_val)->tp_name, "aerospike.null")) || AS_Matches_Classname(py_val, AS_CDT_WILDCARD_NAME) || AS_Matches_Classname(py_val, AS_CDT_INFINITE_NAME) || PyBytes_Check(py_val))) { diff --git a/test/new_tests/test_free_threading.py b/test/new_tests/test_free_threading.py new file mode 100644 index 0000000000..96fe1d813b --- /dev/null +++ b/test/new_tests/test_free_threading.py @@ -0,0 +1,51 @@ +import threading +import pytest +import time + +import aerospike +from aerospike_helpers.operations import operations +from .test_base_class import TestBaseClass + + +@pytest.mark.usefixtures("as_connection") +class TestFreeThreading: + def test_unsafe(self): + key = ("test", "demo", 1) + INIT_BIN_VALUE = 0 + BIN_NAME = "a" + BIN_VALUE_AMOUNT_TO_ADD_IN_EACH_THREAD = 1 + self.as_connection.put(key, bins={BIN_NAME: INIT_BIN_VALUE}) + + THREAD_COUNT = 10 + barrier = threading.Barrier(parties=THREAD_COUNT) + + ops = [ + operations.increment(bin_name=BIN_NAME, amount=BIN_VALUE_AMOUNT_TO_ADD_IN_EACH_THREAD) + ] + config = TestBaseClass.get_connection_config() + + def increment_bin(): + nonlocal barrier + barrier.wait() + nonlocal config + client = aerospike.client(config) + nonlocal key, ops + client.operate(key, ops) + + workers = [] + for _ in range(THREAD_COUNT): + workers.append(threading.Thread(target=increment_bin)) + + start = time.time_ns() + + for worker in workers: + worker.start() + + for worker in workers: + worker.join() + + end = time.time_ns() + + _, _, bins = self.as_connection.get(key) + assert bins[BIN_NAME] == INIT_BIN_VALUE + BIN_VALUE_AMOUNT_TO_ADD_IN_EACH_THREAD * THREAD_COUNT + print(f"Threads took {end - start} ns to run.")