1 name: Restart Preempted Libc++ Workflow
3 # The libc++ builders run on preemptable VMs, which can be shutdown at any time.
4 # This workflow identifies when a workflow run was canceled due to the VM being preempted,
5 # and restarts the workflow run.
7 # We identify a canceled workflow run by checking the annotations of the check runs in the check suite,
8 # which should contain the message "The runner has received a shutdown signal."
10 # Note: If a job is both preempted and also contains a non-preemption failure, we do not restart the workflow.
14 workflows: [Build and Test libc\+\+]
23 if: github.repository_owner == 'llvm' && (github.event.workflow_run.conclusion == 'failure' || github.event.workflow_run.conclusion == 'cancelled')
29 runs-on: ubuntu-latest
32 uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea #v7.0.1
35 const failure_regex = /Process completed with exit code 1./
36 const preemption_regex = /The runner has received a shutdown signal/
38 const wf_run = context.payload.workflow_run
39 core.notice(`Running on "${wf_run.display_title}" by @${wf_run.actor.login} (event: ${wf_run.event})\nWorkflow run URL: ${wf_run.html_url}`)
42 async function create_check_run(conclusion, message) {
43 // Create a check run on the given workflow run to indicate if
44 // we are restarting the workflow or not.
45 if (conclusion != 'success' && conclusion != 'skipped' && conclusion != 'neutral') {
46 core.setFailed('Invalid conclusion: ' + conclusion)
48 await github.rest.checks.create({
49 owner: context.repo.owner,
50 repo: context.repo.repo,
51 name: 'Restart Preempted Job',
52 head_sha: wf_run.head_sha,
54 conclusion: conclusion,
56 title: 'Restarted Preempted Job',
62 console.log('Listing check runs for suite')
63 const check_suites = await github.rest.checks.listForSuite({
64 owner: context.repo.owner,
65 repo: context.repo.repo,
66 check_suite_id: context.payload.workflow_run.check_suite_id,
67 per_page: 100 // FIXME: We don't have 100 check runs yet, but we should handle this better.
71 for (check_run of check_suites.data.check_runs) {
72 console.log('Checking check run: ' + check_run.id);
73 if (check_run.status != 'completed') {
74 console.log('Check run was not completed. Skipping.');
77 if (check_run.conclusion != 'failure' && check_run.conclusion != 'cancelled') {
78 console.log('Check run had conclusion: ' + check_run.conclusion + '. Skipping.');
81 check_run_ids.push(check_run.id);
84 has_preempted_job = false;
86 for (check_run_id of check_run_ids) {
87 console.log('Listing annotations for check run: ' + check_run_id);
89 annotations = await github.rest.checks.listAnnotations({
90 owner: context.repo.owner,
91 repo: context.repo.repo,
92 check_run_id: check_run_id
95 for (annotation of annotations.data) {
96 if (annotation.annotation_level != 'failure') {
100 const preemption_match = annotation.message.match(preemption_regex);
102 if (preemption_match != null) {
103 console.log('Found preemption message: ' + annotation.message);
104 has_preempted_job = true;
107 const failure_match = annotation.message.match(failure_regex);
108 if (failure_match != null) {
109 // We only want to restart the workflow if all of the failures were due to preemption.
110 // We don't want to restart the workflow if there were other failures.
111 core.notice('Choosing not to rerun workflow because we found a non-preemption failure' +
112 'Failure message: "' + annotation.message + '"');
113 await create_check_run('skipped', 'Choosing not to rerun workflow because we found a non-preemption failure\n'
114 + 'Failure message: ' + annotation.message)
120 if (!has_preempted_job) {
121 core.notice('No preempted jobs found. Not restarting workflow.');
122 await create_check_run('neutral', 'No preempted jobs found. Not restarting workflow.')
126 core.notice("Restarted workflow: " + context.payload.workflow_run.id);
127 await github.rest.actions.reRunWorkflowFailedJobs({
128 owner: context.repo.owner,
129 repo: context.repo.repo,
130 run_id: context.payload.workflow_run.id
132 await create_check_run('success', 'Restarted workflow run due to preempted job')
135 if: github.repository_owner == 'llvm' && (github.event.workflow_run.conclusion == 'failure' || github.event.workflow_run.conclusion == 'cancelled') && github.event.actor.login == 'ldionne' # TESTING ONLY
136 name: "Restart Job (test)"
141 runs-on: ubuntu-latest
143 - name: "Restart Job (test)"
144 uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea #v7.0.1
147 const FAILURE_REGEX = /Process completed with exit code 1./
148 const PREEMPTION_REGEX = /(The runner has received a shutdown signal)|(The operation was canceled)/
154 const wf_run = context.payload.workflow_run
155 log(`Running on "${wf_run.display_title}" by @${wf_run.actor.login} (event: ${wf_run.event})\nWorkflow run URL: ${wf_run.html_url}`)
157 log('Listing check runs for suite')
158 const check_suites = await github.rest.checks.listForSuite({
159 owner: context.repo.owner,
160 repo: context.repo.repo,
161 check_suite_id: context.payload.workflow_run.check_suite_id,
162 per_page: 100 // FIXME: We don't have 100 check runs yet, but we should handle this better.
166 legitimate_failures = [];
167 for (check_run of check_suites.data.check_runs) {
168 log(`Checking check run: ${check_run.id}`);
169 if (check_run.status != 'completed') {
170 log('Check run was not completed. Skipping.');
174 if (check_run.conclusion != 'failure' && check_run.conclusion != 'cancelled') {
175 log(`Check run had conclusion: ${check_run.conclusion}. Skipping.`);
179 annotations = await github.rest.checks.listAnnotations({
180 owner: context.repo.owner,
181 repo: context.repo.repo,
182 check_run_id: check_run.id
185 preemption_annotation = annotations.data.find(function(annotation) {
186 return annotation.annotation_level == 'failure' &&
187 annotation.message.match(PREEMPTION_REGEX) != null;
189 if (preemption_annotation != null) {
190 log(`Found preemption message: ${preemption_annotation.message}`);
191 preemptions.push(check_run);
195 failure_annotation = annotations.data.find(function(annotation) {
196 return annotation.annotation_level == 'failure' &&
197 annotation.message.match(FAILURE_REGEX) != null;
199 if (failure_annotation != null) {
200 log(`Found legitimate failure annotation: ${failure_annotation.message}`);
201 legitimate_failures.push(check_run);
207 log('Found some preempted jobs');
208 if (legitimate_failures) {
209 log('Also found some legitimate failures, so not restarting the workflow.');
211 log('Did not find any legitimate failures. Restarting workflow.');
212 await github.rest.actions.reRunWorkflowFailedJobs({
213 owner: context.repo.owner,
214 repo: context.repo.repo,
215 run_id: context.payload.workflow_run.id
219 log('Did not find any preempted jobs. Not restarting the workflow.');