diff --git a/Doc/c-api/capsule.rst b/Doc/c-api/capsule.rst index cdb8aa33e9fd325..4133d83cf77c649 100644 --- a/Doc/c-api/capsule.rst +++ b/Doc/c-api/capsule.rst @@ -157,3 +157,19 @@ Refer to :ref:`using-capsules` for more information on using these objects. ``NULL``. Return ``0`` on success. Return nonzero and set an exception on failure. + + +.. c:function:: int PyCapsule_SetTraverse(PyObject *capsule, traverseproc traverse_func, inquiry clear_func) + + Set a traverse and clear functions inside *capsule*. + + It can be used if the capsule contains Python objects which should be + visited (and cleared) by the garbage collector (:mod:`gc`). When this + function is called, the capsule is tracked by the GC. + + See :c:member:`~PyTypeObject.tp_traverse` and + :c:member:`~PyTypeObject.tp_clear` for the signature of these functions. + + Return ``0`` on success. Return nonzero and set an exception on failure. + + .. versionadded:: 3.13 diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat index ed415a4dc644a43..824cac7ba4ff169 100644 --- a/Doc/data/stable_abi.dat +++ b/Doc/data/stable_abi.dat @@ -65,6 +65,7 @@ function,PyCapsule_SetContext,3.2,, function,PyCapsule_SetDestructor,3.2,, function,PyCapsule_SetName,3.2,, function,PyCapsule_SetPointer,3.2,, +function,PyCapsule_SetTraverse,3.13,, var,PyCapsule_Type,3.2,, var,PyClassMethodDescr_Type,3.2,, function,PyCodec_BackslashReplaceErrors,3.2,, diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 8509e18a7d792e5..e8b8ef1c430271d 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -866,6 +866,10 @@ New Features :term:`shutting down `. (Contributed by Victor Stinner in :gh:`108014`.) +* Add :c:func:`PyCapsule_SetTraverse` function to set GC traverse and clear + functions on a capsule. + (Contributed by Victor Stinner in :gh:`108014`.) + Porting to Python 3.13 ---------------------- diff --git a/Include/pycapsule.h b/Include/pycapsule.h index 929a9a685259fb3..186e9fa20bf9990 100644 --- a/Include/pycapsule.h +++ b/Include/pycapsule.h @@ -48,6 +48,8 @@ PyAPI_FUNC(int) PyCapsule_SetName(PyObject *capsule, const char *name); PyAPI_FUNC(int) PyCapsule_SetContext(PyObject *capsule, void *context); +PyAPI_FUNC(int) PyCapsule_SetTraverse(PyObject *op, traverseproc traverse_func, inquiry clear_func); + PyAPI_FUNC(void *) PyCapsule_Import( const char *name, /* UTF-8 encoded string */ int no_block); diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py index 566d36a3f5ba11f..0654add52e9755f 100644 --- a/Lib/test/test_stable_abi_ctypes.py +++ b/Lib/test/test_stable_abi_ctypes.py @@ -98,6 +98,7 @@ def test_windows_feature_macros(self): "PyCapsule_SetDestructor", "PyCapsule_SetName", "PyCapsule_SetPointer", + "PyCapsule_SetTraverse", "PyCapsule_Type", "PyClassMethodDescr_Type", "PyCodec_BackslashReplaceErrors", diff --git a/Misc/NEWS.d/next/C API/2023-08-22-22-12-28.gh-issue-108014.Ey0BN1.rst b/Misc/NEWS.d/next/C API/2023-08-22-22-12-28.gh-issue-108014.Ey0BN1.rst new file mode 100644 index 000000000000000..f1a8fccda6e66af --- /dev/null +++ b/Misc/NEWS.d/next/C API/2023-08-22-22-12-28.gh-issue-108014.Ey0BN1.rst @@ -0,0 +1,2 @@ +Add :c:func:`PyCapsule_SetTraverse` function to set GC traverse and clear +functions on a capsule. Patch by Victor Stinner. diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml index 16d5c1a07ae3e22..7da0c16668b5d08 100644 --- a/Misc/stable_abi.toml +++ b/Misc/stable_abi.toml @@ -2450,3 +2450,5 @@ added = '3.13' [function.PyDict_GetItemStringRef] added = '3.13' +[function.PyCapsule_SetTraverse] + added = '3.13' diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c index bb5edc368decb34..5d6bcacaca5a234 100644 --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -7314,20 +7314,39 @@ os_init(void) } #endif +static int +sock_capi_traverse(PyObject *capsule, visitproc visit, void *arg) +{ + PySocketModule_APIObject *capi = PyCapsule_GetPointer(capsule, PySocket_CAPSULE_NAME); + assert(capi != NULL); + Py_VISIT(capi->Sock_Type); + return 0; +} + +static int +sock_capi_clear(PyObject *capsule) +{ + PySocketModule_APIObject *capi = PyCapsule_GetPointer(capsule, PySocket_CAPSULE_NAME); + assert(capi != NULL); + Py_CLEAR(capi->Sock_Type); + return 0; +} + static void -sock_free_api(PySocketModule_APIObject *capi) +sock_capi_free(PySocketModule_APIObject *capi) { - Py_DECREF(capi->Sock_Type); + Py_XDECREF(capi->Sock_Type); // sock_capi_free() can clear it Py_DECREF(capi->error); Py_DECREF(capi->timeout_error); PyMem_Free(capi); } static void -sock_destroy_api(PyObject *capsule) +sock_capi_destroy(PyObject *capsule) { void *capi = PyCapsule_GetPointer(capsule, PySocket_CAPSULE_NAME); - sock_free_api(capi); + assert(capi != NULL); + sock_capi_free(capi); } static PySocketModule_APIObject * @@ -7432,11 +7451,16 @@ socket_exec(PyObject *m) } PyObject *capsule = PyCapsule_New(capi, PySocket_CAPSULE_NAME, - sock_destroy_api); + sock_capi_destroy); if (capsule == NULL) { - sock_free_api(capi); + sock_capi_free(capi); goto error; } + if (PyCapsule_SetTraverse(capsule, sock_capi_traverse, sock_capi_clear) < 0) { + sock_capi_free(capi); + goto error; + } + if (PyModule_Add(m, PySocket_CAPI_NAME, capsule) < 0) { goto error; } diff --git a/Objects/capsule.c b/Objects/capsule.c index baaddb3f1f0849c..f2e6c431375f2d2 100644 --- a/Objects/capsule.c +++ b/Objects/capsule.c @@ -9,18 +9,28 @@ typedef struct { const char *name; void *context; PyCapsule_Destructor destructor; + traverseproc traverse_func; + inquiry clear_func; } PyCapsule; static int -_is_legal_capsule(PyCapsule *capsule, const char *invalid_capsule) +_is_legal_capsule(PyObject *op, const char *invalid_capsule) { - if (!capsule || !PyCapsule_CheckExact(capsule) || capsule->pointer == NULL) { - PyErr_SetString(PyExc_ValueError, invalid_capsule); - return 0; + if (!op || !PyCapsule_CheckExact(op)) { + goto error; + } + PyCapsule *capsule = (PyCapsule *)op; + + if (capsule->pointer == NULL) { + goto error; } return 1; + +error: + PyErr_SetString(PyExc_ValueError, invalid_capsule); + return 0; } #define is_legal_capsule(capsule, name) \ @@ -50,7 +60,7 @@ PyCapsule_New(void *pointer, const char *name, PyCapsule_Destructor destructor) return NULL; } - capsule = PyObject_New(PyCapsule, &PyCapsule_Type); + capsule = PyObject_GC_New(PyCapsule, &PyCapsule_Type); if (capsule == NULL) { return NULL; } @@ -59,15 +69,18 @@ PyCapsule_New(void *pointer, const char *name, PyCapsule_Destructor destructor) capsule->name = name; capsule->context = NULL; capsule->destructor = destructor; + capsule->traverse_func = NULL; + capsule->clear_func = NULL; + // Only track the capsule if PyCapsule_SetTraverse() is called return (PyObject *)capsule; } int -PyCapsule_IsValid(PyObject *o, const char *name) +PyCapsule_IsValid(PyObject *op, const char *name) { - PyCapsule *capsule = (PyCapsule *)o; + PyCapsule *capsule = (PyCapsule *)op; return (capsule != NULL && PyCapsule_CheckExact(capsule) && @@ -77,13 +90,12 @@ PyCapsule_IsValid(PyObject *o, const char *name) void * -PyCapsule_GetPointer(PyObject *o, const char *name) +PyCapsule_GetPointer(PyObject *op, const char *name) { - PyCapsule *capsule = (PyCapsule *)o; - - if (!is_legal_capsule(capsule, "PyCapsule_GetPointer")) { + if (!is_legal_capsule(op, "PyCapsule_GetPointer")) { return NULL; } + PyCapsule *capsule = (PyCapsule *)op; if (!name_matches(name, capsule->name)) { PyErr_SetString(PyExc_ValueError, "PyCapsule_GetPointer called with incorrect name"); @@ -95,52 +107,48 @@ PyCapsule_GetPointer(PyObject *o, const char *name) const char * -PyCapsule_GetName(PyObject *o) +PyCapsule_GetName(PyObject *op) { - PyCapsule *capsule = (PyCapsule *)o; - - if (!is_legal_capsule(capsule, "PyCapsule_GetName")) { + if (!is_legal_capsule(op, "PyCapsule_GetName")) { return NULL; } + PyCapsule *capsule = (PyCapsule *)op; return capsule->name; } PyCapsule_Destructor -PyCapsule_GetDestructor(PyObject *o) +PyCapsule_GetDestructor(PyObject *op) { - PyCapsule *capsule = (PyCapsule *)o; - - if (!is_legal_capsule(capsule, "PyCapsule_GetDestructor")) { + if (!is_legal_capsule(op, "PyCapsule_GetDestructor")) { return NULL; } + PyCapsule *capsule = (PyCapsule *)op; return capsule->destructor; } void * -PyCapsule_GetContext(PyObject *o) +PyCapsule_GetContext(PyObject *op) { - PyCapsule *capsule = (PyCapsule *)o; - - if (!is_legal_capsule(capsule, "PyCapsule_GetContext")) { + if (!is_legal_capsule(op, "PyCapsule_GetContext")) { return NULL; } + PyCapsule *capsule = (PyCapsule *)op; return capsule->context; } int -PyCapsule_SetPointer(PyObject *o, void *pointer) +PyCapsule_SetPointer(PyObject *op, void *pointer) { - PyCapsule *capsule = (PyCapsule *)o; - - if (!pointer) { - PyErr_SetString(PyExc_ValueError, "PyCapsule_SetPointer called with null pointer"); + if (!is_legal_capsule(op, "PyCapsule_SetPointer")) { return -1; } + PyCapsule *capsule = (PyCapsule *)op; - if (!is_legal_capsule(capsule, "PyCapsule_SetPointer")) { + if (!pointer) { + PyErr_SetString(PyExc_ValueError, "PyCapsule_SetPointer called with null pointer"); return -1; } @@ -150,13 +158,12 @@ PyCapsule_SetPointer(PyObject *o, void *pointer) int -PyCapsule_SetName(PyObject *o, const char *name) +PyCapsule_SetName(PyObject *op, const char *name) { - PyCapsule *capsule = (PyCapsule *)o; - - if (!is_legal_capsule(capsule, "PyCapsule_SetName")) { + if (!is_legal_capsule(op, "PyCapsule_SetName")) { return -1; } + PyCapsule *capsule = (PyCapsule *)op; capsule->name = name; return 0; @@ -164,13 +171,12 @@ PyCapsule_SetName(PyObject *o, const char *name) int -PyCapsule_SetDestructor(PyObject *o, PyCapsule_Destructor destructor) +PyCapsule_SetDestructor(PyObject *op, PyCapsule_Destructor destructor) { - PyCapsule *capsule = (PyCapsule *)o; - - if (!is_legal_capsule(capsule, "PyCapsule_SetDestructor")) { + if (!is_legal_capsule(op, "PyCapsule_SetDestructor")) { return -1; } + PyCapsule *capsule = (PyCapsule *)op; capsule->destructor = destructor; return 0; @@ -178,19 +184,36 @@ PyCapsule_SetDestructor(PyObject *o, PyCapsule_Destructor destructor) int -PyCapsule_SetContext(PyObject *o, void *context) +PyCapsule_SetContext(PyObject *op, void *context) { - PyCapsule *capsule = (PyCapsule *)o; - - if (!is_legal_capsule(capsule, "PyCapsule_SetContext")) { + if (!is_legal_capsule(op, "PyCapsule_SetContext")) { return -1; } + PyCapsule *capsule = (PyCapsule *)op; capsule->context = context; return 0; } +int +PyCapsule_SetTraverse(PyObject *op, traverseproc traverse_func, inquiry clear_func) +{ + if (!is_legal_capsule(op, "PyCapsule_SetTraverse")) { + return -1; + } + PyCapsule *capsule = (PyCapsule *)op; + + if (!PyObject_GC_IsTracked(op)) { + PyObject_GC_Track(op); + } + + capsule->traverse_func = traverse_func; + capsule->clear_func = clear_func; + return 0; +} + + void * PyCapsule_Import(const char *name, int no_block) { @@ -249,13 +272,14 @@ PyCapsule_Import(const char *name, int no_block) static void -capsule_dealloc(PyObject *o) +capsule_dealloc(PyObject *op) { - PyCapsule *capsule = (PyCapsule *)o; + PyCapsule *capsule = (PyCapsule *)op; + PyObject_GC_UnTrack(op); if (capsule->destructor) { - capsule->destructor(o); + capsule->destructor(op); } - PyObject_Free(o); + PyObject_GC_Del(op); } @@ -279,6 +303,29 @@ capsule_repr(PyObject *o) } +static int +capsule_traverse(PyCapsule *capsule, visitproc visit, void *arg) +{ + if (capsule->traverse_func) { + return capsule->traverse_func((PyObject*)capsule, visit, arg); + } + else { + return 0; + } +} + + +static int +capsule_clear(PyCapsule *capsule) +{ + if (capsule->clear_func) { + return capsule->clear_func((PyObject*)capsule); + } + else { + return 0; + } +} + PyDoc_STRVAR(PyCapsule_Type__doc__, "Capsule objects let you wrap a C \"void *\" pointer in a Python\n\ @@ -293,27 +340,14 @@ Python import mechanism to link to one another.\n\ PyTypeObject PyCapsule_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) - "PyCapsule", /*tp_name*/ - sizeof(PyCapsule), /*tp_basicsize*/ - 0, /*tp_itemsize*/ - /* methods */ - capsule_dealloc, /*tp_dealloc*/ - 0, /*tp_vectorcall_offset*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_as_async*/ - capsule_repr, /*tp_repr*/ - 0, /*tp_as_number*/ - 0, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ - 0, /*tp_hash*/ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - 0, /*tp_flags*/ - PyCapsule_Type__doc__ /*tp_doc*/ + .tp_name = "PyCapsule", + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, + .tp_basicsize = sizeof(PyCapsule), + .tp_dealloc = capsule_dealloc, + .tp_repr = capsule_repr, + .tp_doc = PyCapsule_Type__doc__, + .tp_traverse = (traverseproc)capsule_traverse, + .tp_clear = (inquiry)capsule_clear, }; diff --git a/PC/python3dll.c b/PC/python3dll.c index 64dfbba3e424a17..897bc99d7cb6b5a 100755 --- a/PC/python3dll.c +++ b/PC/python3dll.c @@ -133,6 +133,7 @@ EXPORT_FUNC(PyCapsule_SetContext) EXPORT_FUNC(PyCapsule_SetDestructor) EXPORT_FUNC(PyCapsule_SetName) EXPORT_FUNC(PyCapsule_SetPointer) +EXPORT_FUNC(PyCapsule_SetTraverse) EXPORT_FUNC(PyCFunction_Call) EXPORT_FUNC(PyCFunction_GetFlags) EXPORT_FUNC(PyCFunction_GetFunction)