From patchwork Sun Apr 11 04:27:27 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 1464744 X-Patchwork-Delegate: sjg@chromium.org Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=lists.denx.de (client-ip=85.214.62.61; helo=phobos.denx.de; envelope-from=u-boot-bounces@lists.denx.de; receiver=) Authentication-Results: ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.a=rsa-sha256 header.s=google header.b=nmc6Ly1z; dkim-atps=neutral Received: from phobos.denx.de (phobos.denx.de [85.214.62.61]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 4FHzRH0swYz9sVt for ; Sun, 11 Apr 2021 14:29:31 +1000 (AEST) Received: from h2850616.stratoserver.net (localhost [IPv6:::1]) by phobos.denx.de (Postfix) with ESMTP id 910CE80515; Sun, 11 Apr 2021 06:28:48 +0200 (CEST) Authentication-Results: phobos.denx.de; dmarc=pass (p=none dis=none) header.from=chromium.org Authentication-Results: phobos.denx.de; spf=pass smtp.mailfrom=u-boot-bounces@lists.denx.de Authentication-Results: phobos.denx.de; dkim=pass (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.b="nmc6Ly1z"; dkim-atps=neutral Received: by phobos.denx.de (Postfix, from userid 109) id EA8EF80590; Sun, 11 Apr 2021 06:28:38 +0200 (CEST) X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on phobos.denx.de X-Spam-Level: X-Spam-Status: No, score=-2.0 required=5.0 tests=BAYES_00,DKIMWL_WL_HIGH, DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,SPF_HELO_NONE autolearn=ham autolearn_force=no version=3.4.2 Received: from mail-pf1-x435.google.com (mail-pf1-x435.google.com [IPv6:2607:f8b0:4864:20::435]) (using TLSv1.3 with cipher TLS_AES_128_GCM_SHA256 (128/128 bits)) (No client certificate requested) by phobos.denx.de (Postfix) with ESMTPS id DF5FD804E7 for ; Sun, 11 Apr 2021 06:28:28 +0200 (CEST) Authentication-Results: phobos.denx.de; dmarc=pass (p=none dis=none) header.from=chromium.org Authentication-Results: phobos.denx.de; spf=pass smtp.mailfrom=sjg@chromium.org Received: by mail-pf1-x435.google.com with SMTP id s11so6975503pfm.1 for ; Sat, 10 Apr 2021 21:28:28 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=SC9yP8YmafqZlhVz0zyVdLec2RHglRGH/DkpK+cVJsQ=; b=nmc6Ly1zlDHWSEK/OHdMs+bHlhzN1HTUtL1F1kq/vrYSRTndj+o4i/4cqFzQhprGaW 2l7fxcQWeL57g1C7iDZ3nRBRiE0fuIGHbe9prnfcYfi69H4+7P9P4ALFTI2RCTYRxgBA BKWVAoGsrumYlR8wOS5AEiQw81zZqC+twVQ+E= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=SC9yP8YmafqZlhVz0zyVdLec2RHglRGH/DkpK+cVJsQ=; b=lG7QMyzf89Oacv567G+piMmE1rrJe0p1wQXNsrUnnE4N6tD/uz5n+huosHZmL/Kdac bkZwffNnpbq9kLLp62CKWE7UREfgD+1cHFs1JHvE5w6Ektu7K4Sy4OkBwWBoeTy8q68P kkKfUnP+leBRKDKUXX2ORaqumdeusJ0nfyQA5+jDhT/555OHQLG9l8BiFKPztL89Tqdx FA3b7qcJnzRPjt62bNRhK6e9VdwpzDDAWzOVW9h8HV5E/FzXF4mG+VKkzMZc60PCdwZc u3BjNEN1pTa0vA9AWMa9TjMZ3YFhImUnXCfDtqbdtZ3K5dQ/BTwz1bdIyeaP/QUXblFM 6OAQ== X-Gm-Message-State: AOAM530rJ34hw0Luo6cGMlHbE82GVZ8GlM4tDwV4tI0iP8i+mj8KlZEI Nwt0l1RB9EwEL/ht5sJTY3j/OypOz+oOOaYr X-Google-Smtp-Source: ABdhPJw7lJcRwYFCZ/e9Vw2gVLJUddHBJ92AKfLR2Z2Yqy68YM7u6Eid9RrS9NyTvOsskCf3fjY9MA== X-Received: by 2002:a63:3189:: with SMTP id x131mr20348171pgx.430.1618115307058; Sat, 10 Apr 2021 21:28:27 -0700 (PDT) Received: from localhost.localdomain (203-173-153-56.dsl.dyn.ihug.co.nz. [203.173.153.56]) by smtp.gmail.com with ESMTPSA id 186sm2856589pfe.212.2021.04.10.21.28.24 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 10 Apr 2021 21:28:26 -0700 (PDT) From: Simon Glass To: U-Boot Mailing List Cc: Tom Rini , Marek Vasut , Simon Glass Subject: [PATCH 3/4] buildman: Handle exceptions in threads gracefully Date: Sun, 11 Apr 2021 16:27:27 +1200 Message-Id: <20210411162720.3.I473fd4e60936c695b1387b5a63437577ab9930aa@changeid> X-Mailer: git-send-email 2.31.1.295.g9ea45b61b8-goog In-Reply-To: <20210411042728.3549261-1-sjg@chromium.org> References: <20210411042728.3549261-1-sjg@chromium.org> MIME-Version: 1.0 X-BeenThere: u-boot@lists.denx.de X-Mailman-Version: 2.1.34 Precedence: list List-Id: U-Boot discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: u-boot-bounces@lists.denx.de Sender: "U-Boot" X-Virus-Scanned: clamav-milter 0.102.4 at phobos.denx.de X-Virus-Status: Clean There have been at least a few cases where an exception has occurred in a thread and resulted in buildman hanging: running out of disk space and getting a unicode error. Handle these by collecting a list of exceptions, printing them out and reporting failure if any are found. Add a test for this. Signed-off-by: Simon Glass Signed-off-by: Simon Glass --- tools/buildman/builder.py | 22 +++++++++++++++++----- tools/buildman/builderthread.py | 14 +++++++++++++- tools/buildman/control.py | 16 +++++++++++----- tools/buildman/func_test.py | 15 +++++++++++++++ 4 files changed, 56 insertions(+), 11 deletions(-) diff --git a/tools/buildman/builder.py b/tools/buildman/builder.py index be8a8fa13a6..ce852eb03a3 100644 --- a/tools/buildman/builder.py +++ b/tools/buildman/builder.py @@ -182,6 +182,7 @@ class Builder: only useful for testing in-tree builds. work_in_output: Use the output directory as the work directory and don't write to a separate output directory. + thread_exceptions: List of exceptions raised by thread jobs Private members: _base_board_dict: Last-summarised Dict of boards @@ -235,7 +236,8 @@ class Builder: no_subdirs=False, full_path=False, verbose_build=False, mrproper=False, per_board_out_dir=False, config_only=False, squash_config_y=False, - warnings_as_errors=False, work_in_output=False): + warnings_as_errors=False, work_in_output=False, + test_thread_exceptions=False): """Create a new Builder object Args: @@ -262,6 +264,9 @@ class Builder: warnings_as_errors: Treat all compiler warnings as errors work_in_output: Use the output directory as the work directory and don't write to a separate output directory. + test_thread_exceptions: Uses for tests only, True to make the + threads raise an exception instead of reporting their result. + This simulates a failure in the code somewhere """ self.toolchains = toolchains self.base_dir = base_dir @@ -311,13 +316,16 @@ class Builder: self._re_migration_warning = re.compile(r'^={21} WARNING ={22}\n.*\n=+\n', re.MULTILINE | re.DOTALL) + self.thread_exceptions = [] + self.test_thread_exceptions = test_thread_exceptions if self.num_threads: self._single_builder = None self.queue = queue.Queue() self.out_queue = queue.Queue() for i in range(self.num_threads): - t = builderthread.BuilderThread(self, i, mrproper, - per_board_out_dir) + t = builderthread.BuilderThread( + self, i, mrproper, per_board_out_dir, + test_exception=test_thread_exceptions) t.setDaemon(True) t.start() self.threads.append(t) @@ -1676,6 +1684,7 @@ class Builder: Tuple containing: - number of boards that failed to build - number of boards that issued warnings + - list of thread exceptions raised """ self.commit_count = len(commits) if commits else 1 self.commits = commits @@ -1689,7 +1698,7 @@ class Builder: Print('\rStarting build...', newline=False) self.SetupBuild(board_selected, commits) self.ProcessResult(None) - + self.thread_exceptions = [] # Create jobs to build all commits for each board for brd in board_selected.values(): job = builderthread.BuilderJob() @@ -1728,5 +1737,8 @@ class Builder: rate = float(self.count) / duration.total_seconds() msg += ', duration %s, rate %1.2f' % (duration, rate) Print(msg) + if self.thread_exceptions: + Print('Failed: %d thread exceptions' % len(self.thread_exceptions), + colour=self.col.RED) - return (self.fail, self.warned) + return (self.fail, self.warned, self.thread_exceptions) diff --git a/tools/buildman/builderthread.py b/tools/buildman/builderthread.py index 76ffbb66be6..ddb3eab8c03 100644 --- a/tools/buildman/builderthread.py +++ b/tools/buildman/builderthread.py @@ -97,12 +97,15 @@ class BuilderThread(threading.Thread): test_exception: Used for testing; True to raise an exception instead of reporting the build result """ + def __init__(self, builder, thread_num, mrproper, per_board_out_dir, + test_exception=False): """Set up a new builder thread""" threading.Thread.__init__(self) self.builder = builder self.thread_num = thread_num self.mrproper = mrproper self.per_board_out_dir = per_board_out_dir + self.test_exception = test_exception def Make(self, commit, brd, stage, cwd, *args, **kwargs): """Run 'make' on a particular commit and board. @@ -449,7 +452,12 @@ class BuilderThread(threading.Thread): Args: result: CommandResult object containing the results of the build + + Raises: + ValueError if self.test_exception is true (for testing) """ + if self.test_exception: + raise ValueError('test exception') if self.thread_num != -1: self.builder.out_queue.put(result) else: @@ -547,5 +555,9 @@ class BuilderThread(threading.Thread): """ while True: job = self.builder.queue.get() - self.RunJob(job) + try: + self.RunJob(job) + except Exception as e: + print('Thread exception:', e) + self.builder.thread_exceptions.append(e) self.builder.queue.task_done() diff --git a/tools/buildman/control.py b/tools/buildman/control.py index 5fcfba7ca36..a98d1b4c06f 100644 --- a/tools/buildman/control.py +++ b/tools/buildman/control.py @@ -110,7 +110,7 @@ def ShowToolchainPrefix(boards, toolchains): return None def DoBuildman(options, args, toolchains=None, make_func=None, boards=None, - clean_dir=False): + clean_dir=False, test_thread_exceptions=False): """The main control code for buildman Args: @@ -126,6 +126,9 @@ def DoBuildman(options, args, toolchains=None, make_func=None, boards=None, boards. If this is None it will be created and scanned. clean_dir: Used for tests only, indicates that the existing output_dir should be removed before starting the build + test_thread_exceptions: Uses for tests only, True to make the threads + raise an exception instead of reporting their result. This simulates + a failure in the code somewhere """ global builder @@ -330,7 +333,8 @@ def DoBuildman(options, args, toolchains=None, make_func=None, boards=None, config_only=options.config_only, squash_config_y=not options.preserve_config_y, warnings_as_errors=options.warnings_as_errors, - work_in_output=options.work_in_output) + work_in_output=options.work_in_output, + test_thread_exceptions=test_thread_exceptions) builder.force_config_on_failure = not options.quick if make_func: builder.do_make = make_func @@ -370,9 +374,11 @@ def DoBuildman(options, args, toolchains=None, make_func=None, boards=None, if options.summary: builder.ShowSummary(commits, board_selected) else: - fail, warned = builder.BuildBoards(commits, board_selected, - options.keep_outputs, options.verbose) - if fail: + fail, warned, excs = builder.BuildBoards( + commits, board_selected, options.keep_outputs, options.verbose) + if excs: + return 102 + elif fail: return 100 elif warned and not options.ignore_warnings: return 101 diff --git a/tools/buildman/func_test.py b/tools/buildman/func_test.py index c6997d1ee25..61e3012d2b1 100644 --- a/tools/buildman/func_test.py +++ b/tools/buildman/func_test.py @@ -16,6 +16,7 @@ from buildman import toolchain from patman import command from patman import gitutil from patman import terminal +from patman import test_util from patman import tools settings_data = ''' @@ -219,6 +220,8 @@ class TestFunctional(unittest.TestCase): return command.RunPipe([[self._buildman_pathname] + list(args)], capture=True, capture_stderr=True) + def _RunControl(self, *args, boards=None, clean_dir=False, + test_thread_exceptions=False): """Run buildman Args: @@ -226,6 +229,9 @@ class TestFunctional(unittest.TestCase): boards: clean_dir: Used for tests only, indicates that the existing output_dir should be removed before starting the build + test_thread_exceptions: Uses for tests only, True to make the threads + raise an exception instead of reporting their result. This simulates + a failure in the code somewhere Returns: result code from buildman @@ -234,6 +240,8 @@ class TestFunctional(unittest.TestCase): options, args = cmdline.ParseArgs() result = control.DoBuildman(options, args, toolchains=self._toolchains, make_func=self._HandleMake, boards=boards or self._boards, + clean_dir=clean_dir, + test_thread_exceptions=test_thread_exceptions) self._builder = control.builder return result @@ -597,3 +605,10 @@ class TestFunctional(unittest.TestCase): with self.assertRaises(SystemExit) as e: self._RunControl('-w', clean_dir=False) self.assertIn("specify -o", str(e.exception)) + + def testThreadExceptions(self): + """Test that exceptions in threads are reported""" + with test_util.capture_sys_output() as (stdout, stderr): + self.assertEqual(102, self._RunControl('-o', self._output_dir, + test_thread_exceptions=True)) + self.assertIn('Thread exception: test exception', stdout.getvalue())