Skip to content

Commit 4bad652

Browse files
committed
fix: improve error handling on task creation failure
This prints the label of the task we failed to create for convenience, as well as defers failing the Decision task until after we've attempted all tasks. This way we'll see all the scope errors at once rather than needing to fix them one at a time.
1 parent 1e1ed20 commit 4bad652

File tree

2 files changed

+37
-11
lines changed

2 files changed

+37
-11
lines changed

src/taskgraph/create.py

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,18 @@
2020
testing = False
2121

2222

23+
class CreateTasksException(Exception):
24+
"""Exception raised when one or more tasks could not be created."""
25+
26+
def __init__(self, errors: dict[str, Exception]):
27+
message = ""
28+
for label, exc in errors.items():
29+
message += f"\nERROR: Could not create '{label}':\n\n"
30+
message += "\n".join(f" {line}" for line in str(exc).splitlines()) + "\n"
31+
32+
super().__init__(message)
33+
34+
2335
def create_tasks(graph_config, taskgraph, label_to_taskid, params, decision_task_id):
2436
taskid_to_label = {t: l for l, t in label_to_taskid.items()}
2537

@@ -50,6 +62,8 @@ def create_tasks(graph_config, taskgraph, label_to_taskid, params, decision_task
5062
session = get_session()
5163
with futures.ThreadPoolExecutor(concurrency) as e:
5264
fs = {}
65+
fs_to_task = {}
66+
skipped = set()
5367

5468
# We can't submit a task until its dependencies have been submitted.
5569
# So our strategy is to walk the graph and submit tasks once all
@@ -58,25 +72,29 @@ def create_tasks(graph_config, taskgraph, label_to_taskid, params, decision_task
5872
alltasks = tasklist.copy()
5973

6074
def schedule_tasks():
61-
# bail out early if any futures have failed
62-
if any(f.done() and f.exception() for f in fs.values()):
63-
return
64-
6575
to_remove = set()
6676
new = set()
6777

6878
def submit(task_id, label, task_def):
6979
fut = e.submit(create_task, session, task_id, label, task_def)
7080
new.add(fut)
7181
fs[task_id] = fut
82+
fs_to_task[fut] = (task_id, label)
7283

7384
for task_id in tasklist:
7485
task_def = taskgraph.tasks[task_id].task
75-
# If we haven't finished submitting all our dependencies yet,
76-
# come back to this later.
7786
# Some dependencies aren't in our graph, so make sure to filter
7887
# those out
7988
deps = set(task_def.get("dependencies", [])) & alltasks
89+
90+
# If one of the dependencies didn't get created, don't attempt to submit.
91+
if any(d in skipped for d in deps):
92+
skipped.add(task_id)
93+
to_remove.add(task_id)
94+
continue
95+
96+
# If we haven't finished submitting all our dependencies yet,
97+
# come back to this later.
8098
if any((d not in fs or not fs[d].done()) for d in deps):
8199
continue
82100

@@ -90,16 +108,24 @@ def submit(task_id, label, task_def):
90108
submit(slugid(), taskid_to_label[task_id], task_def)
91109
tasklist.difference_update(to_remove)
92110

93-
# as each of those futures complete, try to schedule more tasks
111+
# As each of those futures complete, try to schedule more tasks.
94112
for f in futures.as_completed(new):
95113
schedule_tasks()
96114

97-
# start scheduling tasks and run until everything is scheduled
115+
# Start scheduling tasks and run until everything is scheduled.
98116
schedule_tasks()
99117

100-
# check the result of each future, raising an exception if it failed
118+
# Check the result of each future and save the exception for later
119+
# printing if it failed.
120+
errors = {}
101121
for f in futures.as_completed(fs.values()):
102-
f.result()
122+
if exc := f.exception():
123+
task_id, label = fs_to_task[f]
124+
skipped.add(task_id)
125+
errors[label] = exc
126+
127+
if errors:
128+
raise CreateTasksException(errors)
103129

104130

105131
def create_task(session, task_id, label, task_def):

src/taskgraph/decision.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ def taskgraph_decision(options, parameters=None):
148148
shutil.copy2(RUN_TASK_DIR / "fetch-content", ARTIFACTS_DIR)
149149

150150
# actually create the graph
151-
create_tasks(
151+
return create_tasks(
152152
tgg.graph_config,
153153
tgg.morphed_task_graph,
154154
tgg.label_to_taskid,

0 commit comments

Comments
 (0)