From 23cf6cb2e4b4d9cb1cd4ea5c109550410df055af Mon Sep 17 00:00:00 2001 From: James Prestwood Date: Fri, 13 Aug 2021 09:24:35 -0700 Subject: [PATCH] test-runner: implement non_block_wait There was a common bit of code all over test-runner and utilities which would wait for 'something' in a loop. At best these loops would do the right thing and use the GLib.iteration call as to not block the main loop, and at worst would not use it and just busy wait. Namespace.non_block_wait unifies all these into a single API to a) do the wait correctly and b) prevent duplicate code. --- tools/test-runner | 80 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 65 insertions(+), 15 deletions(-) diff --git a/tools/test-runner b/tools/test-runner index 87f6ec6d..22e9066f 100755 --- a/tools/test-runner +++ b/tools/test-runner @@ -233,7 +233,7 @@ class Process: if not wait and not check: return - self.pid.wait(timeout=5) + Namespace.non_block_wait(self.wait_for_process, 10, 1) self.killed = True self.ret = self.pid.returncode @@ -245,6 +245,13 @@ class Process: if check and self.ret != 0: raise subprocess.CalledProcessError(returncode=self.ret, cmd=self.args) + def wait_for_process(self, timeout): + try: + self.pid.wait(timeout) + return True + except: + return False + def process_io(self, source): data = source.read() @@ -312,12 +319,7 @@ class Process: self.killed = True def wait_for_socket(self, socket, wait): - waited = 0 - while not os.path.exists(socket): - sleep(0.5) - waited += 0.5 - if waited > wait: - raise Exception("Timed out waiting for socket") + Namespace.non_block_wait(os.path.exists, wait, socket) def __str__(self): return str(self.args) + '\n' @@ -641,7 +643,7 @@ class Namespace: p = self.start_process(['dbus-daemon', '--config-file=%s' % self.dbus_cfg], wait=False, cleanup=self._cleanup_dbus) - p.wait_for_socket(self.dbus_address.split('=')[1], wait=5) + p.wait_for_socket(self.dbus_address.split('=')[1], 5) self._bus = dbus.bus.BusConnection(address_or_type=self.dbus_address) @@ -699,15 +701,62 @@ class Namespace: return False - def wait_for_dbus_service(self, service): - tries = 0 + @staticmethod + def non_block_wait(func, timeout, *args, exception=True): + ''' + Convenience function for waiting in a non blocking + manor using GLibs context iteration i.e. does not block + the main loop while waiting. + + 'func' will be called at least once and repeatedly until + either it returns success, throws an exception, or the + 'timeout' expires. + + 'timeout' is the ultimate timeout in seconds + + '*args' will be passed to 'func' + + If 'exception' is an Exception type it will be raised. + If 'exception' is True a generic TimeoutError will be raised. + Any other value will not result in an exception. + ''' + # Simple class for signaling the wait timeout + class Bool: + def __init__(self, value): + self.value = value + + def wait_timeout_cb(done): + done.value = True + return False + + mainloop = GLib.MainLoop() + done = Bool(False) + + timeout = GLib.timeout_add_seconds(timeout, wait_timeout_cb, done) + context = mainloop.get_context() + + while True: + context.iteration(may_block=False) + + try: + ret = func(*args) + if ret: + GLib.source_remove(timeout) + return ret + except Exception as e: + GLib.source_remove(timeout) + raise e - while not self._bus.name_has_owner(service): - if tries > 200: - raise TimeoutError('DBus service %s did not appear', service) - tries += 1 sleep(0.1) + if done.value == True: + if isinstance(exception, Exception): + raise exception + elif type(exception) == bool and exception: + raise TimeoutError("Timeout on non_block_wait") + else: + return + def __str__(self): ret = 'Namespace: %s\n' % self.name ret += 'Processes:\n' @@ -765,7 +814,8 @@ class TestContext(Namespace): args.extend(['--no-register']) self.start_process(args) - self.wait_for_dbus_service('net.connman.hwsim') + self.non_block_wait(self._bus.name_has_owner, 20, 'net.connman.hwsim', + exception=TimeoutError('net.connman.hwsim did not appear')) for i in range(nradios): name = 'rad%u' % i