Skip to content

Commit d663511

Browse files
committed
Merge branch 'main' into long_export
2 parents 1d2863e + ba088c8 commit d663511

8 files changed

Lines changed: 87 additions & 15 deletions

File tree

Lib/multiprocessing/managers.py

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -759,22 +759,29 @@ class BaseProxy(object):
759759
_address_to_local = {}
760760
_mutex = util.ForkAwareThreadLock()
761761

762+
# Each instance gets a `_serial` number. Unlike `id(...)`, this number
763+
# is never reused.
764+
_next_serial = 1
765+
762766
def __init__(self, token, serializer, manager=None,
763767
authkey=None, exposed=None, incref=True, manager_owned=False):
764768
with BaseProxy._mutex:
765-
tls_idset = BaseProxy._address_to_local.get(token.address, None)
766-
if tls_idset is None:
767-
tls_idset = util.ForkAwareLocal(), ProcessLocalSet()
768-
BaseProxy._address_to_local[token.address] = tls_idset
769+
tls_serials = BaseProxy._address_to_local.get(token.address, None)
770+
if tls_serials is None:
771+
tls_serials = util.ForkAwareLocal(), ProcessLocalSet()
772+
BaseProxy._address_to_local[token.address] = tls_serials
773+
774+
self._serial = BaseProxy._next_serial
775+
BaseProxy._next_serial += 1
769776

770777
# self._tls is used to record the connection used by this
771778
# thread to communicate with the manager at token.address
772-
self._tls = tls_idset[0]
779+
self._tls = tls_serials[0]
773780

774-
# self._idset is used to record the identities of all shared
775-
# objects for which the current process owns references and
781+
# self._all_serials is a set used to record the identities of all
782+
# shared objects for which the current process owns references and
776783
# which are in the manager at token.address
777-
self._idset = tls_idset[1]
784+
self._all_serials = tls_serials[1]
778785

779786
self._token = token
780787
self._id = self._token.id
@@ -857,20 +864,20 @@ def _incref(self):
857864
dispatch(conn, None, 'incref', (self._id,))
858865
util.debug('INCREF %r', self._token.id)
859866

860-
self._idset.add(self._id)
867+
self._all_serials.add(self._serial)
861868

862869
state = self._manager and self._manager._state
863870

864871
self._close = util.Finalize(
865872
self, BaseProxy._decref,
866-
args=(self._token, self._authkey, state,
867-
self._tls, self._idset, self._Client),
873+
args=(self._token, self._serial, self._authkey, state,
874+
self._tls, self._all_serials, self._Client),
868875
exitpriority=10
869876
)
870877

871878
@staticmethod
872-
def _decref(token, authkey, state, tls, idset, _Client):
873-
idset.discard(token.id)
879+
def _decref(token, serial, authkey, state, tls, idset, _Client):
880+
idset.discard(serial)
874881

875882
# check whether manager is still alive
876883
if state is None or state.value == State.STARTED:

Lib/test/test_unittest/testmock/testpatch.py

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,54 @@ def test_stop_idempotent(self):
745745
self.assertIsNone(patcher.stop())
746746

747747

748+
def test_exit_idempotent(self):
749+
patcher = patch(foo_name, 'bar', 3)
750+
with patcher:
751+
patcher.stop()
752+
753+
754+
def test_second_start_failure(self):
755+
patcher = patch(foo_name, 'bar', 3)
756+
patcher.start()
757+
try:
758+
self.assertRaises(RuntimeError, patcher.start)
759+
finally:
760+
patcher.stop()
761+
762+
763+
def test_second_enter_failure(self):
764+
patcher = patch(foo_name, 'bar', 3)
765+
with patcher:
766+
self.assertRaises(RuntimeError, patcher.start)
767+
768+
769+
def test_second_start_after_stop(self):
770+
patcher = patch(foo_name, 'bar', 3)
771+
patcher.start()
772+
patcher.stop()
773+
patcher.start()
774+
patcher.stop()
775+
776+
777+
def test_property_setters(self):
778+
mock_object = Mock()
779+
mock_bar = mock_object.bar
780+
patcher = patch.object(mock_object, 'bar', 'x')
781+
with patcher:
782+
self.assertEqual(patcher.is_local, False)
783+
self.assertIs(patcher.target, mock_object)
784+
self.assertEqual(patcher.temp_original, mock_bar)
785+
patcher.is_local = True
786+
patcher.target = mock_bar
787+
patcher.temp_original = mock_object
788+
self.assertEqual(patcher.is_local, True)
789+
self.assertIs(patcher.target, mock_bar)
790+
self.assertEqual(patcher.temp_original, mock_object)
791+
# if changes are left intact, they may lead to disruption as shown below (it might be what someone needs though)
792+
self.assertEqual(mock_bar.bar, mock_object)
793+
self.assertEqual(mock_object.bar, 'x')
794+
795+
748796
def test_patchobject_start_stop(self):
749797
original = something
750798
patcher = patch.object(PTModule, 'something', 'foo')
@@ -1098,7 +1146,7 @@ def test_new_callable_patch(self):
10981146

10991147
self.assertIsNot(m1, m2)
11001148
for mock in m1, m2:
1101-
self.assertNotCallable(m1)
1149+
self.assertNotCallable(mock)
11021150

11031151

11041152
def test_new_callable_patch_object(self):
@@ -1111,7 +1159,7 @@ def test_new_callable_patch_object(self):
11111159

11121160
self.assertIsNot(m1, m2)
11131161
for mock in m1, m2:
1114-
self.assertNotCallable(m1)
1162+
self.assertNotCallable(mock)
11151163

11161164

11171165
def test_new_callable_keyword_arguments(self):

Lib/unittest/async_case.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from .case import TestCase
77

8+
__unittest = True
89

910
class IsolatedAsyncioTestCase(TestCase):
1011
# Names intentionally have a long prefix

Lib/unittest/mock.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1360,6 +1360,7 @@ def __init__(
13601360
self.autospec = autospec
13611361
self.kwargs = kwargs
13621362
self.additional_patchers = []
1363+
self.is_started = False
13631364

13641365

13651366
def copy(self):
@@ -1472,6 +1473,9 @@ def get_original(self):
14721473

14731474
def __enter__(self):
14741475
"""Perform the patch."""
1476+
if self.is_started:
1477+
raise RuntimeError("Patch is already started")
1478+
14751479
new, spec, spec_set = self.new, self.spec, self.spec_set
14761480
autospec, kwargs = self.autospec, self.kwargs
14771481
new_callable = self.new_callable
@@ -1603,6 +1607,7 @@ def __enter__(self):
16031607
self.temp_original = original
16041608
self.is_local = local
16051609
self._exit_stack = contextlib.ExitStack()
1610+
self.is_started = True
16061611
try:
16071612
setattr(self.target, self.attribute, new_attr)
16081613
if self.attribute_name is not None:
@@ -1622,6 +1627,9 @@ def __enter__(self):
16221627

16231628
def __exit__(self, *exc_info):
16241629
"""Undo the patch."""
1630+
if not self.is_started:
1631+
return
1632+
16251633
if self.is_local and self.temp_original is not DEFAULT:
16261634
setattr(self.target, self.attribute, self.temp_original)
16271635
else:
@@ -1638,6 +1646,7 @@ def __exit__(self, *exc_info):
16381646
del self.target
16391647
exit_stack = self._exit_stack
16401648
del self._exit_stack
1649+
self.is_started = False
16411650
return exit_stack.__exit__(*exc_info)
16421651

16431652

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,7 @@ Larry Hastings
733733
Tim Hatch
734734
Zac Hatfield-Dodds
735735
Shane Hathaway
736+
Akinori Hattori
736737
Michael Haubenwallner
737738
Janko Hauser
738739
Flavian Hautbois
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Strip :class:`unittest.IsolatedAsyncioTestCase` stack frames from reported
2+
stacktraces.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix a race condition in :class:`multiprocessing.pool.Pool`.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Limit starting a patcher (from :func:`unittest.mock.patch` or
2+
:func:`unittest.mock.patch.object`) more than
3+
once without stopping it

0 commit comments

Comments
 (0)