-
Notifications
You must be signed in to change notification settings - Fork 413
Description
See also #1649
Using the latest version of Cython, PyAV builds and passes all tests on free-threaded Python 3.13.5 on my Mac if I force the GIL to be disabled. If I additionally run the tests many times in a per-test thread pool using pytest-run-parallel, I do see some failures in test_logging, but I think that's due to use of global logging state rather than real thread safety issues:
def test_threaded_captures() -> None:
av.logging.set_level(av.logging.VERBOSE)
with av.logging.Capture(local=True) as logs:
do_log("main")
thread = threading.Thread(target=do_log, args=("thread",))
thread.start()
thread.join()
assert (av.logging.INFO, "test", "main") in logs
E AssertionError: assert (32, 'test', 'main') in []
tests/test_logging.py:31: AssertionError
____________________________________ ERROR at call of test_global_captures ____________________________________
def test_global_captures() -> None:
av.logging.set_level(av.logging.VERBOSE)
with av.logging.Capture(local=False) as logs:
do_log("main")
thread = threading.Thread(target=do_log, args=("thread",))
thread.start()
thread.join()
assert (av.logging.INFO, "test", "main") in logs
E AssertionError: assert (32, 'test', 'main') in []
tests/test_logging.py:44: AssertionError
________________________________________ ERROR at call of test_repeats ________________________________________
def test_repeats() -> None:
av.logging.set_level(av.logging.VERBOSE)
with av.logging.Capture() as logs:
do_log("foo")
do_log("foo")
do_log("bar")
do_log("bar")
do_log("bar")
do_log("baz")
logs = [log for log in logs if log[1] == "test"]
assert logs == [
(av.logging.INFO, "test", "foo"),
(av.logging.INFO, "test", "foo"),
(av.logging.INFO, "test", "bar"),
(av.logging.INFO, "test", "bar (repeated 2 more times)"),
(av.logging.INFO, "test", "baz"),
]
E AssertionError: assert [(32, 'test',...test', 'baz')] == [(32, 'test',...test', 'baz')]
E At index 0 diff: (32, 'test', 'bar') != (32, 'test', 'foo')
E Left contains one more item: (32, 'test', 'baz')
E Use -v to get more diff
tests/test_logging.py:62: AssertionError
_________________________________________ ERROR at call of test_error _________________________________________
def test_error() -> None:
av.logging.set_level(av.logging.VERBOSE)
log = (av.logging.ERROR, "test", "This is a test.")
av.logging.log(*log)
try:
av.error.err_check(-errno.EPERM)
tests/test_logging.py:79:
av/error.pyx:385: in av.error.err_check
cpdef int err_check(int res, filename=None) except -1:
raise cls(code, message, filename, log)
E av.error.PermissionError: [Errno 1] Operation not permitted
av/error.pyx:424: PermissionError
During handling of the above exception, another exception occurred:
def test_error() -> None:
av.logging.set_level(av.logging.VERBOSE)
log = (av.logging.ERROR, "test", "This is a test.")
av.logging.log(*log)
try:
av.error.err_check(-errno.EPERM)
except av.error.PermissionError as e:
assert e.log == log
E AssertionError: assert None == (16, 'test', 'This is a test.')
E + where None = PermissionError(1, 'Operation not permitted').log
tests/test_logging.py:81: AssertionError
---------------------------------------------- Captured log call ----------------------------------------------
ERROR libav.test:test_logging.py:77 This is a test.
***************************************** pytest-run-parallel report ******************************************
All tests were run in parallel! 🎉
=========================================== short test summary info ===========================================
PARALLEL FAILED tests/test_logging.py::test_threaded_captures - AssertionError: assert (32, 'test', 'main') in []
PARALLEL FAILED tests/test_logging.py::test_global_captures - AssertionError: assert (32, 'test', 'main') in []
PARALLEL FAILED tests/test_logging.py::test_repeats - AssertionError: assert [(32, 'test',...test', 'baz')] == [(32, 'test',...test', 'baz')]
PARALLEL FAILED tests/test_logging.py::test_error - AssertionError: assert None == (16, 'test', 'This is a test.')
================================== 286 passed, 13 skipped, 4 errors in 7.62s ==================================
It looks like both test_logging.py and test_timeout.py have tests that create Python threads, but I don't see anything like a parallel processing stress test using multiprocessing or threading. I also don't see any thread safety guarantees in the docs.
What are the thread safety guarantees that you're interested in guaranteeing? Presumably it should be safe to simultaneously read from streams in multiple threads, but then is there any sort of protection from someone abusing threads to simultaneously read and mutate a libav stream? If not, is that also an issue on the GIL-enabled build?
Ultimately I'd like to add support for the free-threaded build following the guidance in the Cython documentation:
https://cython.readthedocs.io/en/latest/src/userguide/freethreading.html
I'd also like to enable free-threaded wheel builds in the cibuildwheel configuration.