From 080e5542d8fbddd65c26c6b2e3f899511b08f951 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 4 Jul 2025 15:53:42 +0300 Subject: [PATCH 1/4] gh-136285: Improve `pickle` protocol testing in `test_interpreters` --- Lib/test/test_interpreters/test_api.py | 8 +++++--- Lib/test/test_interpreters/test_channels.py | 16 ++++++++++------ Lib/test/test_interpreters/test_queues.py | 8 +++++--- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/Lib/test/test_interpreters/test_api.py b/Lib/test/test_interpreters/test_api.py index 0ee4582b5d1568..537a3c2067475f 100644 --- a/Lib/test/test_interpreters/test_api.py +++ b/Lib/test/test_interpreters/test_api.py @@ -412,9 +412,11 @@ def test_equality(self): def test_pickle(self): interp = interpreters.create() - data = pickle.dumps(interp) - unpickled = pickle.loads(data) - self.assertEqual(unpickled, interp) + for protocol in range(2, pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(protocol=protocol): + data = pickle.dumps(interp, protocol) + unpickled = pickle.loads(data) + self.assertEqual(unpickled, interp) class TestInterpreterIsRunning(TestBase): diff --git a/Lib/test/test_interpreters/test_channels.py b/Lib/test/test_interpreters/test_channels.py index 109ddf344539ad..d3bfa4ceed3dee 100644 --- a/Lib/test/test_interpreters/test_channels.py +++ b/Lib/test/test_interpreters/test_channels.py @@ -121,9 +121,11 @@ def test_equality(self): def test_pickle(self): ch, _ = channels.create() - data = pickle.dumps(ch) - unpickled = pickle.loads(data) - self.assertEqual(unpickled, ch) + for protocol in range(2, pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(protocol=protocol): + data = pickle.dumps(ch, protocol) + unpickled = pickle.loads(data) + self.assertEqual(unpickled, ch) class TestSendChannelAttrs(TestBase): @@ -152,9 +154,11 @@ def test_equality(self): def test_pickle(self): _, ch = channels.create() - data = pickle.dumps(ch) - unpickled = pickle.loads(data) - self.assertEqual(unpickled, ch) + for protocol in range(2, pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(protocol=protocol): + data = pickle.dumps(ch, protocol) + unpickled = pickle.loads(data) + self.assertEqual(unpickled, ch) class TestSendRecv(TestBase): diff --git a/Lib/test/test_interpreters/test_queues.py b/Lib/test/test_interpreters/test_queues.py index cb17340f581b0a..bfad3f4973a86b 100644 --- a/Lib/test/test_interpreters/test_queues.py +++ b/Lib/test/test_interpreters/test_queues.py @@ -188,9 +188,11 @@ def test_equality(self): def test_pickle(self): queue = queues.create() - data = pickle.dumps(queue) - unpickled = pickle.loads(data) - self.assertEqual(unpickled, queue) + for protocol in range(2, pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(protocol=protocol): + data = pickle.dumps(queue, protocol) + unpickled = pickle.loads(data) + self.assertEqual(unpickled, queue) class TestQueueOps(TestBase): From 8b6fbdec846eeea53a597ec46340712edecb5471 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sat, 5 Jul 2025 09:43:11 +0300 Subject: [PATCH 2/4] Fix pickling bug --- Lib/concurrent/interpreters/__init__.py | 8 ++------ Lib/concurrent/interpreters/_queues.py | 8 ++------ Lib/test/support/channels.py | 8 ++------ Lib/test/test_interpreters/test_api.py | 2 +- Lib/test/test_interpreters/test_channels.py | 15 ++++++++------- Lib/test/test_interpreters/test_queues.py | 2 +- 6 files changed, 16 insertions(+), 27 deletions(-) diff --git a/Lib/concurrent/interpreters/__init__.py b/Lib/concurrent/interpreters/__init__.py index 0fd661249a276c..aa46a2b37a48d5 100644 --- a/Lib/concurrent/interpreters/__init__.py +++ b/Lib/concurrent/interpreters/__init__.py @@ -146,12 +146,8 @@ def __del__(self): self._decref() # for pickling: - def __getnewargs__(self): - return (self._id,) - - # for pickling: - def __getstate__(self): - return None + def __reduce__(self): + return (type(self), (self._id,)) def _decref(self): if not self._ownsref: diff --git a/Lib/concurrent/interpreters/_queues.py b/Lib/concurrent/interpreters/_queues.py index 99987f2f6926b0..9c12b2c8c24664 100644 --- a/Lib/concurrent/interpreters/_queues.py +++ b/Lib/concurrent/interpreters/_queues.py @@ -129,12 +129,8 @@ def __hash__(self): return hash(self._id) # for pickling: - def __getnewargs__(self): - return (self._id,) - - # for pickling: - def __getstate__(self): - return None + def __reduce__(self): + return (type(self), (self._id,)) def _set_unbound(self, op, items=None): assert not hasattr(self, '_unbound') diff --git a/Lib/test/support/channels.py b/Lib/test/support/channels.py index b2de24d9d3e534..fab1797659b312 100644 --- a/Lib/test/support/channels.py +++ b/Lib/test/support/channels.py @@ -105,12 +105,8 @@ def __eq__(self, other): return other._id == self._id # for pickling: - def __getnewargs__(self): - return (int(self._id),) - - # for pickling: - def __getstate__(self): - return None + def __reduce__(self): + return (type(self), (int(self._id),)) @property def id(self): diff --git a/Lib/test/test_interpreters/test_api.py b/Lib/test/test_interpreters/test_api.py index 537a3c2067475f..a34b20beaca7a3 100644 --- a/Lib/test/test_interpreters/test_api.py +++ b/Lib/test/test_interpreters/test_api.py @@ -412,7 +412,7 @@ def test_equality(self): def test_pickle(self): interp = interpreters.create() - for protocol in range(2, pickle.HIGHEST_PROTOCOL + 1): + for protocol in range(pickle.HIGHEST_PROTOCOL + 1): with self.subTest(protocol=protocol): data = pickle.dumps(interp, protocol) unpickled = pickle.loads(data) diff --git a/Lib/test/test_interpreters/test_channels.py b/Lib/test/test_interpreters/test_channels.py index d3bfa4ceed3dee..f15865df8dc1ec 100644 --- a/Lib/test/test_interpreters/test_channels.py +++ b/Lib/test/test_interpreters/test_channels.py @@ -120,12 +120,13 @@ def test_equality(self): self.assertNotEqual(ch1, ch2) def test_pickle(self): - ch, _ = channels.create() - for protocol in range(2, pickle.HIGHEST_PROTOCOL + 1): - with self.subTest(protocol=protocol): - data = pickle.dumps(ch, protocol) - unpickled = pickle.loads(data) - self.assertEqual(unpickled, ch) + recv, send = channels.create() + for ch in [recv, send]: + for protocol in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(ch=ch, protocol=protocol): + data = pickle.dumps(ch, protocol) + unpickled = pickle.loads(data) + self.assertEqual(unpickled, ch) class TestSendChannelAttrs(TestBase): @@ -154,7 +155,7 @@ def test_equality(self): def test_pickle(self): _, ch = channels.create() - for protocol in range(2, pickle.HIGHEST_PROTOCOL + 1): + for protocol in range(pickle.HIGHEST_PROTOCOL + 1): with self.subTest(protocol=protocol): data = pickle.dumps(ch, protocol) unpickled = pickle.loads(data) diff --git a/Lib/test/test_interpreters/test_queues.py b/Lib/test/test_interpreters/test_queues.py index bfad3f4973a86b..5451c6654acb47 100644 --- a/Lib/test/test_interpreters/test_queues.py +++ b/Lib/test/test_interpreters/test_queues.py @@ -188,7 +188,7 @@ def test_equality(self): def test_pickle(self): queue = queues.create() - for protocol in range(2, pickle.HIGHEST_PROTOCOL + 1): + for protocol in range(pickle.HIGHEST_PROTOCOL + 1): with self.subTest(protocol=protocol): data = pickle.dumps(queue, protocol) unpickled = pickle.loads(data) From b9f6ce6642fd2d4de1bbbcd86fe110eb3c3aeed8 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sat, 5 Jul 2025 09:45:11 +0300 Subject: [PATCH 3/4] Add NEWS --- .../next/Library/2025-07-05-09-45-04.gh-issue-136286.N67Amr.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2025-07-05-09-45-04.gh-issue-136286.N67Amr.rst diff --git a/Misc/NEWS.d/next/Library/2025-07-05-09-45-04.gh-issue-136286.N67Amr.rst b/Misc/NEWS.d/next/Library/2025-07-05-09-45-04.gh-issue-136286.N67Amr.rst new file mode 100644 index 00000000000000..0a0d66ac0b8abf --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-07-05-09-45-04.gh-issue-136286.N67Amr.rst @@ -0,0 +1,2 @@ +Fix pickling failures for protocols 0 and 1 for many objects realted to +subinterpreters. From 19daa81995ad29e1ec262349bdc5fb57e6d33fc3 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sun, 6 Jul 2025 10:10:15 +0300 Subject: [PATCH 4/4] Address review --- Lib/test/test_interpreters/test_channels.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_interpreters/test_channels.py b/Lib/test/test_interpreters/test_channels.py index f15865df8dc1ec..52827357078b85 100644 --- a/Lib/test/test_interpreters/test_channels.py +++ b/Lib/test/test_interpreters/test_channels.py @@ -120,13 +120,12 @@ def test_equality(self): self.assertNotEqual(ch1, ch2) def test_pickle(self): - recv, send = channels.create() - for ch in [recv, send]: - for protocol in range(pickle.HIGHEST_PROTOCOL + 1): - with self.subTest(ch=ch, protocol=protocol): - data = pickle.dumps(ch, protocol) - unpickled = pickle.loads(data) - self.assertEqual(unpickled, ch) + ch, _ = channels.create() + for protocol in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(protocol=protocol): + data = pickle.dumps(ch, protocol) + unpickled = pickle.loads(data) + self.assertEqual(unpickled, ch) class TestSendChannelAttrs(TestBase):