Skip to content

ci(java): retry a failed merge-queue IT once, report it to #ci-cleanup, retry dependency downloads - #35505

Open
sonika-shah wants to merge 7 commits into
mainfrom
ci/it-retry-safety-net
Open

sonika-shah wants to merge 7 commits into
mainfrom
ci/it-retry-safety-net

Conversation

@sonika-shah

@sonika-shah sonika-shah commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Since #34892 the Java ITs run only in the merge queue, so one flaky test or one network blip ejects a PR and restarts everything queued behind it. In the 12 hours after #35469, 6 PRs were ejected and none of them by their own change: 4 by flaky tests, 2 by Maven Central dropping the connection before any test ran.

This adds a safety net for the merge-queue IT lanes. It does not replace fixing the flaky tests.

  • Retry a failed test once. After the lane finishes, failsafe re-runs only the failed test methods, once, against the same server. If it passes it's reported as a flake (<flakyFailure>, Flakes: N) and the lane stays green. If it fails twice the lane still fails. Green runs cost nothing extra. CI-only via -DintegrationTests.rerunFailingTestsCount=1; the pom default stays 0, so local runs are unchanged.
  • Keep the server up for that retry. Failsafe closes the JUnit launcher session between the two passes, and TestSuiteBootstrap tore the whole stack down on session close, so the retry hit a dead server. Teardown now runs in a JVM shutdown hook.
  • Give the retry fresh names. TestNamespace.prefix is deterministic per JVM/class/method, so the retry reused the failed attempt's names and hit 409 Entity already exists on whatever that attempt created. TestNamespaceExtension now adds __retry to the method id the second time it sees a test's unique id; first attempts are unchanged.
  • Retry a failed dependency download. A lane re-runs mvn verify (up to 3 attempts, 30s apart) only when the attempt failed on a dependency download within 10 minutes and wrote no test report. Before each retry it deletes ~/.m2/**/*.lastUpdated: Maven caches a "not found" there and would otherwise never ask Central again, which is exactly what the Fixes #30361: Show failed rows sample when a test passes within its threshold #35042 ejection's log shows. Test failures and hangs are never retried this way, and every attempt is kept in maven.log.
  • Post the flakes to #ci-cleanup. Each lane annotates the tests that passed only on retry; after a green merge-queue run the workflow posts one message to #ci-cleanup with those tests, the PRs in the group and the run link (same mechanism as the Playwright flaky post to #pw-health). The retry hides nothing: every flake lands in the channel so it gets an owner.

Decision record: ADR:2026-10-15-merge-queue-its-retry-a-failed-test-once.

Verified locally (postgres-opensearch, parallel lane)

Run Result
Throwaway IT that fails its first attempt, then calls /v1/system/version; this PR Attempt 1 fails, attempt 2 reaches the same server (same start timestamp) and passes → Flakes: 1, <flakyFailure> in the XML, BUILD SUCCESS
Throwaway IT that creates an untracked classification via ns.prefix, then fails its first attempt Without the __retry suffix: retry fails 409 Entity already exists → BUILD FAILURE. With it: retry creates …__retry, passes → Flakes: 1, BUILD SUCCESS
Same IT, bootstrap reverted to teardown-on-session-close Retry gets Connection refused → BUILD FAILURE, so the harness change is required
Flaky annotation step on a failsafe XML with one flaky, one failed-twice and one passing test Annotates only the flaky test ✅; Slack text rendered with jq ✅
Download retry loop, simulated under bash with a stub mvn that models Maven's not-found cache (attempt 1 writes *.lastUpdated; a later attempt that finds one fails without touching the network) Before this change: 3 attempts, all fail, only the last attempt logged. After: markers cleared, attempt 2 downloads and passes, both attempts in maven.log ✅ · download error then compile error → no second retry ✅ · attempt over 10 min with no report (hang) → not retried ✅. (The first version of this test used a stub that cached nothing, which is why it missed the not-found case.)
Details

Why the retry needs the harness change. Surefire 3.1.2's JUnitPlatformProvider.invokeAllTests runs all tests, calls closeLauncher() (which closes the LauncherSession), then opens a new session and re-executes only the failed test IDs. TestSuiteBootstrap.launcherSessionClosed() called cleanup(), which stops the app and every container. On the second session STARTED is already true, so it skipped boot and the retried test ran against nothing. The bootstrap now registers cleanup as a shutdown hook once boot succeeds, and no longer overrides launcherSessionClosed. forkedProcessExitTimeoutInSeconds is 180 so the fork can finish that teardown before Surefire halts it. External mode (JPW_MODE=external) is unchanged: it never boots, so it registers no hook.

Download retry condition. Retry only if exit != 0, there are no failsafe-reports/TEST-*.xml files, and the maven log matches Could not (resolve dependencies|transfer artifact|find artifact). This retries the whole mvn verify rather than a separate pre-fetch step, because some of the artifacts that failed (e.g. junit-platform-launcher) are resolved by Surefire at test start-up, which a pre-fetch wouldn't cover.

Ejections this would have prevented (all after #35469):

The flaky tests are being fixed separately.

… download

Merge-queue IT lanes now re-run a failed test method once after the lane
finishes (failsafe rerunFailingTestsCount=1, CI only; default stays 0). A
test that passes on the re-run is reported as a flake and no longer ejects
the entry; one that fails twice still blocks.

Failsafe closes the JUnit launcher session between the two passes, and
TestSuiteBootstrap tore the whole stack down on session close, so the
re-run pass hit a dead server. Teardown now runs in a JVM shutdown hook.

A lane also re-invokes mvn verify (up to 3 attempts, 30s apart) when it
failed on a dependency download before writing any test report.

ADR:2026-10-15-merge-queue-its-retry-a-failed-test-once
@github-actions

Copy link
Copy Markdown
Contributor

❌ PR checklist incomplete

This PR cannot be merged until the following are addressed on its linked issue:

  • No GitHub issue is linked. Link an issue in the Development section of the PR (or add Fixes #12345 to the description). For a same-org cross-repo issue, add Fixes open-metadata/<repo>#123 to the description.

The fields live on the linked issue in the Shipping project (open the issue → right sidebar → Projects). After you set them, re-run this check (or push a commit) — issue/project changes do not re-trigger it automatically.

Maintainers can bypass this check by adding the skip-pr-checks label.

@github-actions github-actions Bot added backend safe to test Add this label to run secure Github workflows on PRs labels Oct 15, 2026
Each IT lane annotates the tests failsafe re-ran and passed. After a green
merge-queue run, the workflow's final job collects those annotations and
posts one message to #ci-cleanup naming the tests, the PRs in the group and
the run, the same way Playwright reports its merge-queue flakes. Failed runs
post nothing; their failures already reach the dequeue report.
@sonika-shah sonika-shah changed the title ci(java): retry a failed merge-queue IT once, and a failed dependency download ci(java): retry a failed merge-queue IT once, report it to #ci-cleanup, retry dependency downloads Oct 15, 2026
…s failed attempt

TestNamespace.prefix is deterministic per JVM, class and method, and the
failsafe re-run happens in the same JVM, so it reused the failed attempt's
names. Entities that attempt created without tracking them as roots were
still there, and the re-run failed with 409 "Entity already exists" instead
of passing. TestNamespaceExtension now suffixes the method id with __retry
the second time it sees a test's unique id; first attempts are unchanged.
The previous commit only renamed method-level namespaces. 94 call sites build
a TestNamespace by hand (class-level @BeforeAll fixtures, shared statics), and
JUnit re-runs the failed test's @BeforeAll in the re-run pass, so those still
reused the failed attempt's names and hit 409. The bootstrap now marks the
second launcher session in the JVM as the re-run pass, and TestNamespace.prefix
changes for every namespace from then on. Replaces the extension-level
__retry suffix.
…splits its report

Re-running a parallel JUnit lane, failsafe can write a retried test's pass
into another class's report file while the first attempt's <failure> stays
in the test's own (seen locally on 2 of 10 retried tests). Maven's verdict
is right, but classify_test_outcome.py counted the stale <failure>, so the
lane job would still fail. It now treats a test that passed in any report as
passed, and the flaky annotation reports a failure-plus-pass as a flake.
…download retry

When Central answers "not found", Maven writes a *.lastUpdated marker and
won't ask again until the update interval passes, so attempts 2 and 3 failed
instantly with the same error (the #35042 ejection's log shows Maven honouring
those markers). Delete them before each retry.

Also: retry only an attempt that failed within 10 minutes (a long attempt
with no report was a hang, and must stay inside the job timeout), judge each
attempt by its own output, and append every attempt to maven.log instead of
overwriting the first failure.
…ive a retry

The parallel lane shares one server, database and search index, and the
merge queue now re-runs a failed test once. Write down the three rules the
recent flakes broke, in the IT README (for people) and in the Java rules
file (for coding agents): assert only on your own data; write only to your
own data or run alone with the GLOBAL resource lock / global-state lane; and
build every name with TestNamespace and keep no cross-method state, so the
retry can re-run the test.
@gitar-bot

gitar-bot Bot commented Oct 15, 2026

Copy link
Copy Markdown
Code Review ✅ Approved

🟡 Medium risk · The merge-queue retry changes could let intermittent integration-test failures pass, allowing a real regression to merge despite flake reporting.

Adds a CI safety net for merge-queue IT lanes: failed tests get one re-run against the same server and flakes are reported to #ci-cleanup, while dependency-download failures get up to three retries after clearing Maven's cached not-found markers. No issues found.

Verification

Rules No rules evaluated

Functional validation Not enabled

Set up

Auto-approval Not enabled

Set up

For more verification details, comment gitar display:verbose.

Options

Display: compact → Showing compact verification results.

Comment with these commands to change the behavior for this request:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Powered by Gitar — free for open source

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend safe to test Add this label to run secure Github workflows on PRs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant