You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
However, we must not make msgspec slower on regular GIL builds.
We really should figure out some reliable, continuous benchmarking solution. However, I'm currently out of ideas, short of spinning up ephemeral VMs on a dedicated machine.
Findings from TSAN and stress testing on a free-threaded build
I've been testing msgspec on free-threaded CPython 3.14t using the TSAN image from nascheme/cpython_sanity, with msgspec built using -fsanitize=thread and assertions enabled.
My fork: https://github.com/Tapanhaz/msgspec
The existing test suite produces no TSAN reports, but a separate stress suite that releases multiple threads simultaneously onto shared objects exposes several issues:
Union decoding uses an uninitialized StructInfo. Concurrent first use of shared Struct types can result in a NULL TypeNode (assert(type != NULL) in json_decode_struct_map_inner). I have a candidate fix using a StructInfo_wait_initialized() helper that is a no-op on GIL builds: fix-union-structinfo-wait.
JSON and msgpack dict encoding can produce corrupt output during concurrent mutation. Both read the dict length before entering the critical section. If the dict is emptied in between, JSON can overwrite its opening { with }, while msgpack can write a header count that doesn't match the number of items encoded. I have a candidate fix that encodes a PyDict_Copy snapshot on free-threaded builds, leaving GIL builds unchanged: fix-dict-encode-snapshot.
Other potential stale-length and borrowed-reference issues, similar to the crash fixed in Fix a crash in mpack_encode_list on a FT build #1252, appear in json_encode_sequence, encode_lines, to_builtins_list, convert_seq_to_list, and struct slot reads (Struct_get_index_noerror). I have TSAN reports and stack traces for these. A PyList_GetSlice snapshot may be an alternative to lock-and-recheck for lists.
A separate bug also affects GIL builds. A failed conversion involving mutually recursive types can leave partially constructed type information cached. A subsequent Decoder(B) may then succeed, and decoding can segfault. I reproduced this on 0.22.0 and will file it separately with a reproducer.
I'm also preparing a report-only TSAN CI job so these issues can be investigated without blocking CI: TSAN CI branch.
Activity
provinzkraut commentedon Oct 12, 2026
We really should figure out some reliable, continuous benchmarking solution. However, I'm currently out of ideas, short of spinning up ephemeral VMs on a dedicated machine.
Tapanhaz commentedon Oct 17, 2026
Findings from TSAN and stress testing on a free-threaded build
I've been testing msgspec on free-threaded CPython 3.14t using the TSAN image from nascheme/cpython_sanity, with msgspec built using -fsanitize=thread and assertions enabled.
My fork: https://github.com/Tapanhaz/msgspec
The existing test suite produces no TSAN reports, but a separate stress suite that releases multiple threads simultaneously onto shared objects exposes several issues:
mpack_encode_liston a FT build #1252, appear in json_encode_sequence, encode_lines, to_builtins_list, convert_seq_to_list, and struct slot reads (Struct_get_index_noerror). I have TSAN reports and stack traces for these. A PyList_GetSlice snapshot may be an alternative to lock-and-recheck for lists.I'm also preparing a report-only TSAN CI job so these issues can be investigated without blocking CI: TSAN CI branch.
ci: add a ThreadSanitizer job for free-threaded builds (refs msgspec#…
ci: add a ThreadSanitizer job for free-threaded builds (refs msgspec#…