diff options
author | Richard Purdie <richard.purdie@linuxfoundation.org> | 2019-07-11 17:05:19 +0100 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2019-07-16 13:53:17 +0100 |
commit | 1069c364170ffbf1bff276fd965aeb85efbc22f8 (patch) | |
tree | 5ec3000158c05890b3b5bfc0eaf7bfc2a6ece66d /bitbake/lib/bb | |
parent | 5333f31fc7555f0a39246d4fcafa71debf71abe2 (diff) | |
download | poky-1069c364170ffbf1bff276fd965aeb85efbc22f8.tar.gz |
bitbake: runqueue: Optimise multiconfig with overlapping setscene
Currently if a multiconfig build contains different configurations which
have overlapping sstate artefacts, it will build them multiple times.
This is clearly suboptimal and not what users want/expect.
This adds code to detect this and stall all but one of the setscne tasks
so that once its built, it can be found by the other tasks.
We take care to iterate the multiconfigs in order so try and avoid
dependency loops. We also match on PN+taskname+taskhash since this is
what we know sstate in OE-Core would use. There are some tasks even within
a multiconfig which match hashes (mostly do_populate_lic tasks) but those
have a much higher chance of circular dependency so aren't work attempting
to optimise.
If a deadlock does occur the build will be slower but there is code to
unbreak such a deadlock so it hopefully doens't break anything.
Comments are injected into the test tasks so they have different task
hashes and a new test for this optimisation is added.
(Bitbake rev: a75c5fd6d4ec56836de0be2fe679c81297a080ad)
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'bitbake/lib/bb')
-rw-r--r-- | bitbake/lib/bb/runqueue.py | 60 | ||||
-rw-r--r-- | bitbake/lib/bb/tests/runqueue-tests/classes/base.bbclass | 19 | ||||
-rw-r--r-- | bitbake/lib/bb/tests/runqueue-tests/conf/bitbake.conf | 9 | ||||
-rw-r--r-- | bitbake/lib/bb/tests/runqueue-tests/conf/multiconfig/mc1.conf | 1 | ||||
-rw-r--r-- | bitbake/lib/bb/tests/runqueue-tests/conf/multiconfig/mc2.conf | 1 | ||||
-rw-r--r-- | bitbake/lib/bb/tests/runqueue.py | 19 |
6 files changed, 98 insertions, 11 deletions
diff --git a/bitbake/lib/bb/runqueue.py b/bitbake/lib/bb/runqueue.py index 00c71070d2..fa848326d8 100644 --- a/bitbake/lib/bb/runqueue.py +++ b/bitbake/lib/bb/runqueue.py | |||
@@ -68,6 +68,14 @@ def build_tid(mc, fn, taskname): | |||
68 | return "mc:" + mc + ":" + fn + ":" + taskname | 68 | return "mc:" + mc + ":" + fn + ":" + taskname |
69 | return fn + ":" + taskname | 69 | return fn + ":" + taskname |
70 | 70 | ||
71 | # Index used to pair up potentially matching multiconfig tasks | ||
72 | # We match on PN, taskname and hash being equal | ||
73 | def pending_hash_index(tid, rqdata): | ||
74 | (mc, fn, taskname, taskfn) = split_tid_mcfn(tid) | ||
75 | pn = rqdata.dataCaches[mc].pkg_fn[taskfn] | ||
76 | h = rqdata.runtaskentries[tid].hash | ||
77 | return pn + ":" + "taskname" + h | ||
78 | |||
71 | class RunQueueStats: | 79 | class RunQueueStats: |
72 | """ | 80 | """ |
73 | Holds statistics on the tasks handled by the associated runQueue | 81 | Holds statistics on the tasks handled by the associated runQueue |
@@ -1717,6 +1725,7 @@ class RunQueueExecute: | |||
1717 | self.build_stamps = {} | 1725 | self.build_stamps = {} |
1718 | self.build_stamps2 = [] | 1726 | self.build_stamps2 = [] |
1719 | self.failed_tids = [] | 1727 | self.failed_tids = [] |
1728 | self.sq_deferred = {} | ||
1720 | 1729 | ||
1721 | self.stampcache = {} | 1730 | self.stampcache = {} |
1722 | 1731 | ||
@@ -1921,17 +1930,32 @@ class RunQueueExecute: | |||
1921 | # Find the next setscene to run | 1930 | # Find the next setscene to run |
1922 | for nexttask in self.rqdata.runq_setscene_tids: | 1931 | for nexttask in self.rqdata.runq_setscene_tids: |
1923 | if nexttask in self.sq_buildable and nexttask not in self.sq_running and self.sqdata.stamps[nexttask] not in self.build_stamps.values(): | 1932 | if nexttask in self.sq_buildable and nexttask not in self.sq_running and self.sqdata.stamps[nexttask] not in self.build_stamps.values(): |
1924 | if nexttask in self.sqdata.unskippable: | ||
1925 | logger.debug(2, "Setscene task %s is unskippable" % nexttask) | ||
1926 | if nexttask not in self.sqdata.unskippable and len(self.sqdata.sq_revdeps[nexttask]) > 0 and self.sqdata.sq_revdeps[nexttask].issubset(self.scenequeue_covered) and self.check_dependencies(nexttask, self.sqdata.sq_revdeps[nexttask]): | 1933 | if nexttask not in self.sqdata.unskippable and len(self.sqdata.sq_revdeps[nexttask]) > 0 and self.sqdata.sq_revdeps[nexttask].issubset(self.scenequeue_covered) and self.check_dependencies(nexttask, self.sqdata.sq_revdeps[nexttask]): |
1927 | if nexttask not in self.rqdata.target_tids: | 1934 | if nexttask not in self.rqdata.target_tids: |
1928 | logger.debug(2, "Skipping setscene for task %s" % nexttask) | 1935 | logger.debug(2, "Skipping setscene for task %s" % nexttask) |
1929 | self.sq_task_skip(nexttask) | 1936 | self.sq_task_skip(nexttask) |
1930 | self.scenequeue_notneeded.add(nexttask) | 1937 | self.scenequeue_notneeded.add(nexttask) |
1938 | if nexttask in self.sq_deferred: | ||
1939 | del self.sq_deferred[nexttask] | ||
1940 | return True | ||
1941 | if nexttask in self.sq_deferred: | ||
1942 | if self.sq_deferred[nexttask] not in self.runq_complete: | ||
1943 | continue | ||
1944 | logger.debug(1, "Task %s no longer deferred" % nexttask) | ||
1945 | del self.sq_deferred[nexttask] | ||
1946 | valid = self.rq.validate_hashes(set([nexttask]), self.cooker.data, None, False) | ||
1947 | if not valid: | ||
1948 | logger.debug(1, "%s didn't become valid, skipping setscene" % nexttask) | ||
1949 | self.sq_task_failoutright(nexttask) | ||
1931 | return True | 1950 | return True |
1951 | else: | ||
1952 | self.sqdata.outrightfail.remove(nexttask) | ||
1932 | if nexttask in self.sqdata.outrightfail: | 1953 | if nexttask in self.sqdata.outrightfail: |
1954 | logger.debug(2, 'No package found, so skipping setscene task %s', nexttask) | ||
1933 | self.sq_task_failoutright(nexttask) | 1955 | self.sq_task_failoutright(nexttask) |
1934 | return True | 1956 | return True |
1957 | if nexttask in self.sqdata.unskippable: | ||
1958 | logger.debug(2, "Setscene task %s is unskippable" % nexttask) | ||
1935 | task = nexttask | 1959 | task = nexttask |
1936 | break | 1960 | break |
1937 | if task is not None: | 1961 | if task is not None: |
@@ -1982,7 +2006,7 @@ class RunQueueExecute: | |||
1982 | if self.can_start_task(): | 2006 | if self.can_start_task(): |
1983 | return True | 2007 | return True |
1984 | 2008 | ||
1985 | if not self.sq_live and not self.sqdone: | 2009 | if not self.sq_live and not self.sqdone and not self.sq_deferred: |
1986 | logger.info("Setscene tasks completed") | 2010 | logger.info("Setscene tasks completed") |
1987 | logger.debug(1, 'We could skip tasks %s', "\n".join(sorted(self.scenequeue_covered))) | 2011 | logger.debug(1, 'We could skip tasks %s', "\n".join(sorted(self.scenequeue_covered))) |
1988 | 2012 | ||
@@ -2083,6 +2107,13 @@ class RunQueueExecute: | |||
2083 | self.rq.read_workers() | 2107 | self.rq.read_workers() |
2084 | return self.rq.active_fds() | 2108 | return self.rq.active_fds() |
2085 | 2109 | ||
2110 | # No more tasks can be run. If we have deferred setscene tasks we should run them. | ||
2111 | if self.sq_deferred: | ||
2112 | tid = self.sq_deferred.pop(list(self.sq_deferred.keys())[0]) | ||
2113 | logger.warning("Runqeueue deadlocked on deferred tasks, forcing task %s" % tid) | ||
2114 | self.sq_task_failoutright(tid) | ||
2115 | return True | ||
2116 | |||
2086 | if len(self.failed_tids) != 0: | 2117 | if len(self.failed_tids) != 0: |
2087 | self.rq.state = runQueueFailed | 2118 | self.rq.state = runQueueFailed |
2088 | return True | 2119 | return True |
@@ -2347,7 +2378,7 @@ class SQData(object): | |||
2347 | # Setscene tasks directly depended upon by the build | 2378 | # Setscene tasks directly depended upon by the build |
2348 | self.unskippable = set() | 2379 | self.unskippable = set() |
2349 | # List of setscene tasks which aren't present | 2380 | # List of setscene tasks which aren't present |
2350 | self.outrightfail = [] | 2381 | self.outrightfail = set() |
2351 | # A list of normal tasks a setscene task covers | 2382 | # A list of normal tasks a setscene task covers |
2352 | self.sq_covered_tasks = {} | 2383 | self.sq_covered_tasks = {} |
2353 | 2384 | ||
@@ -2510,7 +2541,9 @@ def build_scenequeue_data(sqdata, rqdata, rq, cooker, stampcache, sqrq): | |||
2510 | 2541 | ||
2511 | rqdata.init_progress_reporter.next_stage() | 2542 | rqdata.init_progress_reporter.next_stage() |
2512 | 2543 | ||
2544 | multiconfigs = set() | ||
2513 | for tid in sqdata.sq_revdeps: | 2545 | for tid in sqdata.sq_revdeps: |
2546 | multiconfigs.add(mc_from_tid(tid)) | ||
2514 | if len(sqdata.sq_revdeps[tid]) == 0: | 2547 | if len(sqdata.sq_revdeps[tid]) == 0: |
2515 | sqrq.sq_buildable.add(tid) | 2548 | sqrq.sq_buildable.add(tid) |
2516 | 2549 | ||
@@ -2552,10 +2585,21 @@ def build_scenequeue_data(sqdata, rqdata, rq, cooker, stampcache, sqrq): | |||
2552 | for v in valid: | 2585 | for v in valid: |
2553 | valid_new.append(v) | 2586 | valid_new.append(v) |
2554 | 2587 | ||
2555 | for tid in sqdata.sq_revdeps: | 2588 | hashes = {} |
2556 | if tid not in valid_new and tid not in noexec: | 2589 | for mc in sorted(multiconfigs): |
2557 | logger.debug(2, 'No package found, so skipping setscene task %s', tid) | 2590 | for tid in sqdata.sq_revdeps: |
2558 | sqdata.outrightfail.append(tid) | 2591 | if mc_from_tid(tid) != mc: |
2592 | continue | ||
2593 | if tid not in valid_new and tid not in noexec and tid not in sqrq.scenequeue_notcovered: | ||
2594 | sqdata.outrightfail.add(tid) | ||
2595 | |||
2596 | h = pending_hash_index(tid, rqdata) | ||
2597 | if h not in hashes: | ||
2598 | hashes[h] = tid | ||
2599 | else: | ||
2600 | sqrq.sq_deferred[tid] = hashes[h] | ||
2601 | bb.warn("Deferring %s after %s" % (tid, hashes[h])) | ||
2602 | |||
2559 | 2603 | ||
2560 | class TaskFailure(Exception): | 2604 | class TaskFailure(Exception): |
2561 | """ | 2605 | """ |
diff --git a/bitbake/lib/bb/tests/runqueue-tests/classes/base.bbclass b/bitbake/lib/bb/tests/runqueue-tests/classes/base.bbclass index e174c02dd6..cf38d09224 100644 --- a/bitbake/lib/bb/tests/runqueue-tests/classes/base.bbclass +++ b/bitbake/lib/bb/tests/runqueue-tests/classes/base.bbclass | |||
@@ -4,7 +4,9 @@ SSTATEVALID ??= "" | |||
4 | def stamptask(d): | 4 | def stamptask(d): |
5 | import time | 5 | import time |
6 | 6 | ||
7 | thistask = d.expand("${PN}:${BB_CURRENTTASK}") | 7 | thistask = d.expand("${PN}:${BB_CURRENTTASK}") |
8 | if d.getVar("BB_CURRENT_MC") != "default": | ||
9 | thistask = d.expand("${BB_CURRENT_MC}:${PN}:${BB_CURRENTTASK}") | ||
8 | if thistask in d.getVar("SLOWTASKS").split(): | 10 | if thistask in d.getVar("SLOWTASKS").split(): |
9 | bb.note("Slowing task %s" % thistask) | 11 | bb.note("Slowing task %s" % thistask) |
10 | time.sleep(0.5) | 12 | time.sleep(0.5) |
@@ -13,48 +15,63 @@ def stamptask(d): | |||
13 | f.write(thistask + "\n") | 15 | f.write(thistask + "\n") |
14 | 16 | ||
15 | python do_fetch() { | 17 | python do_fetch() { |
18 | # fetch | ||
16 | stamptask(d) | 19 | stamptask(d) |
17 | } | 20 | } |
18 | python do_unpack() { | 21 | python do_unpack() { |
22 | # unpack | ||
19 | stamptask(d) | 23 | stamptask(d) |
20 | } | 24 | } |
21 | python do_patch() { | 25 | python do_patch() { |
26 | # patch | ||
22 | stamptask(d) | 27 | stamptask(d) |
23 | } | 28 | } |
24 | python do_populate_lic() { | 29 | python do_populate_lic() { |
30 | # populate_lic | ||
25 | stamptask(d) | 31 | stamptask(d) |
26 | } | 32 | } |
27 | python do_prepare_recipe_sysroot() { | 33 | python do_prepare_recipe_sysroot() { |
34 | # prepare_recipe_sysroot | ||
28 | stamptask(d) | 35 | stamptask(d) |
29 | } | 36 | } |
30 | python do_configure() { | 37 | python do_configure() { |
38 | # configure | ||
31 | stamptask(d) | 39 | stamptask(d) |
32 | } | 40 | } |
33 | python do_compile() { | 41 | python do_compile() { |
42 | # compile | ||
34 | stamptask(d) | 43 | stamptask(d) |
35 | } | 44 | } |
36 | python do_install() { | 45 | python do_install() { |
46 | # install | ||
37 | stamptask(d) | 47 | stamptask(d) |
38 | } | 48 | } |
39 | python do_populate_sysroot() { | 49 | python do_populate_sysroot() { |
50 | # populate_sysroot | ||
40 | stamptask(d) | 51 | stamptask(d) |
41 | } | 52 | } |
42 | python do_package() { | 53 | python do_package() { |
54 | # package | ||
43 | stamptask(d) | 55 | stamptask(d) |
44 | } | 56 | } |
45 | python do_package_write_ipk() { | 57 | python do_package_write_ipk() { |
58 | # package_write_ipk | ||
46 | stamptask(d) | 59 | stamptask(d) |
47 | } | 60 | } |
48 | python do_package_write_rpm() { | 61 | python do_package_write_rpm() { |
62 | # package_write_rpm | ||
49 | stamptask(d) | 63 | stamptask(d) |
50 | } | 64 | } |
51 | python do_packagedata() { | 65 | python do_packagedata() { |
66 | # packagedata | ||
52 | stamptask(d) | 67 | stamptask(d) |
53 | } | 68 | } |
54 | python do_package_qa() { | 69 | python do_package_qa() { |
70 | # package_qa | ||
55 | stamptask(d) | 71 | stamptask(d) |
56 | } | 72 | } |
57 | python do_build() { | 73 | python do_build() { |
74 | # build | ||
58 | stamptask(d) | 75 | stamptask(d) |
59 | } | 76 | } |
60 | do_prepare_recipe_sysroot[deptask] = "do_populate_sysroot" | 77 | do_prepare_recipe_sysroot[deptask] = "do_populate_sysroot" |
diff --git a/bitbake/lib/bb/tests/runqueue-tests/conf/bitbake.conf b/bitbake/lib/bb/tests/runqueue-tests/conf/bitbake.conf index 8c7b754dab..96ee1cd5ec 100644 --- a/bitbake/lib/bb/tests/runqueue-tests/conf/bitbake.conf +++ b/bitbake/lib/bb/tests/runqueue-tests/conf/bitbake.conf | |||
@@ -6,6 +6,11 @@ PROVIDES = "${PN}" | |||
6 | PN = "${@bb.parse.vars_from_file(d.getVar('FILE', False),d)[0]}" | 6 | PN = "${@bb.parse.vars_from_file(d.getVar('FILE', False),d)[0]}" |
7 | PF = "${BB_CURRENT_MC}:${PN}" | 7 | PF = "${BB_CURRENT_MC}:${PN}" |
8 | export PATH | 8 | export PATH |
9 | STAMP = "${TOPDIR}/stamps/${PN}" | 9 | TMPDIR ??= "${TOPDIR}" |
10 | T = "${TOPDIR}/workdir/${PN}/temp" | 10 | STAMP = "${TMPDIR}/stamps/${PN}" |
11 | T = "${TMPDIR}/workdir/${PN}/temp" | ||
11 | BB_NUMBER_THREADS = "4" | 12 | BB_NUMBER_THREADS = "4" |
13 | |||
14 | BB_HASHBASE_WHITELIST = "BB_CURRENT_MC" | ||
15 | |||
16 | include conf/multiconfig/${BB_CURRENT_MC}.conf | ||
diff --git a/bitbake/lib/bb/tests/runqueue-tests/conf/multiconfig/mc1.conf b/bitbake/lib/bb/tests/runqueue-tests/conf/multiconfig/mc1.conf new file mode 100644 index 0000000000..ecf23e1c73 --- /dev/null +++ b/bitbake/lib/bb/tests/runqueue-tests/conf/multiconfig/mc1.conf | |||
@@ -0,0 +1 @@ | |||
TMPDIR = "${TOPDIR}/mc1/" | |||
diff --git a/bitbake/lib/bb/tests/runqueue-tests/conf/multiconfig/mc2.conf b/bitbake/lib/bb/tests/runqueue-tests/conf/multiconfig/mc2.conf new file mode 100644 index 0000000000..eef338e4cc --- /dev/null +++ b/bitbake/lib/bb/tests/runqueue-tests/conf/multiconfig/mc2.conf | |||
@@ -0,0 +1 @@ | |||
TMPDIR = "${TOPDIR}/mc2/" | |||
diff --git a/bitbake/lib/bb/tests/runqueue.py b/bitbake/lib/bb/tests/runqueue.py index 4a65b5b6e7..f0cea6483f 100644 --- a/bitbake/lib/bb/tests/runqueue.py +++ b/bitbake/lib/bb/tests/runqueue.py | |||
@@ -198,3 +198,22 @@ class RunQueueTests(unittest.TestCase): | |||
198 | 'b1:packagedata_setscene', 'b1:package_qa_setscene', 'b1:populate_sysroot_setscene'] | 198 | 'b1:packagedata_setscene', 'b1:package_qa_setscene', 'b1:populate_sysroot_setscene'] |
199 | self.assertEqual(set(tasks), set(expected)) | 199 | self.assertEqual(set(tasks), set(expected)) |
200 | 200 | ||
201 | def test_multiconfig_setscene_optimise(self): | ||
202 | with tempfile.TemporaryDirectory(prefix="runqueuetest") as tempdir: | ||
203 | extraenv = { | ||
204 | "BBMULTICONFIG" : "mc1 mc2", | ||
205 | "BB_SIGNATURE_HANDLER" : "basic" | ||
206 | } | ||
207 | cmd = ["bitbake", "b1", "mc:mc1:b1", "mc:mc2:b1"] | ||
208 | setscenetasks = ['package_write_ipk_setscene', 'package_write_rpm_setscene', 'packagedata_setscene', | ||
209 | 'populate_sysroot_setscene', 'package_qa_setscene'] | ||
210 | sstatevalid = "" | ||
211 | tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid, extraenv=extraenv) | ||
212 | expected = ['a1:' + x for x in self.alltasks] + ['b1:' + x for x in self.alltasks] + \ | ||
213 | ['mc1:b1:' + x for x in setscenetasks] + ['mc1:a1:' + x for x in setscenetasks] + \ | ||
214 | ['mc2:b1:' + x for x in setscenetasks] + ['mc2:a1:' + x for x in setscenetasks] + \ | ||
215 | ['mc1:b1:build', 'mc2:b1:build'] | ||
216 | for x in ['mc1:a1:package_qa_setscene', 'mc2:a1:package_qa_setscene', 'a1:build', 'a1:package_qa']: | ||
217 | expected.remove(x) | ||
218 | self.assertEqual(set(tasks), set(expected)) | ||
219 | |||