Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit7e9eaad

Browse files
authored
Add test.support.busy_retry() (#93770)
Add busy_retry() and sleeping_retry() functions to test.support.
1 parent4e9fa71 commit7e9eaad

File tree

12 files changed

+186
-99
lines changed

12 files changed

+186
-99
lines changed

‎Doc/library/test.rst‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,51 @@ The :mod:`test.support` module defines the following constants:
413413

414414
The:mod:`test.support` module defines the following functions:
415415

416+
..function::busy_retry(timeout, err_msg=None, /, *, error=True)
417+
418+
Run the loop body until ``break`` stops the loop.
419+
420+
After *timeout* seconds, raise an:exc:`AssertionError` if *error* is true,
421+
or just stop the loop if *error* is false.
422+
423+
Example::
424+
425+
for _ in support.busy_retry(support.SHORT_TIMEOUT):
426+
if check():
427+
break
428+
429+
Example of error=False usage::
430+
431+
for _ in support.busy_retry(support.SHORT_TIMEOUT, error=False):
432+
if check():
433+
break
434+
else:
435+
raise RuntimeError('my custom error')
436+
437+
..function::sleeping_retry(timeout, err_msg=None, /, *, init_delay=0.010, max_delay=1.0, error=True)
438+
439+
Wait strategy that applies exponential backoff.
440+
441+
Run the loop body until ``break`` stops the loop. Sleep at each loop
442+
iteration, but not at the first iteration. The sleep delay is doubled at
443+
each iteration (up to *max_delay* seconds).
444+
445+
See:func:`busy_retry` documentation for the parameters usage.
446+
447+
Example raising an exception after SHORT_TIMEOUT seconds::
448+
449+
for _ in support.sleeping_retry(support.SHORT_TIMEOUT):
450+
if check():
451+
break
452+
453+
Example of error=False usage::
454+
455+
for _ in support.sleeping_retry(support.SHORT_TIMEOUT, error=False):
456+
if check():
457+
break
458+
else:
459+
raise RuntimeError('my custom error')
460+
416461
..function::is_resource_enabled(resource)
417462

418463
Return ``True`` if *resource* is enabled and available. The list of

‎Lib/test/_test_multiprocessing.py‎

Lines changed: 25 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4313,18 +4313,13 @@ def test_shared_memory_cleaned_after_process_termination(self):
43134313
p.terminate()
43144314
p.wait()
43154315

4316-
deadline=time.monotonic()+support.LONG_TIMEOUT
4317-
t=0.1
4318-
whiletime.monotonic()<deadline:
4319-
time.sleep(t)
4320-
t=min(t*2,5)
4316+
err_msg= ("A SharedMemory segment was leaked after "
4317+
"a process was abruptly terminated")
4318+
for_insupport.sleeping_retry(support.LONG_TIMEOUT,err_msg):
43214319
try:
43224320
smm=shared_memory.SharedMemory(name,create=False)
43234321
exceptFileNotFoundError:
43244322
break
4325-
else:
4326-
raiseAssertionError("A SharedMemory segment was leaked after"
4327-
" a process was abruptly terminated.")
43284323

43294324
ifos.name=='posix':
43304325
# Without this line it was raising warnings like:
@@ -5334,20 +5329,18 @@ def create_and_register_resource(rtype):
53345329
p.terminate()
53355330
p.wait()
53365331

5337-
deadline=time.monotonic()+support.LONG_TIMEOUT
5338-
whiletime.monotonic()<deadline:
5339-
time.sleep(.5)
5332+
err_msg= (f"A{rtype} resource was leaked after a process was "
5333+
f"abruptly terminated")
5334+
for_insupport.sleeping_retry(support.SHORT_TIMEOUT,
5335+
err_msg):
53405336
try:
53415337
_resource_unlink(name2,rtype)
53425338
exceptOSErrorase:
53435339
# docs say it should be ENOENT, but OSX seems to give
53445340
# EINVAL
53455341
self.assertIn(e.errno, (errno.ENOENT,errno.EINVAL))
53465342
break
5347-
else:
5348-
raiseAssertionError(
5349-
f"A{rtype} resource was leaked after a process was "
5350-
f"abruptly terminated.")
5343+
53515344
err=p.stderr.read().decode('utf-8')
53525345
p.stderr.close()
53535346
expected= ('resource_tracker: There appear to be 2 leaked {} '
@@ -5575,18 +5568,17 @@ def wait_proc_exit(self):
55755568
# but this can take a bit on slow machines, so wait a few seconds
55765569
# if there are other children too (see #17395).
55775570
join_process(self.proc)
5571+
55785572
start_time=time.monotonic()
5579-
t=0.01
5580-
whilelen(multiprocessing.active_children())>1:
5581-
time.sleep(t)
5582-
t*=2
5583-
dt=time.monotonic()-start_time
5584-
ifdt>=5.0:
5585-
test.support.environment_altered=True
5586-
support.print_warning(f"multiprocessing.Manager still has "
5587-
f"{multiprocessing.active_children()} "
5588-
f"active children after{dt} seconds")
5573+
for_insupport.sleeping_retry(5.0,error=False):
5574+
iflen(multiprocessing.active_children())<=1:
55895575
break
5576+
else:
5577+
dt=time.monotonic()-start_time
5578+
support.environment_altered=True
5579+
support.print_warning(f"multiprocessing.Manager still has "
5580+
f"{multiprocessing.active_children()} "
5581+
f"active children after{dt:.1f} seconds")
55905582

55915583
defrun_worker(self,worker,obj):
55925584
self.proc=multiprocessing.Process(target=worker,args=(obj, ))
@@ -5884,17 +5876,15 @@ def tearDownClass(cls):
58845876
# but this can take a bit on slow machines, so wait a few seconds
58855877
# if there are other children too (see #17395)
58865878
start_time=time.monotonic()
5887-
t=0.01
5888-
whilelen(multiprocessing.active_children())>1:
5889-
time.sleep(t)
5890-
t*=2
5891-
dt=time.monotonic()-start_time
5892-
ifdt>=5.0:
5893-
test.support.environment_altered=True
5894-
support.print_warning(f"multiprocessing.Manager still has "
5895-
f"{multiprocessing.active_children()} "
5896-
f"active children after{dt} seconds")
5879+
for_insupport.sleeping_retry(5.0,error=False):
5880+
iflen(multiprocessing.active_children())<=1:
58975881
break
5882+
else:
5883+
dt=time.monotonic()-start_time
5884+
support.environment_altered=True
5885+
support.print_warning(f"multiprocessing.Manager still has "
5886+
f"{multiprocessing.active_children()} "
5887+
f"active children after{dt:.1f} seconds")
58985888

58995889
gc.collect()# do garbage collection
59005890
ifcls.manager._number_of_objects()!=0:

‎Lib/test/fork_wait.py‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,8 @@ def test_wait(self):
5454
self.threads.append(thread)
5555

5656
# busy-loop to wait for threads
57-
deadline=time.monotonic()+support.SHORT_TIMEOUT
58-
whilelen(self.alive)<NUM_THREADS:
59-
time.sleep(0.1)
60-
ifdeadline<time.monotonic():
57+
for_insupport.sleeping_retry(support.SHORT_TIMEOUT,error=False):
58+
iflen(self.alive)>=NUM_THREADS:
6159
break
6260

6361
a=sorted(self.alive.keys())

‎Lib/test/support/__init__.py‎

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2250,3 +2250,79 @@ def atfork_func():
22502250
pass
22512251
atfork_func.reference=ref_cycle
22522252
os.register_at_fork(before=atfork_func)
2253+
2254+
2255+
defbusy_retry(timeout,err_msg=None,/,*,error=True):
2256+
"""
2257+
Run the loop body until "break" stops the loop.
2258+
2259+
After *timeout* seconds, raise an AssertionError if *error* is true,
2260+
or just stop if *error is false.
2261+
2262+
Example:
2263+
2264+
for _ in support.busy_retry(support.SHORT_TIMEOUT):
2265+
if check():
2266+
break
2267+
2268+
Example of error=False usage:
2269+
2270+
for _ in support.busy_retry(support.SHORT_TIMEOUT, error=False):
2271+
if check():
2272+
break
2273+
else:
2274+
raise RuntimeError('my custom error')
2275+
2276+
"""
2277+
iftimeout<=0:
2278+
raiseValueError("timeout must be greater than zero")
2279+
2280+
start_time=time.monotonic()
2281+
deadline=start_time+timeout
2282+
2283+
whileTrue:
2284+
yield
2285+
2286+
iftime.monotonic()>=deadline:
2287+
break
2288+
2289+
iferror:
2290+
dt=time.monotonic()-start_time
2291+
msg=f"timeout ({dt:.1f} seconds)"
2292+
iferr_msg:
2293+
msg=f"{msg}:{err_msg}"
2294+
raiseAssertionError(msg)
2295+
2296+
2297+
defsleeping_retry(timeout,err_msg=None,/,
2298+
*,init_delay=0.010,max_delay=1.0,error=True):
2299+
"""
2300+
Wait strategy that applies exponential backoff.
2301+
2302+
Run the loop body until "break" stops the loop. Sleep at each loop
2303+
iteration, but not at the first iteration. The sleep delay is doubled at
2304+
each iteration (up to *max_delay* seconds).
2305+
2306+
See busy_retry() documentation for the parameters usage.
2307+
2308+
Example raising an exception after SHORT_TIMEOUT seconds:
2309+
2310+
for _ in support.sleeping_retry(support.SHORT_TIMEOUT):
2311+
if check():
2312+
break
2313+
2314+
Example of error=False usage:
2315+
2316+
for _ in support.sleeping_retry(support.SHORT_TIMEOUT, error=False):
2317+
if check():
2318+
break
2319+
else:
2320+
raise RuntimeError('my custom error')
2321+
"""
2322+
2323+
delay=init_delay
2324+
for_inbusy_retry(timeout,err_msg,error=error):
2325+
yield
2326+
2327+
time.sleep(delay)
2328+
delay=min(delay*2,max_delay)

‎Lib/test/test__xxsubinterpreters.py‎

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,11 @@ def _wait_for_interp_to_run(interp, timeout=None):
4545
# run subinterpreter eariler than the main thread in multiprocess.
4646
iftimeoutisNone:
4747
timeout=support.SHORT_TIMEOUT
48-
start_time=time.monotonic()
49-
deadline=start_time+timeout
50-
whilenotinterpreters.is_running(interp):
51-
iftime.monotonic()>deadline:
52-
raiseRuntimeError('interp is not running')
53-
time.sleep(0.010)
48+
for_insupport.sleeping_retry(timeout,error=False):
49+
ifinterpreters.is_running(interp):
50+
break
51+
else:
52+
raiseRuntimeError('interp is not running')
5453

5554

5655
@contextlib.contextmanager

‎Lib/test/test_concurrent_futures.py‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -256,12 +256,12 @@ def test_initializer(self):
256256
else:
257257
withself.assertRaises(BrokenExecutor):
258258
future.result()
259+
259260
# At some point, the executor should break
260-
t1=time.monotonic()
261-
whilenotself.executor._broken:
262-
iftime.monotonic()-t1>5:
263-
self.fail("executor not broken after 5 s.")
264-
time.sleep(0.01)
261+
for_insupport.sleeping_retry(5,"executor not broken"):
262+
ifself.executor._broken:
263+
break
264+
265265
# ... and from this point submit() is guaranteed to fail
266266
withself.assertRaises(BrokenExecutor):
267267
self.executor.submit(get_init_status)

‎Lib/test/test_multiprocessing_main_handling.py‎

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import sys
4141
import time
4242
from multiprocessing import Pool, set_start_method
43+
from test import support
4344
4445
# We use this __main__ defined function in the map call below in order to
4546
# check that multiprocessing in correctly running the unguarded
@@ -59,13 +60,11 @@ def f(x):
5960
results = []
6061
with Pool(5) as pool:
6162
pool.map_async(f, [1, 2, 3], callback=results.extend)
62-
start_time = time.monotonic()
63-
while not results:
64-
time.sleep(0.05)
65-
# up to 1 min to report the results
66-
dt = time.monotonic() - start_time
67-
if dt > 60.0:
68-
raise RuntimeError("Timed out waiting for results (%.1f sec)" % dt)
63+
64+
# up to 1 min to report the results
65+
for _ in support.sleeping_retry(60, "Timed out waiting for results"):
66+
if results:
67+
break
6968
7069
results.sort()
7170
print(start_method, "->", results)
@@ -86,19 +85,17 @@ def f(x):
8685
import sys
8786
import time
8887
from multiprocessing import Pool, set_start_method
88+
from test import support
8989
9090
start_method = sys.argv[1]
9191
set_start_method(start_method)
9292
results = []
9393
with Pool(5) as pool:
9494
pool.map_async(int, [1, 4, 9], callback=results.extend)
95-
start_time = time.monotonic()
96-
while not results:
97-
time.sleep(0.05)
98-
# up to 1 min to report the results
99-
dt = time.monotonic() - start_time
100-
if dt > 60.0:
101-
raise RuntimeError("Timed out waiting for results (%.1f sec)" % dt)
95+
# up to 1 min to report the results
96+
for _ in support.sleeping_retry(60, "Timed out waiting for results"):
97+
if results:
98+
break
10299
103100
results.sort()
104101
print(start_method, "->", results)

‎Lib/test/test_signal.py‎

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -812,13 +812,14 @@ def test_itimer_virtual(self):
812812
signal.signal(signal.SIGVTALRM,self.sig_vtalrm)
813813
signal.setitimer(self.itimer,0.3,0.2)
814814

815-
start_time=time.monotonic()
816-
whiletime.monotonic()-start_time<60.0:
815+
for_insupport.busy_retry(60.0,error=False):
817816
# use up some virtual time by doing real work
818817
_=pow(12345,67890,10000019)
819818
ifsignal.getitimer(self.itimer)== (0.0,0.0):
820-
break# sig_vtalrm handler stopped this itimer
821-
else:# Issue 8424
819+
# sig_vtalrm handler stopped this itimer
820+
break
821+
else:
822+
# bpo-8424
822823
self.skipTest("timeout: likely cause: machine too slow or load too "
823824
"high")
824825

@@ -832,13 +833,14 @@ def test_itimer_prof(self):
832833
signal.signal(signal.SIGPROF,self.sig_prof)
833834
signal.setitimer(self.itimer,0.2,0.2)
834835

835-
start_time=time.monotonic()
836-
whiletime.monotonic()-start_time<60.0:
836+
for_insupport.busy_retry(60.0,error=False):
837837
# do some work
838838
_=pow(12345,67890,10000019)
839839
ifsignal.getitimer(self.itimer)== (0.0,0.0):
840-
break# sig_prof handler stopped this itimer
841-
else:# Issue 8424
840+
# sig_prof handler stopped this itimer
841+
break
842+
else:
843+
# bpo-8424
842844
self.skipTest("timeout: likely cause: machine too slow or load too "
843845
"high")
844846

@@ -1307,8 +1309,6 @@ def handler(signum, frame):
13071309
self.setsig(signal.SIGALRM,handler)# for ITIMER_REAL
13081310

13091311
expected_sigs=0
1310-
deadline=time.monotonic()+support.SHORT_TIMEOUT
1311-
13121312
whileexpected_sigs<N:
13131313
# Hopefully the SIGALRM will be received somewhere during
13141314
# initial processing of SIGUSR1.
@@ -1317,8 +1317,9 @@ def handler(signum, frame):
13171317

13181318
expected_sigs+=2
13191319
# Wait for handlers to run to avoid signal coalescing
1320-
whilelen(sigs)<expected_sigsandtime.monotonic()<deadline:
1321-
time.sleep(1e-5)
1320+
for_insupport.sleeping_retry(support.SHORT_TIMEOUT,error=False):
1321+
iflen(sigs)>=expected_sigs:
1322+
break
13221323

13231324
# All ITIMER_REAL signals should have been delivered to the
13241325
# Python handler

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp