Skip to content

Commit 99ecd32

Browse files
authored
fix timeout checker "dictionary changed size during iteration" bug (#411)
1 parent 968d56f commit 99ecd32

File tree

1 file changed

+13
-7
lines changed

1 file changed

+13
-7
lines changed

mutmut/__main__.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import inspect
55
import itertools
66
import json
7-
from multiprocessing import Pool, set_start_method
7+
from multiprocessing import Pool, set_start_method, Lock
88
import os
99
import resource
1010
import shutil
@@ -77,6 +77,7 @@
7777
34: 'skipped',
7878
35: 'suspicious',
7979
36: 'timeout',
80+
-24: 'timeout', # SIGXCPU
8081
24: 'timeout', # SIGXCPU
8182
152: 'timeout', # SIGXCPU
8283
255: 'timeout',
@@ -282,15 +283,17 @@ def load(self):
282283

283284
def register_pid(self, *, pid, key, estimated_time_of_tests):
284285
self.key_by_pid[pid] = key
285-
self.start_time_by_pid[pid] = datetime.now()
286+
with START_TIMES_BY_PID_LOCK:
287+
self.start_time_by_pid[pid] = datetime.now()
286288
self.estimated_time_of_tests_by_pid[pid] = estimated_time_of_tests
287289

288290
def register_result(self, *, pid, exit_code):
289291
assert self.key_by_pid[pid] in self.exit_code_by_key
290292
self.exit_code_by_key[self.key_by_pid[pid]] = exit_code
291293
# TODO: maybe rate limit this? Saving on each result can slow down mutation testing a lot if the test run is fast.
292294
del self.key_by_pid[pid]
293-
del self.start_time_by_pid[pid]
295+
with START_TIMES_BY_PID_LOCK:
296+
del self.start_time_by_pid[pid]
294297
self.save()
295298

296299
def stop_children(self):
@@ -860,6 +863,9 @@ def stop_all_children(mutants):
860863
for m, _, _ in mutants:
861864
m.stop_children()
862865

866+
# used to copy the global mutmut.config to subprocesses
867+
set_start_method('fork')
868+
START_TIMES_BY_PID_LOCK = Lock()
863869

864870
def timeout_checker(mutants):
865871
def inner_timout_checker():
@@ -868,7 +874,10 @@ def inner_timout_checker():
868874

869875
now = datetime.now()
870876
for m, mutant_name, result in mutants:
871-
for pid, start_time in m.start_time_by_pid.items():
877+
# copy dict inside lock, so it is not modified by another process while we iterate it
878+
with START_TIMES_BY_PID_LOCK:
879+
start_times_by_pid = dict(m.start_time_by_pid)
880+
for pid, start_time in start_times_by_pid.items():
872881
run_time = now - start_time
873882
if run_time.total_seconds() > (m.estimated_time_of_tests_by_mutant[mutant_name] + 1) * 4:
874883
try:
@@ -882,9 +891,6 @@ def inner_timout_checker():
882891
@click.option('--max-children', type=int)
883892
@click.argument('mutant_names', required=False, nargs=-1)
884893
def run(mutant_names, *, max_children):
885-
# used to copy the global mutmut.config to subprocesses
886-
set_start_method('fork')
887-
888894
assert isinstance(mutant_names, (tuple, list)), mutant_names
889895
_run(mutant_names, max_children)
890896

0 commit comments

Comments
 (0)