Skip to content

Commit be62fab

Browse files
authored
donate_cpu_lib.py: improved timeout reporting and process handling (danmar#3153)
1 parent bc8eb16 commit be62fab

2 files changed

Lines changed: 59 additions & 35 deletions

File tree

.github/workflows/scriptcheck.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ jobs:
3838
python -m pip install pytest
3939
python -m pip install pygments
4040
python -m pip install requests
41+
python -m pip install psutil
4142
4243
- name: run Shellcheck
4344
if: matrix.python-version == '3.8'

tools/donate_cpu_lib.py

Lines changed: 58 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@
1010
import signal
1111
import tarfile
1212
import shlex
13+
import psutil
1314

1415

1516
# Version scheme (MAJOR.MINOR.PATCH) should orientate on "Semantic Versioning" https://semver.org/
1617
# Every change in this script should result in increasing the version number accordingly (exceptions may be cosmetic
1718
# changes)
18-
CLIENT_VERSION = "1.3.8"
19+
CLIENT_VERSION = "1.3.9"
1920

2021
# Timeout for analysis with Cppcheck in seconds
2122
CPPCHECK_TIMEOUT = 30 * 60
@@ -255,14 +256,25 @@ def has_include(path, includes):
255256
def run_command(cmd):
256257
print(cmd)
257258
startTime = time.time()
259+
comm = None
258260
p = subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE, stderr=subprocess.PIPE, preexec_fn=os.setsid)
259261
try:
260262
comm = p.communicate(timeout=CPPCHECK_TIMEOUT)
261263
return_code = p.returncode
264+
p = None
262265
except subprocess.TimeoutExpired:
263-
os.killpg(os.getpgid(p.pid), signal.SIGTERM) # Send the signal to all the process groups
264-
comm = p.communicate()
265266
return_code = RETURN_CODE_TIMEOUT
267+
# terminate all the child processes so we get messages about which files were hanging
268+
child_procs = psutil.Process(p.pid).children(recursive=True)
269+
if len(child_procs) > 0:
270+
for child in child_procs:
271+
child.terminate()
272+
comm = p.communicate()
273+
p = None
274+
finally:
275+
if p:
276+
os.killpg(os.getpgid(p.pid), signal.SIGTERM) # Send the signal to all the process groups
277+
comm = p.communicate()
266278
stop_time = time.time()
267279
stdout = comm[0].decode(encoding='utf-8', errors='ignore')
268280
stderr = comm[1].decode(encoding='utf-8', errors='ignore')
@@ -290,16 +302,51 @@ def scan_package(work_path, cppcheck_path, jobs, libraries):
290302
cppcheck_cmd = cppcheck_path + '/cppcheck' + ' ' + options
291303
cmd = 'nice ' + cppcheck_cmd
292304
returncode, stdout, stderr, elapsed_time = run_command(cmd)
305+
306+
# collect messages
307+
information_messages_list = []
308+
issue_messages_list = []
309+
internal_error_messages_list = []
310+
count = 0
311+
for line in stderr.split('\n'):
312+
if ': information: ' in line:
313+
information_messages_list.append(line + '\n')
314+
elif line:
315+
issue_messages_list.append(line + '\n')
316+
if re.match(r'.*:[0-9]+:.*\]$', line):
317+
count += 1
318+
if ': error: Internal error: ' in line:
319+
internal_error_messages_list.append(line + '\n')
320+
print('Number of issues: ' + str(count))
321+
# Collect timing information
322+
stdout_lines = stdout.split('\n')
323+
timing_info_list = []
324+
overall_time_found = False
325+
max_timing_lines = 6
326+
current_timing_lines = 0
327+
for reverse_line in reversed(stdout_lines):
328+
if reverse_line.startswith('Overall time:'):
329+
overall_time_found = True
330+
if overall_time_found:
331+
if not reverse_line or current_timing_lines >= max_timing_lines:
332+
break
333+
timing_info_list.insert(0, ' ' + reverse_line + '\n')
334+
current_timing_lines += 1
335+
timing_str = ''.join(timing_info_list)
336+
337+
# detect errors
293338
sig_num = -1
294339
sig_msg = 'Internal error: Child process crashed with signal '
295340
sig_pos = stderr.find(sig_msg)
296341
if sig_pos != -1:
297342
sig_start_pos = sig_pos + len(sig_msg)
298343
sig_num = int(stderr[sig_start_pos:stderr.find(' ', sig_start_pos)])
299344
print('cppcheck finished with ' + str(returncode) + ('' if sig_num == -1 else ' (signal ' + str(sig_num) + ')'))
345+
300346
if returncode == RETURN_CODE_TIMEOUT:
301347
print('Timeout!')
302-
return returncode, stdout, '', elapsed_time, options, ''
348+
return returncode, ''.join(internal_error_messages_list), '', elapsed_time, options, ''
349+
303350
# generate stack trace for SIGSEGV, SIGABRT, SIGILL, SIGFPE, SIGBUS
304351
if returncode in (-11, -6, -4, -8, -7) or sig_num in (11, 6, 4, 8, 7):
305352
print('Crash!')
@@ -319,48 +366,24 @@ def scan_package(work_path, cppcheck_path, jobs, libraries):
319366
if not stacktrace:
320367
stacktrace = stdout
321368
return returncode, stacktrace, '', returncode, options, ''
369+
322370
if returncode != 0:
323371
print('Error!')
324372
if returncode > 0:
325373
returncode = -100-returncode
326374
return returncode, stdout, '', returncode, options, ''
327-
err_s = 'Internal error: Child process crashed with signal '
328-
err_pos = stderr.find(err_s)
329-
if err_pos != -1:
375+
376+
if sig_pos != -1:
330377
print('Error!')
331-
pos2 = stderr.find(' [cppcheckError]', err_pos)
332-
signr = int(stderr[err_pos+len(err_s):pos2])
378+
pos2 = stderr.find(' [cppcheckError]', sig_pos)
379+
signr = int(stderr[sig_pos+len(sig_msg):pos2])
333380
return -signr, '', '', -signr, options, ''
381+
334382
thr_pos = stderr.find('#### ThreadExecutor')
335383
if thr_pos != -1:
336384
print('Thread!')
337385
return -222, stderr[thr_pos:], '', -222, options, ''
338-
information_messages_list = []
339-
issue_messages_list = []
340-
count = 0
341-
for line in stderr.split('\n'):
342-
if ': information: ' in line:
343-
information_messages_list.append(line + '\n')
344-
elif line:
345-
issue_messages_list.append(line + '\n')
346-
if re.match(r'.*:[0-9]+:.*\]$', line):
347-
count += 1
348-
print('Number of issues: ' + str(count))
349-
# Collect timing information
350-
stdout_lines = stdout.split('\n')
351-
timing_info_list = []
352-
overall_time_found = False
353-
max_timing_lines = 6
354-
current_timing_lines = 0
355-
for reverse_line in reversed(stdout_lines):
356-
if reverse_line.startswith('Overall time:'):
357-
overall_time_found = True
358-
if overall_time_found:
359-
if not reverse_line or current_timing_lines >= max_timing_lines:
360-
break
361-
timing_info_list.insert(0, ' ' + reverse_line + '\n')
362-
current_timing_lines += 1
363-
timing_str = ''.join(timing_info_list)
386+
364387
return count, ''.join(issue_messages_list), ''.join(information_messages_list), elapsed_time, options, timing_str
365388

366389

0 commit comments

Comments
 (0)