From 567739483c23f0a9146f731427299143b1453e36 Mon Sep 17 00:00:00 2001 From: Yaniv Michael Kaul Date: Thu, 26 Mar 2026 12:06:14 +0200 Subject: [PATCH] fix: make HashableMock thread-safe by restoring __hash__ after MagicMixin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NonCallableMagicMock.__init__ (via MagicMixin) replaces __hash__ on the type with a MagicMock object. That MagicMock is not thread-safe, so concurrent hash() calls — e.g. `connection in self._trash` in pool.py return_connection — can raise `TypeError: __hash__ method should return an integer` under concurrent access on Windows. The previous __hash__ override was dead code: MagicMixin.__init__ always replaced it with a MagicMock before any test could call it. Fix by restoring a plain function as the class-level __hash__ after super().__init__ runs, so hash() always resolves to a real function instead of a thread-unsafe MagicMock callable. Fixes flaky test_successful_wait_for_connection on Windows CI. --- tests/unit/util.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/tests/unit/util.py b/tests/unit/util.py index 042f07fb99..8aa5b96f93 100644 --- a/tests/unit/util.py +++ b/tests/unit/util.py @@ -32,6 +32,24 @@ def _check_order_consistency(smaller, bigger, equal=False): class HashableMock(NonCallableMagicMock): + """A Mock subclass that is safely hashable and usable in sets/dicts. - def __hash__(self): - return id(self) \ No newline at end of file + NonCallableMagicMock's __init__ (via MagicMixin) replaces __hash__ + on the *type* with a MagicMock object. That MagicMock is not + thread-safe, so concurrent hash() calls on the same instance — + e.g. ``connection in self._trash`` in pool.py — can raise + ``TypeError: __hash__ method should return an integer`` on Windows. + + We fix this by restoring a plain function as the class-level + __hash__ after super().__init__ runs, so hash() always resolves to + a real function (id-based) instead of a MagicMock callable. + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + # Restore a real __hash__ after MagicMixin overwrites it. + type(self).__hash__ = HashableMock._id_hash + + @staticmethod + def _id_hash(self): + return id(self)