Repository navigation
Publish nothing of a CI run cancelled before its builds finish, and correct the CI policy docs - #16726
Conversation
…orrect the CI policy docs Follow-up to #16673. publishGradle needed only buildGradle, so on any push run a cancel more than a few minutes in still published the superseded commit's Gradle plugins, and on a run without tests also its core and docs but not its Forge, which was still building. 8.0.x run 39638916842 shows the first case: its plugins went out at 17:13 and a newer push cancelled the run at 17:26, before its tests finished. publishGradle now also waits for buildForge and validateDependencies, so a cancel before the builds finish publishes nothing of that commit, and a commit whose Forge build or dependency validation failed is no longer published. A cancel after the builds publishes the whole chain on a run without tests, and only the Gradle plugins while tests are still running; the comments in gradle.yml and the CI policy section of AGENTS.md now say that instead of claiming that a cancelled commit is never published. AGENTS.md also no longer lists the snapshot publish among the jobs that run on pull requests, and names force-cancel for a publish that a normal cancel cannot stop. CONTRIBUTING.md no longer says every push to a branch cancels the run in flight, which only CI and the Groovy joint build do. RELEASE.md notes that a merge cancels the manual test run of the release branch, since both share a concurrency group.
jamesfredley
left a comment
There was a problem hiding this comment.
Approving. publishGradle now waits for buildForge and validateDependencies and requires each result to be success, so a cancel or failure before those builds finish publishes nothing. A cancel after the builds still lets the documented chain publish. The docs match the workflow.
|
@matrei thank you for following up on this.
As for the discussion topics:
|
codeconsole
left a comment
There was a problem hiding this comment.
Approving. This is the fix I asked for on #16673: publishGradle now needs buildForge and validateDependencies and checks that each of them succeeded. publish and everything after it need publishGradle, so a cancel or a failure before the builds finish publishes nothing. I checked the job graph against two real runs:
- 7.0.x run 39615263956 (the merge of #16673, no tests):
buildGradleended about 6.2 minutes in,validateDependenciesat 7.8 andbuildForgeat 14.2.publishGradlehad already published by then (8.5 to 11.8). With this change, a cancel any time before 14.2 publishes nothing. - 8.0.x run 39638916842:
publishGradleran from 17:10:46 to 17:13:11, and the cancel came at about 17:26 while everyBuild Grails Forgeleg was still running. With this change,publishGradlewould have been skipped.
The PR also merges into 8.0.x, 8.1.x and 9.0.x without conflicts. Both CI failures here are unrelated: the Forge code style job got a 429 from repo.grails.org, and the Release Drafter was cancelled by its per-base-branch concurrency group. The RELEASE.md and CONTRIBUTING.md wording matches the concurrency settings of every workflow.
I left two inline comments; neither blocks:
- One test job isn't in
publish'sneeds, so the new "only publishGradle" sentence is wrong during a window that shows up in real runs. - When this merges up, the same reasoning applies to three more 8.0.x jobs.
A note on the cost section: on a run without tests, the core, Forge and docs publishes move back by the same amount as the plugin snapshot, because publish waits for publishGradle. In 39615263956 that would have been about 5 minutes.
On discussion point 1, for the weekly: @jdaugherty, a merge commit does get its own message, but this repository also allows squash and rebase merges. The squash title setting is COMMIT_OR_PR_TITLE, so squashing a single-commit PR reuses that commit's title. A [skip tests] head commit then carries into the push unless the person merging edits the title. A rebase merge keeps the commit's message as it is. The other half of the point is the PR run itself. On a fork PR into 8.0.x or above, a [skip tests] head commit means the PR's tests never ran, which is easy to miss when every check is green.
| # validateDependencies), so nothing of the cancelled commit is published when the cancel arrives before the | ||
| # builds have finished. A later cancel lets the publish jobs whose needs have already succeeded or been skipped | ||
| # run: on a run without tests that is the whole chain, and on a run whose tests are still running only | ||
| # publishGradle, because a cancelled test job fails the explicit checks of publish and of everything after it. |
There was a problem hiding this comment.
buildRerunTasks is a test job, but it isn't in publish's needs. While it is the only test job still running, this sentence (and the same one at AGENTS.md:258) doesn't hold. Its result is never checked either, so the core is published even if it fails.
The window shows up in real runs. In 8.0.x run 39644767954:
publishstarted 236.7 minutes in, right after the lastBuild Grails-Coreleg finished.Build Grails-Core Rerunning all Taskskept running until 260.3.
A cancel during those 24 minutes publishes the whole chain, and a failure there wouldn't have stopped it either. On 8.0.x, casSecurityConfig is missing from publish's needs in the same way: it was added in 155a845, after ee2b481 made the test jobs gate the publish.
I'd add buildRerunTasks to publish's needs here, with the same success || skipped check, and add casSecurityConfig in the merge-up. In 39644767954 that would have delayed the core snapshot by those 24 minutes. If that's too slow, narrow the wording to "a cancelled test job that publish needs" instead.
There was a problem hiding this comment.
Good catch, thanks. I added buildRerunTasks to the needs and the checks of publish in dbf938c. I'd rather take the 24 minutes than publish a core whose --rerun-tasks build failed: it is the only core test run whose results can't come from the build cache.
The same commit adds a line to the publish comment and a rule to AGENTS.md: every test job goes into the needs of publish, and every job that builds or validates without tests goes into those of publishGradle. That should catch the next casSecurityConfig.
| needs.validateDependencies.result == 'success' | ||
| }} | ||
| needs: [ buildGradle ] | ||
| needs: [ buildGradle, buildForge, validateDependencies ] |
There was a problem hiding this comment.
This merges into 8.0.x and 9.0.x without conflicts, but both branches also have validateBomProperties, buildLogic and validateGithubBranch, and none of them is in these needs. In 39644767954 all three finished well before buildForge (at 7.3, 15.3 and 7.0 minutes, against 55.9), so the cancel behaviour still holds there. The problems are elsewhere:
- A commit whose BOM property validation failed would still be published.
- On those branches, "every build job (buildGradle, buildForge and validateDependencies)" in the comment at the top would no longer be true.
It's worth adding the three jobs to needs and to the check in the merge-up commit.
There was a problem hiding this comment.
Agreed. With buildRerunTasks added, the merge-up now conflicts on the needs line of publish on 8.0.x and above, because those branches also list hibernate7Functional and neo4jFunctional there. So the merge-up needs a hand anyway. I checked this resolution on 8.0.x, 8.1.x and 9.0.x:
publishGradle:
needs: [ buildGradle, buildForge, validateDependencies, validateBomProperties, validateGithubBranch, buildLogic ]
publish:
needs: [ publishGradle, build, buildRerunTasks, functional, security, securityConfig, casSecurityConfig, redis, hibernate5Functional, hibernate7Functional, mongodbFunctional, neo4jFunctional ]It also needs the matching result checks (success for the new build jobs, success || skipped for casSecurityConfig), and the three build jobs added to the list in the comment at the top and in AGENTS.md. With that, the job-graph check passes on all three branches, every job is upstream of publish, and actionlint is clean. I can do the 8.0.x merge-up of this PR myself, or open a PR against 8.0.x right after it, whichever suits whoever merges up.
publish did not need buildRerunTasks, so the core snapshot was published while that test job was still running, and even when it failed. A cancel in that window published the whole chain, although the concurrency comment says a cancel while tests are running publishes only the Gradle plugins. Add it to the needs of publish and to its explicit checks, and state in the publish comment and in AGENTS.md which jobs belong in the needs of publish and of publishGradle.
|
Thanks both. @codeconsole, you're right about the cost: on a run without tests, everything after @jdaugherty, the drafts point isn't about publishing, sorry for the confusion. On a draft, On |
|
@matrei Let's discuss these 2 items in the weekly and then merge what's been done already? Do you agree? |
jamesfredley
left a comment
There was a problem hiding this comment.
A cancelled run no longer publishes, and the CI policy text matches that. 7.0.x is the right line.
|
@jdaugherty, agreed. I've merged 7.0.x in to resolve the conflict with #16779 in |
🚨 Jobs failed without executing tests 🚨Failed Jobs without Test Failures❌ Build Health / Forge Projects 🏷️ Commit: 93b8c0e Rerun ControlsClick the checkbox to trigger a rerun:
Learn more about TestLens at testlens.app/docs. |
Summary
This is a follow-up to #16673. It contains the review points that were still open when it was merged: @codeconsole's change request and the second review round.
Publish chain after a cancel
The problem.
publishGradleneeded onlybuildGradle. Since #16673, a newer push cancels the run in flight, and the publish jobs run onalways(). So on any push run, a cancel more than a few minutes in still published part of the superseded commit:publishGradlepublished at 17:13, and a newer push cancelled the run at 17:26, before its tests had finished.The fix.
publishGradlenow also waits forbuildForgeandvalidateDependencies, with the same explicitsuccesschecks.publishnow also waits forbuildRerunTasks, the only test job it did not need, so the core is no longer published while that job is still running, or after it failed (@codeconsole found this in review).publishand everything after it already wait forpublishGradle, so:The comments in
gradle.ymland the CI policy section ofAGENTS.mdnow describe this, instead of saying that a cancelled commit is never published.The cost is that the publishes start later:
publishand everything after it wait forpublishGradle. In 7.0.x run 39615263956,publishGradlestarted at 8.5 minutes andbuildForgeended at 14.2, so the plugins, core, Forge and docs would each have been published about 5 to 6 minutes later.buildRerunTasks. In 39644767954,publishstarted at 236.7 minutes andbuildRerunTasksended at 260.3, so the core snapshot would have been published 24 minutes later.Docs
AGENTS.md:gh api -X POST repos/apache/grails-core/actions/runs/<run id>/force-cancel) for a publish that a normal cancel cannot stop. The upload's retry step lets a stuck publish keep running for hours.CONTRIBUTING.md: no longer says that every push to a branch cancels the run in flight. Only CI and the Groovy joint build do; for drafts, the relevant case is a push to a pull request, which every workflow cancels.RELEASE.md: notes that a merge to the release branch cancels the manual test run, because both share a concurrency group.Merging up
needsline ofpublishconflicts on 8.0.x, 8.1.x and 9.0.x, which also listhibernate7Functionalandneo4jFunctionalthere. The resolution keeps both sides and addscasSecurityConfig, the CAS test job that 155a845 added to 8.0.x without adding it topublish, together with itssuccess || skippedcheck.validateBomProperties,validateGithubBranchandbuildLogic. They go into theneedsofpublishGradlewith asuccesscheck, and into the list of build jobs in the comment at the top ofgradle.ymland inAGENTS.md.publish, and actionlint is clean.Not changed here, discussed at the weekly
These are policy questions rather than bugs, so they were left out of this PR and discussed at the weekly:
[skip tests]on pull requests. Since Gate CI through one policy: draft and no-ci pull requests, tests by branch and marker #16673, a[skip tests]head commit skips the tests on a pull request into 8.0.x and above too, and on a fork pull request the author picks that commit (@codeconsole). Outcome: a follow-up pull request makes the marker count on a pull request only when its author isOWNER,MEMBERorCOLLABORATOR.run cion drafts. On a draft,run cialso runs the full test matrix, so a draft into an older release branch or a side branch can't get a build-only run. Outcome: kept as it is. Before Gate CI through one policy: draft and no-ci pull requests, tests by branch and marker #16673, every draft ran the full matrix.Verification
Job-graph check:
needsresolves.needsresult lists that job, there is no cycle, and everyalways()job checks the result of every job it needs.gradle.ymlother thanpolicyand the publish jobs is upstream ofpublish, and every test job is in itsneeds.actionlint 1.7.7: clean on the changed workflows. Its shellcheck notes are all in scripts this change doesn't touch.
./gradlew rat: passes.Publish chain under cancels. I modelled
gradle.yml's job graph with GitHub's documented rules and cancelled it at every minute. The timings are rounded from the 7.0.x merge push of Gate CI through one policy: draft and no-ci pull requests, tests by branch and marker #16673: builds end at 4, 5 and 11 minutes, and tests at 61.buildGradleendsbuildGradle, beforebuildForgeendsbuildGradleendsbuildGradle, beforebuildForgeendsBefore the second commit, a cancel while
buildRerunTaskswas the only test job still running published everything. In 8.0.x run 39644767954 that window lasted 24 minutes.First real run. The publish jobs only run on a push in
apache, so the merge push to 7.0.x is the first real run of this change. On that run,publishGradleshould start afterbuildForge, andpublish,publishForge,verifyWrapperanddocsshould follow as before.