def join(self):
        """
        Wait for subprocesses completion or timeout; postprocess the profiler results with delegate(s)
        """
        done, not_done = concurrent.futures.wait(self._futures, self._timeout)
        for f in not_done:
            # attempts cancelling, will fail if call is being executed or has finished
            f.cancel()
        cancelled_cmds = [
            cmd for cmd, f in zip(self._cmds, self._futures) if f.cancelled()
        ]
        if cancelled_cmds:
            raise RuntimeError(
                f"Profiler timed out after {self._timeout} sec. "
                "Try increasing the timeout. "
                f"Cancelled profilers: {cancelled_cmds}"
            )
        for _ in [f for f in self._futures if f.done() or f.running()]:
            # sync point between futures and queue.
            # wait for callbacks to finish
            self._done_queue.get(timeout=self._timeout)
        self._postprocessing_delegate.postprocess_results()

        Wait for subprocesses completion or timeout; postprocess the profiler results with delegate(s)
        
python/aitemplate/backend/profiler_runner.py