thread_pool: support passing thread IDs

Then use that to fix gem5 error log read race.
This commit is contained in:
Ciro Santilli 六四事件 法轮功
2019-05-05 00:00:00 +00:00
parent b49ebb1c8a
commit 5daad53289
5 changed files with 52 additions and 16 deletions

View File

@@ -439,7 +439,8 @@ if one was not given explicitly.
''', ''',
) )
self.add_argument( self.add_argument(
'-u', '--userland', '-u',
'--userland',
help='''\ help='''\
Run the given userland executable in user mode instead of booting the Linux kernel Run the given userland executable in user mode instead of booting the Linux kernel
in full system mode. In gem5, user mode is called Syscall Emulation (SE) mode and in full system mode. In gem5, user mode is called Syscall Emulation (SE) mode and
@@ -459,14 +460,16 @@ CLI arguments to pass to the userland executable.
# Run. # Run.
self.add_argument( self.add_argument(
'-n', '--run-id', default='0', '--port-offset',
type=int,
help='''\ help='''\
ID for run outputs such as gem5's m5out. Allows you to do multiple runs, Increase the ports to be used such as for GDB by an offset to run multiple
and then inspect separate outputs later in different output directories. instances in parallel. Default: the run ID (-n) if that is an integer, otherwise 0.
''' '''
) )
self.add_argument( self.add_argument(
'-P', '--prebuilt', default=False, '--prebuilt',
default=False,
help='''\ help='''\
Use prebuilt packaged host utilities as much as possible instead Use prebuilt packaged host utilities as much as possible instead
of the ones we built ourselves. Saves build time, but decreases of the ones we built ourselves. Saves build time, but decreases
@@ -474,10 +477,11 @@ the likelihood of incompatibilities.
''' '''
) )
self.add_argument( self.add_argument(
'--port-offset', type=int, '--run-id',
default='0',
help='''\ help='''\
Increase the ports to be used such as for GDB by an offset to run multiple ID for run outputs such as gem5's m5out. Allows you to do multiple runs,
instances in parallel. Default: the run ID (-n) if that is an integer, otherwise 0. and then inspect separate outputs later in different output directories.
''' '''
) )
@@ -1418,7 +1422,8 @@ class TestCliFunction(LkmcCliFunction):
run_obj, run_obj,
run_args=None, run_args=None,
test_id=None, test_id=None,
expected_exit_status=None expected_exit_status=None,
thread_id=0,
): ):
''' '''
This is a setup / run / teardown setup for simple tests that just do a single run. This is a setup / run / teardown setup for simple tests that just do a single run.
@@ -1429,10 +1434,12 @@ class TestCliFunction(LkmcCliFunction):
:param run_obj: callable object :param run_obj: callable object
:param run_args: arguments to be passed to the runnable object :param run_args: arguments to be passed to the runnable object
:param test_id: test identifier, to be added in addition to of arch and emulator ids :param test_id: test identifier, to be added in addition to of arch and emulator ids
:param thread_id: which thread the test is running under
''' '''
if run_obj.is_arch_supported(self.env['arch']): if run_obj.is_arch_supported(self.env['arch']):
if run_args is None: if run_args is None:
run_args = {} run_args = {}
run_args['run_id'] = thread_id
test_id_string = self.test_setup(test_id) test_id_string = self.test_setup(test_id)
exit_status = run_obj(**run_args) exit_status = run_obj(**run_args)
return self.test_teardown( return self.test_teardown(

2
run
View File

@@ -715,7 +715,7 @@ Extra options to append at the end of the emulator command line.
if line.rstrip() == self.env['magic_fail_string']: if line.rstrip() == self.env['magic_fail_string']:
exit_status = 1 exit_status = 1
break break
if exit_status != 0: if exit_status != 0 and self.env['show_stdout']:
self.log_error('simulation error detected by parsing logs') self.log_error('simulation error detected by parsing logs')
return exit_status return exit_status

View File

@@ -175,6 +175,7 @@ class ShellHelpers:
if not self.quiet: if not self.quiet:
self._print_thread_safe('+ ' + cmd_string) self._print_thread_safe('+ ' + cmd_string)
if cmd_file is not None: if cmd_file is not None:
os.makedirs(os.path.dirname(cmd_file), exist_ok=True)
with open(cmd_file, 'w') as f: with open(cmd_file, 'w') as f:
f.write('#!/usr/bin/env bash\n') f.write('#!/usr/bin/env bash\n')
f.write(cmd_string) f.write(cmd_string)

View File

@@ -24,7 +24,6 @@ If given, run only the given tests. Otherwise, run all tests.
) )
def timed_main(self): def timed_main(self):
run = self.import_path_main('run')
run_args = self.get_common_args() run_args = self.get_common_args()
run_args['ctrl_c_host'] = True run_args['ctrl_c_host'] = True
run_args['show_stdout'] = False run_args['show_stdout'] = False
@@ -36,6 +35,7 @@ If given, run only the given tests. Otherwise, run all tests.
with ThreadPool( with ThreadPool(
self.run_test, self.run_test,
nthreads=self.env['nproc'], nthreads=self.env['nproc'],
thread_id_arg='thread_id',
) as thread_pool: ) as thread_pool:
try: try:
for path, in_dirnames, in_filenames in self.walk_source_targets( for path, in_dirnames, in_filenames in self.walk_source_targets(
@@ -56,7 +56,7 @@ If given, run only the given tests. Otherwise, run all tests.
error = thread_pool.submit({ error = thread_pool.submit({
'expected_exit_status': test.exit_status, 'expected_exit_status': test.exit_status,
'run_args': cur_run_args, 'run_args': cur_run_args,
'run_obj': run, 'run_obj': self.import_path_main('run'),
'test_id': path_relative_root, 'test_id': path_relative_root,
}) })
if error is not None: if error is not None:

View File

@@ -31,6 +31,7 @@ class ThreadPool:
python3 thread_pool.py 2 -10 20 1 python3 thread_pool.py 2 -10 20 1
python3 thread_pool.py 2 -10 20 2 python3 thread_pool.py 2 -10 20 2
python3 thread_pool.py 2 -10 20 3 python3 thread_pool.py 2 -10 20 3
python3 thread_pool.py 2 -10 20 0 1
.... ....
These ensure that execution stops neatly on error. These ensure that execution stops neatly on error.
@@ -39,7 +40,8 @@ class ThreadPool:
self, self,
func: Callable, func: Callable,
handle_output: Union[Callable[[Any,Any,Exception],Any],None] = None, handle_output: Union[Callable[[Any,Any,Exception],Any],None] = None,
nthreads: Union[int,None] = None nthreads: Union[int,None] = None,
thread_id_arg: Union[str,None] = None,
): ):
''' '''
Start in a thread pool immediately. Start in a thread pool immediately.
@@ -62,6 +64,9 @@ class ThreadPool:
Default: a handler that does nothing and just exits on exception. Default: a handler that does nothing and just exits on exception.
:param nthreads: number of threads to use. Default: nproc. :param nthreads: number of threads to use. Default: nproc.
:param thread_id_arg: if not None, set the argument of func with this name
to a 0-indexed thread ID. This allows function calls to coordinate
usage of external resources such as files or ports.
''' '''
self.func = func self.func = func
if handle_output is None: if handle_output is None:
@@ -69,6 +74,7 @@ class ThreadPool:
self.handle_output = handle_output self.handle_output = handle_output
if nthreads is None: if nthreads is None:
nthreads = len(os.sched_getaffinity(0)) nthreads = len(os.sched_getaffinity(0))
self.thread_id_arg = thread_id_arg
self.nthreads = nthreads self.nthreads = nthreads
self.error_output = None self.error_output = None
self.error_output_lock = threading.Lock() self.error_output_lock = threading.Lock()
@@ -77,6 +83,7 @@ class ThreadPool:
for i in range(self.nthreads): for i in range(self.nthreads):
thread = threading.Thread( thread = threading.Thread(
target=self._func_runner, target=self._func_runner,
args=(i,)
) )
self.threads.append(thread) self.threads.append(thread)
thread.start() thread.start()
@@ -123,11 +130,13 @@ class ThreadPool:
thread.join() thread.join()
return self.error_output return self.error_output
def _func_runner(self): def _func_runner(self, thread_id):
while True: while True:
work = self.in_queue.get(block=True) work = self.in_queue.get(block=True)
if work is None: if work is None:
break break
if self.thread_id_arg is not None:
work[self.thread_id_arg] = thread_id
try: try:
exception = None exception = None
out = self.func(**work) out = self.func(**work)
@@ -147,7 +156,7 @@ class ThreadPool:
self.in_queue.task_done() self.in_queue.task_done()
if __name__ == '__main__': if __name__ == '__main__':
def my_func(i): def func_maybe_raise(i):
''' '''
The main function that will be evaluated. The main function that will be evaluated.
@@ -156,6 +165,10 @@ if __name__ == '__main__':
time.sleep((abs(i) % 4) / 10.0) time.sleep((abs(i) % 4) / 10.0)
return 10.0 / i return 10.0 / i
def func_get_thread(i, thread_id):
time.sleep((abs(i) % 4) / 10.0)
return thread_id
def get_work(min_, max_): def get_work(min_, max_):
''' '''
Generate simple range work for my_func. Generate simple range work for my_func.
@@ -202,14 +215,17 @@ if __name__ == '__main__':
nthreads = None nthreads = None
else: else:
nthreads = None nthreads = None
if argv_len > 2: if argv_len > 2:
min_ = int(sys.argv[2]) min_ = int(sys.argv[2])
else: else:
min_ = 1 min_ = 1
if argv_len > 3: if argv_len > 3:
max_ = int(sys.argv[3]) max_ = int(sys.argv[3])
else: else:
max_ = 100 max_ = 100
if argv_len > 4: if argv_len > 4:
c = sys.argv[4][0] c = sys.argv[4][0]
else: else:
@@ -223,11 +239,23 @@ if __name__ == '__main__':
else: else:
handle_output = handle_output_print handle_output = handle_output_print
if argv_len > 5:
c = sys.argv[5][0]
else:
c = '0'
if c == '1':
my_func = func_get_thread
thread_id_arg = 'thread_id'
else:
my_func = func_maybe_raise
thread_id_arg = None
# Action. # Action.
thread_pool = ThreadPool( thread_pool = ThreadPool(
my_func, my_func,
handle_output, handle_output,
nthreads nthreads,
thread_id_arg,
) )
for work in get_work(min_, max_): for work in get_work(min_, max_):
error = thread_pool.submit(work) error = thread_pool.submit(work)