Skip to content

Commit 75d96f8

Browse files
authored
gh-158975: Replace fixed sleeps in multiprocessing tests (gh-158977)
Replace fixed sleeps used for synchronization in multiprocessing tests with bounded waits to avoid failures on slow or overloaded systems. Ensure pool cleanup runs even when assertions fail in the imap buffer tests.
1 parent 495fef1 commit 75d96f8

2 files changed

Lines changed: 60 additions & 42 deletions

File tree

‎Lib/test/_test_multiprocessing.py‎

Lines changed: 57 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1222,8 +1222,10 @@ def test_put(self):
12221222
queue.put(5, False, None)
12231223
queue.put_nowait(6)
12241224

1225-
# the values may be in buffer but not yet in pipe so sleep a bit
1226-
time.sleep(DELTA)
1225+
# The values may be in the buffer but not yet in the pipe.
1226+
for _ in support.sleeping_retry(support.SHORT_TIMEOUT):
1227+
if not queue_empty(queue):
1228+
break
12271229

12281230
self.assertEqual(queue_empty(queue), False)
12291231
self.assertEqual(queue_full(queue, MAXSIZE), True)
@@ -1344,8 +1346,10 @@ def test_fork(self):
13441346
for i in range(10):
13451347
queue.put(i)
13461348

1347-
# wait to make sure thread starts before we fork a new process
1348-
time.sleep(DELTA)
1349+
# Wait for the feeder thread to write to the pipe before forking.
1350+
for _ in support.sleeping_retry(support.SHORT_TIMEOUT):
1351+
if not queue_empty(queue):
1352+
break
13491353

13501354
# fork process
13511355
p = self.Process(target=self._test_fork, args=(queue,))
@@ -1568,15 +1572,15 @@ def test_repr_lock(self):
15681572
args=(lock, l),
15691573
name=tname)
15701574
t.start()
1571-
time.sleep(0.1)
1575+
threading_helper.join_thread(t)
15721576
self.assertEqual(f'<Lock(owner=MainProcess|{tname})>', l[0])
15731577
lock.release()
15741578

15751579
t = threading.Thread(target=self._acquire,
15761580
args=(lock,),
15771581
name=tname)
15781582
t.start()
1579-
time.sleep(0.1)
1583+
threading_helper.join_thread(t)
15801584
self.assertEqual('<Lock(owner=SomeOtherThread)>', repr(lock))
15811585
lock.release()
15821586

@@ -3177,25 +3181,28 @@ def produce_args():
31773181
last_produced_task_arg.value = arg
31783182
yield arg
31793183

3180-
method = getattr(p, method_name)
3181-
it = method(functools.partial(sqr, wait=0.2), produce_args())
3184+
def wait_for_produced_args(threshold):
3185+
for _ in support.sleeping_retry(support.SHORT_TIMEOUT, error=False):
3186+
if last_produced_task_arg.value > threshold:
3187+
break
3188+
self.assertGreater(last_produced_task_arg.value, threshold)
31823189

3183-
time.sleep(0.2)
3184-
# `iterable` could've been advanced only `processes` times,
3185-
# but in fact it advances further (`> processes`) because of
3186-
# not waiting for workers or user code to catch up.
3187-
self.assertGreater(last_produced_task_arg.value, processes)
3190+
try:
3191+
method = getattr(p, method_name)
3192+
it = method(functools.partial(sqr, wait=0.2), produce_args())
31883193

3189-
next(it)
3190-
time.sleep(0.2)
3191-
self.assertGreater(last_produced_task_arg.value, processes + 1)
3194+
# Without a buffer limit, the iterable advances without waiting
3195+
# for workers or user code to catch up.
3196+
wait_for_produced_args(processes)
31923197

3193-
next(it)
3194-
time.sleep(0.2)
3195-
self.assertGreater(last_produced_task_arg.value, processes + 2)
3198+
next(it)
3199+
wait_for_produced_args(processes + 1)
31963200

3197-
p.terminate()
3198-
p.join()
3201+
next(it)
3202+
wait_for_produced_args(processes + 2)
3203+
finally:
3204+
p.terminate()
3205+
p.join()
31993206

32003207
@unittest.skipUnless(HAS_SHAREDCTYPES, 'needs sharedctypes')
32013208
@warnings_helper.ignore_fork_in_thread_deprecation_warnings()
@@ -3215,23 +3222,29 @@ def produce_args():
32153222
last_produced_task_arg.value = arg
32163223
yield arg
32173224

3218-
method = getattr(p, method_name)
3219-
it = method(functools.partial(sqr, wait=0.2), produce_args(),
3220-
buffersize=processes)
3225+
def wait_for_produced_args(expected):
3226+
# Consuming a result allows the task handler to advance the
3227+
# iterable, but does not wait for it to do so.
3228+
for _ in support.sleeping_retry(support.SHORT_TIMEOUT, error=False):
3229+
if last_produced_task_arg.value >= expected:
3230+
break
3231+
self.assertEqual(last_produced_task_arg.value, expected)
32213232

3222-
time.sleep(0.2)
3223-
self.assertEqual(last_produced_task_arg.value, processes)
3233+
try:
3234+
method = getattr(p, method_name)
3235+
it = method(functools.partial(sqr, wait=0.2), produce_args(),
3236+
buffersize=processes)
32243237

3225-
next(it)
3226-
time.sleep(0.2)
3227-
self.assertEqual(last_produced_task_arg.value, processes + 1)
3238+
wait_for_produced_args(processes)
32283239

3229-
next(it)
3230-
time.sleep(0.2)
3231-
self.assertEqual(last_produced_task_arg.value, processes + 2)
3240+
next(it)
3241+
wait_for_produced_args(processes + 1)
32323242

3233-
p.terminate()
3234-
p.join()
3243+
next(it)
3244+
wait_for_produced_args(processes + 2)
3245+
finally:
3246+
p.terminate()
3247+
p.join()
32353248

32363249
@warnings_helper.ignore_fork_in_thread_deprecation_warnings()
32373250
@support.subTests('method_name', ("imap", "imap_unordered"))
@@ -3437,8 +3450,11 @@ def test_release_task_refs(self):
34373450
self.pool.map(identity, objs)
34383451

34393452
del objs
3440-
time.sleep(DELTA) # let threaded cleanup code run
3441-
support.gc_collect() # For PyPy or other GCs.
3453+
for _ in support.sleeping_retry(support.SHORT_TIMEOUT, error=False):
3454+
support.gc_collect() # For PyPy or other GCs.
3455+
if (all(wr() is None for wr in refs)
3456+
and CountedObject.n_instances == 0):
3457+
break
34423458
self.assertEqual(set(wr() for wr in refs), {None})
34433459
# With a process pool, copies of the objects are returned, check
34443460
# they were released too.
@@ -4032,7 +4048,7 @@ def test_connection(self):
40324048
self.assertTimingAlmostEqual(poll.elapsed, TIMEOUT1)
40334049

40344050
conn.send(None)
4035-
time.sleep(.1)
4051+
self.assertTrue(conn.poll(support.SHORT_TIMEOUT))
40364052

40374053
self.assertEqual(poll(TIMEOUT1), True)
40384054
self.assertTimingAlmostEqual(poll.elapsed, 0)
@@ -4333,11 +4349,10 @@ def test_issue14725(self):
43334349
p = self.Process(target=self._test, args=(l.address,))
43344350
p.daemon = True
43354351
p.start()
4336-
time.sleep(1)
4337-
# On Windows the client process should by now have connected,
4338-
# written data and closed the pipe handle by now. This causes
4339-
# ConnectNamdedPipe() to fail with ERROR_NO_DATA. See Issue
4340-
# 14725.
4352+
join_process(p)
4353+
# On Windows the client process has now connected, written data
4354+
# and closed the pipe handle. This causes ConnectNamedPipe()
4355+
# to fail with ERROR_NO_DATA. See Issue 14725.
43414356
conn = l.accept()
43424357
self.assertEqual(conn.recv(), 'hello')
43434358
conn.close()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Replace fixed sleeps used for synchronization in multiprocessing tests with
2+
bounded waits to avoid failures on slow or overloaded systems. Ensure pool
3+
cleanup runs even when assertions fail in the imap buffer tests.

0 commit comments

Comments
 (0)