class CBytesIOTest(ThreadSafetyMixin, TestCase):
ioclass = io.BytesIO
+ @threading_helper.requires_working_threading()
+ @threading_helper.reap_threads
+ def test_concurrent_setstate_and_method_call(self):
+ # gh-153290: __setstate__() installed the instance __dict__ with a
+ # plain store, racing the lock-free LOAD_ATTR method fast path that
+ # reads the dict slot with an atomic acquire load.
+ states = [(b"A" * 64, 0, {}), (b"B" * 128, 32, {}), (b"C" * 256, 0, {})]
+ nreaders = 4
+ for _ in range(25):
+ shared = self.ioclass(b"initial payload")
+ barrier = threading.Barrier(1 + nreaders)
+
+ def setter():
+ barrier.wait()
+ for state in states:
+ shared.__setstate__(state)
+
+ def reader():
+ barrier.wait()
+ for _ in range(100):
+ shared.read(8)
+
+ threads = [threading.Thread(target=setter)]
+ threads += [threading.Thread(target=reader)
+ for _ in range(nreaders)]
+ with threading_helper.start_threads(threads):
+ pass
+
@threading_helper.requires_working_threading()
@threading_helper.reap_threads
def test_concurrent_whole_buffer_read_and_resize(self):
return NULL;
}
else {
- self->dict = Py_NewRef(dict);
+ /* The LOAD_ATTR specializations read the dict slot lock-free
+ with an acquire load, so pair it with a release store. */
+ FT_ATOMIC_STORE_PTR_RELEASE(self->dict, Py_NewRef(dict));
}
}