gh-150175: Fix ThreadingMock call_count race condition#150176
Conversation
|
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 |
|
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 |
ThreadingMock._increment_mock_call() was not thread-safe. Multiple threads calling the mock simultaneously could lose increments due to race conditions on call_count and other attributes. Fix by overriding _increment_mock_call in ThreadingMixin and wrapping it with the existing _mock_calls_events_lock.
d444e47 to
8e08b9c
Compare
|
Thanks @saisneha196 for the PR, and @cjw296 for merging it 🌮🎉.. I'm working now to backport this PR to: 3.14. |
|
Thanks @saisneha196 for the PR, and @cjw296 for merging it 🌮🎉.. I'm working now to backport this PR to: 3.13. |
|
Thanks @saisneha196 for the PR, and @cjw296 for merging it 🌮🎉.. I'm working now to backport this PR to: 3.15. |
|
GH-150180 is a backport of this pull request to the 3.13 branch. |
|
GH-150181 is a backport of this pull request to the 3.14 branch. |
|
GH-150182 is a backport of this pull request to the 3.15 branch. |
ThreadingMock._increment_mock_call() was not thread-safe causing
intermittent ptest failures on qemuarm64:
FAIL: test_call_count_thread_safe
AssertionError: 983 != 1000
Each time a mock is called, Python reads the call_count, adds 1,
and writes it back. When multiple threads do this simultaneously,
some increments get lost because two threads can read the same
value before either writes back.
The fix adds a lock around this operation in ThreadingMock so
only one thread can update call_count at a time.
Reproduction and testing:
- x86 stress test (50 threads x 10000 calls x 30 runs):
Before fix: 23/30 failures, missing up to 42095 calls
After fix: 0/30 failures
- qemuarm64 (10 threads x 100 calls x 20 runs):
Before fix: 3/20 failures, missing up to 49 calls
After fix: 0/20 failures
- All 19 existing ThreadingMock tests pass
Upstream fix merged into CPython main:
python/cpython#150176
Fixes [YOCTO #16213]
Signed-off-by: Sai Sneha <saisneha196@gmail.com>
Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
) (#150182) gh-150175: Fix ThreadingMock call_count race condition (GH-150176) ThreadingMock._increment_mock_call() was not thread-safe. Multiple threads calling the mock simultaneously could lose increments due to race conditions on call_count and other attributes. Fix by overriding _increment_mock_call in ThreadingMixin and wrapping it with the existing _mock_calls_events_lock. (cherry picked from commit 388e023) Co-authored-by: saisneha196 <156835592+saisneha196@users.noreply.github.com>
) (#150181) gh-150175: Fix ThreadingMock call_count race condition (GH-150176) ThreadingMock._increment_mock_call() was not thread-safe. Multiple threads calling the mock simultaneously could lose increments due to race conditions on call_count and other attributes. Fix by overriding _increment_mock_call in ThreadingMixin and wrapping it with the existing _mock_calls_events_lock. (cherry picked from commit 388e023) Co-authored-by: saisneha196 <156835592+saisneha196@users.noreply.github.com>
) (#150180) gh-150175: Fix ThreadingMock call_count race condition (GH-150176) ThreadingMock._increment_mock_call() was not thread-safe. Multiple threads calling the mock simultaneously could lose increments due to race conditions on call_count and other attributes. Fix by overriding _increment_mock_call in ThreadingMixin and wrapping it with the existing _mock_calls_events_lock. (cherry picked from commit 388e023) Co-authored-by: saisneha196 <156835592+saisneha196@users.noreply.github.com>
Problem
ThreadingMock._increment_mock_call() is not thread-safe.
Multiple threads calling the mock simultaneously lose increments
due to race conditions on call_count and other attributes.
This causes intermittent test failures like:
AssertionError: 983 != 1000
Reported in Yocto Project bug:
https://bugzilla.yoctoproject.org/show_bug.cgi?id=16213
Fixes gh-150175
Root Cause
In the base Mock class, _increment_mock_call does:
self.called = True
self.call_count += 1 # not atomic!
self.call_args = _call
self.call_args_list.append(_call)
These are not protected by any lock in ThreadingMock.
Fix
Override _increment_mock_call in ThreadingMixin and wrap it
with the existing _mock_calls_events_lock:
Testing
Verified on both x86 and ARM64 (qemuarm64):