| import contextvars | |
| import unittest | |
| from threading import Event, Thread | |
| from test.support import threading_helper | |
| @threading_helper.requires_working_threading() | |
| class TestContext(unittest.TestCase): | |
| def test_racing_read_write(self): | |
| # gh-154535: reading a Context object from one thread while another | |
| # thread sets variables in it used to crash. The readers looked at | |
| # Context.ctx_vars without owning a reference to it, so the writer | |
| # could deallocate the mapping while a reader was walking it. | |
| ctx = contextvars.Context() | |
| cvars = [contextvars.ContextVar(f"cvar{i}") for i in range(64)] | |
| done = Event() | |
| errors = [] | |
| def writer(): | |
| def body(): | |
| i = 0 | |
| while not done.is_set(): | |
| cvars[i % len(cvars)].set(i) | |
| i += 1 | |
| try: | |
| ctx.run(body) | |
| except BaseException as e: | |
| errors.append(e) | |
| def reader(): | |
| try: | |
| for _ in range(200): | |
| ctx.copy() | |
| len(ctx) | |
| list(ctx) | |
| list(ctx.items()) | |
| list(ctx.keys()) | |
| list(ctx.values()) | |
| cvars[0] in ctx | |
| ctx.get(cvars[0]) | |
| ctx == ctx | |
| except BaseException as e: | |
| errors.append(e) | |
| finally: | |
| done.set() | |
| threads = [Thread(target=writer)] | |
| threads += [Thread(target=reader) for _ in range(4)] | |
| with threading_helper.start_threads(threads, done.set): | |
| pass | |
| self.assertEqual(errors, [], msg=f"unexpected errors: {errors}") | |
| if __name__ == "__main__": | |
| unittest.main() |