Skip to content

Commit c6f2553

Browse files
committed
gh-158975: Replace fixed sleeps in multiprocessing tests
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 e19dc47 commit c6f2553

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
@@ -1220,8 +1220,10 @@ def test_put(self):
12201220
queue.put(5, False, None)
12211221
queue.put_nowait(6)
12221222

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

12261228
self.assertEqual(queue_empty(queue), False)
12271229
self.assertEqual(queue_full(queue, MAXSIZE), True)
@@ -1342,8 +1344,10 @@ def test_fork(self):
13421344
for i in range(10):
13431345
queue.put(i)
13441346

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

13481352
# fork process
13491353
p = self.Process(target=self._test_fork, args=(queue,))
@@ -1566,15 +1570,15 @@ def test_repr_lock(self):
15661570
args=(lock, l),
15671571
name=tname)
15681572
t.start()
1569-
time.sleep(0.1)
1573+
threading_helper.join_thread(t)
15701574
self.assertEqual(f'<Lock(owner=MainProcess|{tname})>', l[0])
15711575
lock.release()
15721576

15731577
t = threading.Thread(target=self._acquire,
15741578
args=(lock,),
15751579
name=tname)
15761580
t.start()
1577-
time.sleep(0.1)
1581+
threading_helper.join_thread(t)
15781582
self.assertEqual('<Lock(owner=SomeOtherThread)>', repr(lock))
15791583
lock.release()
15801584

@@ -3175,25 +3179,28 @@ def produce_args():
31753179
last_produced_task_arg.value = arg
31763180
yield arg
31773181

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

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

3187-
next(it)
3188-
time.sleep(0.2)
3189-
self.assertGreater(last_produced_task_arg.value, processes + 1)
3192+
# Without a buffer limit, the iterable advances without waiting
3193+
# for workers or user code to catch up.
3194+
wait_for_produced_args(processes)
31903195

3191-
next(it)
3192-
time.sleep(0.2)
3193-
self.assertGreater(last_produced_task_arg.value, processes + 2)
3196+
next(it)
3197+
wait_for_produced_args(processes + 1)
31943198

3195-
p.terminate()
3196-
p.join()
3199+
next(it)
3200+
wait_for_produced_args(processes + 2)
3201+
finally:
3202+
p.terminate()
3203+
p.join()
31973204

31983205
@unittest.skipUnless(HAS_SHAREDCTYPES, 'needs sharedctypes')
31993206
@warnings_helper.ignore_fork_in_thread_deprecation_warnings()
@@ -3213,23 +3220,29 @@ def produce_args():
32133220
last_produced_task_arg.value = arg
32143221
yield arg
32153222

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

3220-
time.sleep(0.2)
3221-
self.assertEqual(last_produced_task_arg.value, processes)
3231+
try:
3232+
method = getattr(p, method_name)
3233+
it = method(functools.partial(sqr, wait=0.2), produce_args(),
3234+
buffersize=processes)
32223235

3223-
next(it)
3224-
time.sleep(0.2)
3225-
self.assertEqual(last_produced_task_arg.value, processes + 1)
3236+
wait_for_produced_args(processes)
32263237

3227-
next(it)
3228-
time.sleep(0.2)
3229-
self.assertEqual(last_produced_task_arg.value, processes + 2)
3238+
next(it)
3239+
wait_for_produced_args(processes + 1)
32303240

3231-
p.terminate()
3232-
p.join()
3241+
next(it)
3242+
wait_for_produced_args(processes + 2)
3243+
finally:
3244+
p.terminate()
3245+
p.join()
32333246

32343247
@warnings_helper.ignore_fork_in_thread_deprecation_warnings()
32353248
@support.subTests('method_name', ("imap", "imap_unordered"))
@@ -3435,8 +3448,11 @@ def test_release_task_refs(self):
34353448
self.pool.map(identity, objs)
34363449

34373450
del objs
3438-
time.sleep(DELTA) # let threaded cleanup code run
3439-
support.gc_collect() # For PyPy or other GCs.
3451+
for _ in support.sleeping_retry(support.SHORT_TIMEOUT, error=False):
3452+
support.gc_collect() # For PyPy or other GCs.
3453+
if (all(wr() is None for wr in refs)
3454+
and CountedObject.n_instances == 0):
3455+
break
34403456
self.assertEqual(set(wr() for wr in refs), {None})
34413457
# With a process pool, copies of the objects are returned, check
34423458
# they were released too.
@@ -4030,7 +4046,7 @@ def test_connection(self):
40304046
self.assertTimingAlmostEqual(poll.elapsed, TIMEOUT1)
40314047

40324048
conn.send(None)
4033-
time.sleep(.1)
4049+
self.assertTrue(conn.poll(support.SHORT_TIMEOUT))
40344050

40354051
self.assertEqual(poll(TIMEOUT1), True)
40364052
self.assertTimingAlmostEqual(poll.elapsed, 0)
@@ -4331,11 +4347,10 @@ def test_issue14725(self):
43314347
p = self.Process(target=self._test, args=(l.address,))
43324348
p.daemon = True
43334349
p.start()
4334-
time.sleep(1)
4335-
# On Windows the client process should by now have connected,
4336-
# written data and closed the pipe handle by now. This causes
4337-
# ConnectNamdedPipe() to fail with ERROR_NO_DATA. See Issue
4338-
# 14725.
4350+
join_process(p)
4351+
# On Windows the client process has now connected, written data
4352+
# and closed the pipe handle. This causes ConnectNamedPipe()
4353+
# to fail with ERROR_NO_DATA. See Issue 14725.
43394354
conn = l.accept()
43404355
self.assertEqual(conn.recv(), 'hello')
43414356
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)