1

I would like to register a Lock acquisition in Python. In my context, I cannot inherit from _thread.LockType and override its acquire method.

Hence, I'm trying to decorate _thread.LockType.acquire with my own function.

Thanks to this post Extension method for python built-in types, I've managed to bypass the mappingproxy that is _thread.LockType.__dict__ with the following code:

import ctypes
from ctypes import pythonapi as api
from ctypes import py_object
from _thread import LockType

class PyObject(ctypes.Structure):
    """
        To quote https://docs.python.org/3/c-api/structures.html#c.PyObject,
        "all object type are extensions of this type".
    
        It contains at least two members:
            - 'ob_refcnt' of type Py_ssize_t which is the reference counter
            - 'ob_type' of type PyTypeObject which is the type of the Python Object

        Its members are represented by a ctypes.Structure, whose member _fields_ is
        a list of (field_name: str, field_type: PyTypeObject)
    """
    pass


"""
From https://docs.python.org/3/c-api/intro.html#c.Py_ssize_t,
'a signed integral type such as sizeof(Py_ssize_t) == sizeof(size_t) in C'
"""
Py_ssize_t = ctypes.sizeof(ctypes.c_voidp)
Py_ssize_t = ctypes.c_int64 if ctypes.sizeof(ctypes.c_int64) == Py_ssize_t else ctypes.c_int32

PyObject._fields_ = [
    ('ob_refcnt', Py_ssize_t),
    ('ob_type', ctypes.POINTER(PyObject))
]

class SlotsPointer(PyObject):
    _fields_ = [('dict', ctypes.POINTER(PyObject))]

mapping_proxy = getattr(LockType, '__dict__')
proxy_pointer = Dict.from_address(id(mapping_proxy))

namespace = {}
api.PyDict_SetItem(py_object(namespace), py_object(LockType.__name__), proxy_pointer.dict)

lck_dict = namespace[LockType.__name__]
lck_dict['acquire'] = my_acquire_decorator # a decorator whose signature matches with acquire's

This chunk of code works perfectly as long as I never, NEVER, use _thread.LockType.acquire explicitly.

Let's say that I need to save the original acquire function (in order to use it in my decorator), with the really exotic statement original_acquire = LockType.acquire. Then the decorator is completely discarded and never called, as if the line lck_dict['acquire'] = my_acquire_decorator was never there.

Any idea as to why ? I'm not even sure it is due to references of the original being kept around since a call to sys.getrefcount(LockType.acquire) suffices to ignore the change.

I tried to

  • Disable the garbage collector automatic collection
  • Save the original
  • Artificially clear its references counter
  • Register the decorator
  • Increment the original's ref counter
  • Enable back the gc

It wasn't the wisest choice, as the Segfault told me.

vultkayn
  • 189
  • 1
  • 8
  • This doesn't really answer your question, but if you're just trying to register lock acquisitions, why not wrap the lock in some containing class that provides a lock-like interface and does what you want? – mackorone Jun 02 '22 at 12:40
  • As I said, I cannot do that. This trick has to be totally transparent to the user, and must work with any Python program, it being specifically designed to be analyzed by my code, or not. Hence I cannot introduce a wrapper class, that is the main constraint. – vultkayn Jun 02 '22 at 12:45

0 Answers0