gh-150505: fix data race in Unpickler_set_memo under free-threading#150550
gh-150505: fix data race in Unpickler_set_memo under free-threading#150550AhmedMagdy9876 wants to merge 4 commits into
Conversation
194ca8e to
486a6e4
Compare
|
…ding Unpickler_set_memo modified self->memo and self->memo_size without any critical section, allowing concurrent threads to race on the same Unpickler instance. Wrap the memo population loop and the pointer swap in Py_BEGIN_CRITICAL_SECTION/Py_END_CRITICAL_SECTION to serialize access.
486a6e4 to
61839ca
Compare
|
I really feel getting ignored. Please read the contributions guidelines of our project first. |
Sharing Pickler or Unpickler instances across threads has no defined semantics. The internal state (memo, stream position, stack) forms a single coherent parse/serialize state that cannot be meaningfully used across threads simultaneously.
Documentation build overview
|
|
Most changes to Python require a NEWS entry. Add one using the blurb_it web app or the blurb command-line tool. If this change has little impact on Python users, wait for a maintainer to apply the |
|
@picnixz apologies, never meant to ignore you, genuinely missed your comments and saw them later:
|
|
@AhmedMagdy9876 I see you reverted a change that I assume fixed the segmentation fault, can I ask you why? The documentation change is looking good, but we probably want to prevent the segfault as well. |
|
@dpdani thanks for the feedback. The revert was based on the discussion in the linked issue (#150505). @kumaraditya303 questioned whether sharing Unpickler instances across threads should be a supported use case. A critical section fix would prevent the crash but wouldn't validate shared usage. |
|
@kumaraditya303 correct me if I'm wrong, but I didn't read your comment as implying to keep the segmentation fault? I think it makes sense to keep a stance similar to #133885: use locks to prevent segmentation faults, but still allow undefined output. I don't see why would like something different here. |
Problem
Unpickler_set_memomodifiedself->memoandself->memo_sizewithout any critical section. Under free-threading (--disable-gil), two threads assigning to.memoon the sameUnpicklerinstance concurrently could race: one thread could setself->memo = NULLwhile another was still indexing into it via_Unpickler_MemoPut, causing a NULL-pointer dereference and segfault.TSAN confirmed the race:
Fix
_Unpickler_MemoPutcalls) in a singlePy_BEGIN_CRITICAL_SECTION/Py_END_CRITICAL_SECTIONonself, so the entire assignment is atomic with respect to other threadsself->memoandself->memo_sizeunder the same critical sectionPy_XDECREFcan trigger arbitrary Python code and should not be called while holding the lockTesting
Verified with a TSAN build (
--disable-gil+-fsanitize=thread) using the reproducer from the issue. TSAN no longer reports a data race after this fix. Added a multithreaded test toLib/test/test_pickle.pyto catch regressions.Fixes #150505
_Unpickler_MemoPut(_pickle.c) fromUnpickler.memoassignment with free-threading #150505