Skip to content

Commit e9d96e2

Browse files
committed
corrections wrt signals send to subprocesses in test
1 parent 5e5128f commit e9d96e2

File tree

2 files changed

+38
-30
lines changed

2 files changed

+38
-30
lines changed

irods/test/data_obj_test.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2951,9 +2951,9 @@ def test_replica_truncate__issue_534(self):
29512951
if data_objs.exists(data_path):
29522952
data_objs.unlink(data_path, force=True)
29532953

2954-
def test_reasonable_handling_of_parallel_download_threads_after_termination_signal__issue_722(self):
2955-
from test_parallel_put_sigint_response.py import test
2956-
test(self)
2954+
def test_handling_of_termination_signals_during_multithread_get__issue_722(self):
2955+
from irods.test.modules.test_signal_handling_in_multithread_get import test as test__issue_722
2956+
test__issue_722(self)
29572957

29582958
if __name__ == "__main__":
29592959
# let the tests find the parent irods lib

irods/test/modules/test_parallel_put_sigint_response.py renamed to irods/test/modules/test_signal_handling_in_multithread_get.py

Lines changed: 35 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
import os
33
import re
4-
from signal import SIGINT, SIGTERM
4+
import signal
55
import subprocess
66
import sys
77
import tempfile
@@ -15,50 +15,58 @@
1515
OBJECT_NAME = 'data_get_issue__722'
1616
LOCAL_TEMPFILE_NAME = 'data_object_for_issue_722.dat'
1717

18-
_clock_resolution = max(.01, *(time.clock_getres(getattr(time,symbol))
19-
for symbol in dir(time) if symbol.startswith('CLOCK_')))
18+
19+
_clock_polling_interval = max(.01, time.clock_getres(time.CLOCK_BOOTTIME))
20+
21+
2022
def wait_till_true(function, timeout=None):
2123
start_time = time.clock_gettime_ns(time.CLOCK_BOOTTIME)
2224
while not (truth_value := function()):
2325
if timeout is not None and (time.clock_gettime_ns(time.CLOCK_BOOTTIME)-start_time)*1e-9 > timeout:
2426
break
25-
time.sleep(_clock_resolution)
27+
time.sleep(_clock_polling_interval)
2628
return truth_value
2729

2830

29-
def test(test_case, sigs = (SIGINT,SIGTERM)):
31+
def test(test_case, signal_names = ("SIGTERM",#"SIGINT"
32+
)):
3033
"""Creates a child process executing a long get() and ensures the process can be
3134
terminated using SIGINT or SIGTERM.
3235
"""
3336
program = os.path.join(test_modules.__path__[0], os.path.basename(__file__))
3437

35-
# Call into this same module as a command. This will initiate another Python process that
36-
# performs a lengthy data object "get" operation (see the main body of the script, below.)
37-
process = subprocess.Popen([sys.executable, program],
38-
stderr=subprocess.PIPE,
39-
stdout=subprocess.PIPE,
40-
text = True)
41-
42-
# Wait for download process to reach the point of spawning data transfer threads. In Python 3.9+ versions
43-
# of the concurrent.futures module, these are nondaemon threads and will block the exit of the main thread
44-
# unless measures are taken (#722).
45-
localfile = process.stdout.readline().strip()
46-
test_case.assertTrue(wait_till_true(lambda:os.path.exists(localfile) and os.stat(localfile).st_size > OBJECT_SIZE//2),
47-
"Parallel download from data_objects.get() probably experienced a fatal error before spawning auxiliary data transfer threads."
48-
)
49-
50-
for sig in sigs:
51-
# Interrupt the sub-process with the given signal.
38+
for signal_name in signal_names:
39+
# Call into this same module as a command. This will initiate another Python process that
40+
# performs a lengthy data object "get" operation (see the main body of the script, below.)
41+
process = subprocess.Popen([sys.executable, program],
42+
stderr=subprocess.PIPE,
43+
stdout=subprocess.PIPE,
44+
text = True)
45+
46+
# Wait for download process to reach the point of spawning data transfer threads. In Python 3.9+ versions
47+
# of the concurrent.futures module, these are nondaemon threads and will block the exit of the main thread
48+
# unless measures are taken (#722).
49+
localfile = process.stdout.readline().strip()
50+
test_case.assertTrue(wait_till_true(lambda:os.path.exists(localfile) and os.stat(localfile).st_size > OBJECT_SIZE//2),
51+
"Parallel download from data_objects.get() probably experienced a fatal error before spawning auxiliary data transfer threads."
52+
)
53+
54+
signal_message_info = f"While testing signal {signal_name}"
55+
sig = getattr(signal, signal_name)
56+
57+
# Interrupt the subprocess with the given signal.
5258
process.send_signal(sig)
53-
# Assert that this signal is what killed the sub-process, rather than a timed out process "wait" or a natural exit
59+
# Assert that this signal is what killed the subprocess, rather than a timed out process "wait" or a natural exit
5460
# due to misproper or incomplete handling of the signal.
5561
try:
56-
test_case.assertEqual(process.wait(timeout = 15), -sig)
62+
test_case.assertEqual(process.wait(timeout = 15), -sig, "{signal_message_info}: unexpected subprocess return code.")
5763
except subprocess.TimeoutExpired as timeout_exc:
58-
test_case.fail("Sub-process timed out before exit. Non-daemon thread(s) probably prevented subprocess's main thread from exiting")
64+
test_case.fail(f"{signal_message_info}: subprocess timed out before terminating. "
65+
"Non-daemon thread(s) probably prevented subprocess's main thread from exiting.")
5966
# Assert that in the case of SIGINT, the process registered a KeyboardInterrupt.
60-
if sig == SIGINT:
61-
test_case.assertTrue(re.search('KeyboardInterrupt',process.stderr.read()))
67+
if sig == signal.SIGINT:
68+
test_case.assertTrue(re.search('KeyboardInterrupt', process.stderr.read()),
69+
"{signal_message_info}: Expected 'KeyboardInterrupt' in log output.")
6270

6371

6472
if __name__ == "__main__":

0 commit comments

Comments
 (0)