Skip to content

Using PyThreadState_SetAsyncExc possibly causes deadlocks in downstream applications #845

Description

@Delgan

Hi.

I am the author of a Python library, and one user has reported bugs that occur when used in conjunction with Dramatiq (ref: Delgan/loguru#1335). Because this bug occurs sporadically, we haven't been able to identify a setup that reproduces it. However, the user investigated the issue and pinpointed a function in the Dramatic code that is likely related to the problem:

def _raise_thread_exception_cpython(thread_id, exception):
exctype = (exception if inspect.isclass(exception) else type(exception)).__name__
thread_id = thread_id_ctype(thread_id)
exception = ctypes.py_object(exception)
count = ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, exception)
if count == 0:
logger.critical("Failed to set exception (%s) in thread %r.", exctype, thread_id.value)
elif count > 1: # pragma: no cover
logger.critical("Exception (%s) was set in multiple threads. Undoing...", exctype)
ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, ctypes.c_long(0))

More precisely, PyThreadState_SetAsyncExc is suspicious. This function is used to raise an Exception in another thread. However, this function is dangerous. Indeed, this can interrupt the thread at any point between two Python bytecode. The problem is that threading.Lock isn't robust enough to handle this use case. In some cases, it can result with locks being never released by the target thread, despite usage of idiomatic pattern such as a context manager.

I shared these concerns with Python core maintainers that confirmed this risk when using PyThreadState_SetAsyncExc. You can see a minimal reproducible example causing deadlock here (combining threading.Lock with PyThreadState_SetAsyncExc).

I'm not sure if this is a known issue for the Dramatiq maintainers. So I'm opening this ticket to at least let you know about it and find out your recommendations about it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions