Skip to content

Fix Free-Threading support #1249

Open
Open

Description

@sobolevn

Current msgspec has lots of places with FT bug, data races, missing atomics, etc.
We need to fix them, so msgspec would work on FT builds correctly.

However, we must not make msgspec slower on regular GIL builds.

This is a meta-issue, report your findings here, propose tooling and CI improvements.

Activity

  1. provinzkraut commented on Oct 12, 2026

    @provinzkraut
    Member

    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.

  2. Tapanhaz commented on Oct 17, 2026

    @Tapanhaz

    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:

    1. 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.
    2. 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.
    3. 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.
    4. 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions