Skip to content

Commit 62af730

Browse files
committed
fix infinite loop in cached_tasks transforms
Detect dependency cycles instead of looping forever, by using taskgraph.graph.Graph instead of a custom graph traversal. Update the graphs in the tests to not have multiple tasks with the same label, as that breaks uniqueness assumptions.
1 parent 1d225ed commit 62af730

File tree

2 files changed

+29
-29
lines changed

2 files changed

+29
-29
lines changed

src/taskgraph/transforms/cached_tasks.py

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,8 @@
33
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
44

55

6-
from collections import deque
7-
86
import taskgraph
7+
from taskgraph.graph import Graph
98
from taskgraph.transforms.base import TransformSequence
109
from taskgraph.util.cached_tasks import add_optimization
1110

@@ -15,25 +14,19 @@
1514
def order_tasks(config, tasks):
1615
"""Iterate image tasks in an order where parent tasks come first."""
1716
kind_prefix = config.kind + "-"
18-
19-
pending = deque(tasks)
20-
task_labels = {task["label"] for task in pending}
21-
emitted = set()
22-
while True:
23-
try:
24-
task = pending.popleft()
25-
except IndexError:
26-
break
27-
parents = {
28-
task
29-
for task in task.get("dependencies", {}).values()
30-
if task.startswith(kind_prefix)
31-
}
32-
if parents and not emitted.issuperset(parents & task_labels):
33-
pending.append(task)
34-
continue
35-
emitted.add(task["label"])
36-
yield task
17+
pending = {task["label"]: task for task in tasks}
18+
nodes = set(pending)
19+
edges = {
20+
(label, dep, "")
21+
for label in pending
22+
for dep in pending[label].get("dependencies", {}).values()
23+
if dep.startswith(kind_prefix)
24+
}
25+
graph = Graph(nodes, edges)
26+
27+
for label in graph.visit_postorder():
28+
yield pending.pop(label)
29+
assert not pending
3730

3831

3932
def format_task_digest(cached_task):

test/test_transforms_cached_tasks.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ def assert_cache_basic(tasks):
5555
def assert_cache_with_dependency(tasks):
5656
handle_exception(tasks)
5757
assert len(tasks) == 2
58+
tasks = sorted(tasks, key=lambda t: t["label"])
5859
assert tasks[1] == {
5960
"attributes": {
6061
"cached_task": {
@@ -65,7 +66,7 @@ def assert_cache_with_dependency(tasks):
6566
},
6667
"dependencies": {"edge": "dep-cached"},
6768
"description": "description",
68-
"label": "cached-task",
69+
"label": "cached-with-dep",
6970
"optimization": {
7071
"index-search": [
7172
"test-domain.cache.level-3.cached-task.v2.cache-foo.hash.db201e53944fccbb16736c8153a14de39748c0d290de84bd976c11ddcc413089",
@@ -92,15 +93,16 @@ def assert_cache_with_non_cached_dependency(e):
9293

9394
def assert_chain_of_trust_influences_digest(tasks):
9495
assert len(tasks) == 3
95-
# The first two tasks are chain-of-trust unspecified, and chain-of-trust: False
96+
tasks = sorted(tasks, key=lambda t: t["label"]) # cot-false, cot-true, no-cot
97+
# The first and third tasks are chain-of-trust: false, and chain-of-trust unspecified
9698
# which should result in the same digest.
9799
digest_0 = tasks[0]["attributes"]["cached_task"]["digest"]
98-
digest_1 = tasks[1]["attributes"]["cached_task"]["digest"]
99-
assert digest_0 == digest_1
100-
101-
# The third task is chain-of-trust: True, and should have a different digest
102100
digest_2 = tasks[2]["attributes"]["cached_task"]["digest"]
103-
assert digest_0 != digest_2
101+
assert digest_0 == digest_2
102+
103+
# The second task is chain-of-trust: True, and should have a different digest
104+
digest_1 = tasks[1]["attributes"]["cached_task"]["digest"]
105+
assert digest_0 != digest_1
104106

105107

106108
@pytest.mark.parametrize(
@@ -141,7 +143,8 @@ def assert_chain_of_trust_influences_digest(tasks):
141143
"type": "cached-task.v2",
142144
"name": "cache-foo",
143145
"digest-data": ["abc"],
144-
}
146+
},
147+
"label": "cached-no-dep",
145148
},
146149
# This task has same digest-data, but a dependency on a cached task.
147150
{
@@ -151,6 +154,7 @@ def assert_chain_of_trust_influences_digest(tasks):
151154
"digest-data": ["abc"],
152155
},
153156
"dependencies": {"edge": "dep-cached"},
157+
"label": "cached-with-dep",
154158
},
155159
],
156160
# kind config
@@ -198,6 +202,7 @@ def assert_chain_of_trust_influences_digest(tasks):
198202
"name": "cache-foo",
199203
"digest-data": ["abc"],
200204
},
205+
"label": "no-cot",
201206
# no explicit chain of trust configuration; should be the
202207
# same as when it is set to False
203208
},
@@ -207,6 +212,7 @@ def assert_chain_of_trust_influences_digest(tasks):
207212
"name": "cache-foo",
208213
"digest-data": ["abc"],
209214
},
215+
"label": "cot-false",
210216
"worker": {
211217
"chain-of-trust": False,
212218
},
@@ -217,6 +223,7 @@ def assert_chain_of_trust_influences_digest(tasks):
217223
"name": "cache-foo",
218224
"digest-data": ["abc"],
219225
},
226+
"label": "cot-true",
220227
"worker": {"chain-of-trust": True},
221228
},
222229
],

0 commit comments

Comments
 (0)