Skip to content

chore(amber): bound region termination to a short backoff - #7088

Merged
aglinxinyuan merged 6 commits into
apache:mainfrom
aglinxinyuan:fix/region-termination-retry
Jul 30, 2026
Merged

chore(amber): bound region termination to a short backoff#7088
aglinxinyuan merged 6 commits into
apache:mainfrom
aglinxinyuan:fix/region-termination-retry

Conversation

@aglinxinyuan

@aglinxinyuan aglinxinyuan commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

What changes were proposed in this PR?

Root cause. RegionExecutionManager retried region termination (EndWorker, then gracefulStop) up to 150 times at a flat 200 ms delay, so a region whose workers do not terminate held teardown for ~30 s before the failure surfaced. Killing a worker is deterministic -- EndWorker plus gracefulStop either succeed, or something upstream is broken in a way retrying cannot fix (#6891, #6918). The long poll bought nothing except a quiet ~30 s stall that masked the real bug.

Before:  EndWorker fails -> 149 retries x 200 ms -> ~30 s of silence -> loud failure
After:   EndWorker fails -> 3 retries (200/400/800 ms) -> ~1.4 s -> loud failure
Before After
Attempts 150 4 (1 + 3 retries)
Delay between attempts flat 200 ms 200 -> 400 -> 800 ms
Worst-case wait per region ~30 s ~1.4 s
Give-up behavior IllegalStateException naming stuck workers unchanged

The retry itself lives in a util, not in the manager (per review): Utils.retry keeps its
attempts + baseBackoffTimeInMS-doubling contract but now schedules its waits on a Timer
instead of calling Thread.sleep, so it can be used from an actor or coordinator thread -- a
blocking sleep there stalls every other message queued on that thread, which is why this path could
not reuse the old version. An onRetry hook keeps caller context in the log (the manager still
names the region and the attempt); callers that do not care can omit it.

Utils.retry(attempts, baseBackoffTimeInMS, timer, onRetry) { operationReturningAFuture }

The previous blocking retry had no production callers -- only its own tests -- so it was replaced
rather than duplicated. A blocking front door can return as a thin sibling if a caller needs one.

RegionExecutionManager is now just a configuration of that util (4 attempts from a 200 ms base)
plus its own give-up message. Two more copies of this loop live elsewhere --
LakeFSStorageClient.retryWithBackoff and FileService.awaitDependency -- but they sit in
common/workflow-core and file-service, neither of which can see amber's Utils (and util-core
is declared only in amber/build.sbt). Folding those in means hosting the util lower in the module
graph, tracked in #7095 rather than smuggled into this fix.

Log line, before and after:

- Failed to terminate region 1 on attempt 1 of 150. Retrying in 200 ms.
+ Failed to terminate region 1 on attempt 1 of 4. Retrying in 200 ms.
+ Failed to terminate region 1 on attempt 2 of 4. Retrying in 400 ms.
+ Failed to terminate region 1 on attempt 3 of 4. Retrying in 800 ms.
+ Region 1 could not be terminated after 4 attempts; giving up. Workers still not terminated: ...

This also shrinks -- but does not close -- the retry-on-JavaTimer-thread race window in #6923, since there are far fewer hops onto that thread.

Any related issues, documentation, discussions?

Closes #7087

Related: #7056 (the 150-attempt line appears in those hang logs), #6891, #6918, #6923. The bounded-retry loop this PR re-tunes was introduced in #5737.

How was this PR tested?

UtilsSpec covers Utils.retry directly: first-attempt success waits not at all; the backoff
doubles (200 -> 400 -> 800); waiting stops as soon as an attempt succeeds; onRetry sees the
1-based failed-attempt number and the upcoming backoff; a synchronous throw from the by-name body
is retried like a failed Future rather than escaping; a fatal error is not retried in either
shape (it escapes on attempt 1, spending no backoff); and attempts <= 1 makes exactly one attempt
with no wait. The three cases that covered the removed blocking variant are gone with it.

RegionExecutionManagerSpec keeps its termination-lifecycle cases and pins what the defaults mean
end to end: the manager asks for 200 -> 400 -> 800 ms, makes 4 EndWorker attempts, and a region
that terminates on attempt 2 spends only the first wait. Budget boundaries are still pinned
(success on the last permitted attempt, give-up at 1 attempt / 2 attempts with every stuck worker
named).

The backoff assertions are exact rather than timing-tolerant: RecordingInlineTimer records each
requested delay and runs the task inline, used inside Time.withCurrentTimeFrozen so
when - Time.now is precisely what Future.sleep asked for. The tests therefore assert the real
schedule without waiting 1.4 s or flaking on slow CI. That needed a killRetryTimer seam on the
constructor (default new JavaTimer(true), as before).

The two directly affected specs, with formatting and scalafix in the same invocation:

sbt "WorkflowExecutionService/scalafmtAll" "WorkflowExecutionService/testOnly org.apache.texera.amber.engine.common.UtilsSpec org.apache.texera.amber.engine.architecture.scheduling.RegionExecutionManagerSpec" "WorkflowExecutionService/scalafixAll --check"
[info] Tests: succeeded 27, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.

Whole scheduling package plus UtilsSpec:

sbt "WorkflowExecutionService/testOnly org.apache.texera.amber.engine.architecture.scheduling.* org.apache.texera.amber.engine.common.UtilsSpec"
[info] Suites: completed 13, aborted 1
[info] Tests: succeeded 145, failed 0, canceled 0, ignored 0, pending 0

(DefaultCostEstimatorSpec aborts on this machine during suite construction, before any of its
tests run, while building a CSV scan descriptor. Pre-existing and unrelated: with this PR's changes
reverted it aborts identically under the same command, 0 tests run.)

Was this PR authored or co-authored using generative AI tooling?

Generated-by: Claude Code (Opus 5)

Region termination retried EndWorker + gracefulStop up to 150 times at a
flat 200ms, holding a region's teardown for ~30s before a failed kill
surfaced. Killing a worker is deterministic, so retries only need to ride
out a transient failure: one attempt plus three backed-off retries at
200/400/800 ms (~1.4s).

The backoff schedule is now the retry budget (one retry per delay), so the
attempt count and the delays cannot drift apart. Give-up behavior is
unchanged: it still fails loudly, naming every worker that never
terminated.

Closes apache#7087
@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Automated Reviewer Suggestions

Based on the git blame history of the changed files, we recommend the following reviewers:

  • Contributors with relevant context: @Yicong-Huang, @carloea2
    You can notify them by mentioning @Yicong-Huang, @carloea2 in a comment.

@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

⚠️ Benchmark changes need a look

🟢 0 better · 🔴 10 worse · ⚪ 5 noise (<±5%) · 0 without baseline

Compared against main f28d8ec benchmarked on this same runner, so the delta is largely free of cross-runner hardware noise. The "7d avg" column still reflects the gh-pages dashboard. Treat <±5% as noise unless repeated.

Dashboard · Run

config throughput MB/s latency max Δ latest / 7d
🔴 bs=10 sw=10 sl=64 373 0.228 25,758/40,261/40,261 us 🔴 +20.1% / 🔴 +155.2%
🔴 bs=100 sw=10 sl=64 735 0.449 130,082/214,383/214,383 us 🔴 +46.3% / 🔴 +99.7%
bs=1000 sw=10 sl=64 921 0.562 1,089,133/1,125,310/1,125,310 us ⚪ within ±5% / 🔴 +11.1%
Baseline details

Latest main f28d8ec from same runner

config metric PR latest main 7d avg Δ latest Δ 7d
bs=10 sw=10 sl=64 throughput 373 tuples/sec 413 tuples/sec 786.12 tuples/sec -9.7% -52.6%
bs=10 sw=10 sl=64 MB/s 0.228 MB/s 0.252 MB/s 0.48 MB/s -9.5% -52.5%
bs=10 sw=10 sl=64 p50 25,758 us 23,651 us 12,305 us +8.9% +109.3%
bs=10 sw=10 sl=64 p95 40,261 us 33,536 us 15,774 us +20.1% +155.2%
bs=10 sw=10 sl=64 p99 40,261 us 33,536 us 18,978 us +20.1% +112.1%
bs=100 sw=10 sl=64 throughput 735 tuples/sec 815 tuples/sec 999.71 tuples/sec -9.8% -26.5%
bs=100 sw=10 sl=64 MB/s 0.449 MB/s 0.497 MB/s 0.61 MB/s -9.7% -26.4%
bs=100 sw=10 sl=64 p50 130,082 us 122,252 us 100,616 us +6.4% +29.3%
bs=100 sw=10 sl=64 p95 214,383 us 146,554 us 107,356 us +46.3% +99.7%
bs=100 sw=10 sl=64 p99 214,383 us 146,554 us 113,255 us +46.3% +89.3%
bs=1000 sw=10 sl=64 throughput 921 tuples/sec 915 tuples/sec 1,031 tuples/sec +0.7% -10.7%
bs=1000 sw=10 sl=64 MB/s 0.562 MB/s 0.559 MB/s 0.63 MB/s +0.5% -10.7%
bs=1000 sw=10 sl=64 p50 1,089,133 us 1,089,277 us 980,328 us -0.0% +11.1%
bs=1000 sw=10 sl=64 p95 1,125,310 us 1,151,335 us 1,027,528 us -2.3% +9.5%
bs=1000 sw=10 sl=64 p99 1,125,310 us 1,151,335 us 1,054,298 us -2.3% +6.7%
Raw CSV
config_idx,batch_size,schema_width,string_len,num_batches,total_ms,total_tuples,total_bytes,tuples_per_sec,mb_per_sec,lat_p50_us,lat_p95_us,lat_p99_us
0,10,10,64,20,535.93,200,128000,373,0.228,25758.29,40260.57,40260.57
1,100,10,64,20,2721.08,2000,1280000,735,0.449,130082.34,214383.23,214383.23
2,1000,10,64,20,21714.44,20000,12800000,921,0.562,1089133.07,1125310.07,1125310.07

@aglinxinyuan aglinxinyuan changed the title fix(amber): bound region termination to a short backoff chore(amber): bound region termination to a short backoff Jul 30, 2026
@aglinxinyuan aglinxinyuan removed the fix label Jul 30, 2026
@codecov-commenter

codecov-commenter commented Jul 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.17949% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.38%. Comparing base (f28d8ec) to head (2f7b1a5).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...chitecture/scheduling/RegionExecutionManager.scala 87.50% 1 Missing and 2 partials ⚠️
.../org/apache/texera/amber/engine/common/Utils.scala 86.66% 0 Missing and 2 partials ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #7088      +/-   ##
============================================
- Coverage     79.38%   79.38%   -0.01%     
+ Complexity     3799     3795       -4     
============================================
  Files          1159     1159              
  Lines         46150    46153       +3     
  Branches       5129     5128       -1     
============================================
+ Hits          36638    36640       +2     
- Misses         7890     7892       +2     
+ Partials       1622     1621       -1     
Flag Coverage Δ *Carryforward flag
access-control-service 70.00% <ø> (ø) Carriedforward from f28d8ec
agent-service 77.42% <ø> (ø) Carriedforward from f28d8ec
amber 72.58% <87.17%> (-0.01%) ⬇️
computing-unit-managing-service 20.49% <ø> (ø) Carriedforward from f28d8ec
config-service 66.66% <ø> (ø) Carriedforward from f28d8ec
file-service 67.21% <ø> (ø) Carriedforward from f28d8ec
frontend 83.05% <ø> (ø) Carriedforward from f28d8ec
notebook-migration-service 78.94% <ø> (ø) Carriedforward from f28d8ec
pyamber 97.36% <ø> (ø) Carriedforward from f28d8ec
workflow-compiling-service 26.31% <ø> (ø) Carriedforward from f28d8ec

*This pull request uses carry forward flags. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Region termination carried its own backoff loop. `Utils.retry` already
covers blocking work, but its `Thread.sleep` would stall the coordinator
thread, so the async path could not reuse it.

Add `Utils.retryAsync`: the same attempts-and-doubling-backoff contract for
`Future`-returning logic, waiting on a `Timer` instead of blocking a thread,
with an `onRetry` hook so callers keep their own log context. Region
termination now configures it (4 attempts from a 200ms base) instead of
hand-rolling the loop, leaving one retry util for new code to reuse.

Addresses review feedback on apache#7088.
@github-actions github-actions Bot added the fix label Jul 30, 2026
The blocking `Utils.retry` had no production callers -- only its own tests --
so keeping it beside the new async variant would have shipped two
near-identical retries. Replace it: `Utils.retry` now takes the same
`attempts` + `baseBackoffTimeInMS`-doubling knobs but schedules its waits on
a `Timer`, so it can be used from an actor or coordinator thread where
`Thread.sleep` would stall unrelated work.

Region termination is its first real caller. A blocking front door can come
back as a thin sibling if a caller needs one.
@aglinxinyuan

Copy link
Copy Markdown
Contributor Author

Good call -- done, and Utils.retry is that util now.

  • Same knobs as before (attempts + baseBackoffTimeInMS, doubling), but the waits are scheduled on a Timer instead of Thread.sleep. The blocking version could not serve this path: sleeping on the coordinator thread stalls every other message queued on it.
  • An onRetry hook keeps caller context in the log, so the line you quoted still reads Failed to terminate region 1 on attempt 2 of 4. Retrying in 400 ms. -- callers that do not care can omit it.
  • Region termination is now just a configuration of it: 4 attempts from a 200 ms base.
Utils.retry(attempts, baseBackoffTimeInMS, timer, onRetry) { operationReturningAFuture }

One thing to flag, since you asked to keep retry in #7028: the blocking version had no production callers -- only its own tests -- so instead of shipping two near-identical retries I replaced it with this one rather than adding a second. If you want a blocking front door back for the test-side case you mentioned (withRetry re-sending EndWorker), say so and I will add it as a thin sibling.

On "lots of places have this logic": the other two are LakeFSStorageClient.retryWithBackoff and FileService.awaitDependency, both the same loop. They live in common/workflow-core and file-service, neither of which can see amber's Utils (and util-core is declared only in amber/build.sbt), so folding them in means hosting the util lower in the module graph. Filed as #7095 so this PR stays a fix -- happy to take it next if you agree with the shape there.

@apache apache deleted a comment from github-actions Bot Jul 30, 2026

@Yicong-Huang Yicong-Huang left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 0 must-fix · 2 advisory · 0 polish — clean, well-scoped refactor with exact, non-flaky tests. Two judgment-call notes for you.

Correctness (1)

  • Utils.scala:90 — Scaladoc "Fatal errors are not retried" vs. a rescue that matches any Throwable (advisory, see inline)

Conventions (1)

  • Title type chore understates a runtime failure-path change that closes a Performance issue (#7087) — consider perf(amber)/fix(amber). Note: backport-auto-label.yml labels only fix(...) PRs for backport, so chore silently excludes this from any release branch. Your call on type and backport intent. (advisory)
Verification trace

Treated the retry rewrite as an equivalence claim. First attempt: Utils.retry evaluates terminateWorkers synchronously via Future(fn).flatten on the calling thread — same as the old direct .rescue call, only enqueues RPC futures, no new blocking. Retries run on the Timer thread via Future.sleep(...)(timer).flatMap(...) — the same thread the old code used, so the #6923 window shrinks but is not newly opened. Budget: attempts 1–3 retry, attempt 4 propagates; waits 200/400/800; onRetry fires 3× and never after the last attempt. Give-up equivalence: old message used the reached attempt, new uses maxTerminationAttempts; since retry always exhausts to attempts, reached == budget at give-up, and <= 1 ? "1 attempt" also covers attempts=0. The teardown sequence in terminateWorkers is byte-unchanged, so the teardown-order invariant holds. Only production caller of Utils.retry is this manager, so the signature break leaves no stale caller.

Comment thread amber/src/main/scala/org/apache/texera/amber/engine/common/Utils.scala Outdated

@Yicong-Huang Yicong-Huang left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving — clean, well-scoped refactor; the two earlier notes are non-blocking judgment calls. Nice test rig (exact, non-flaky backoff assertions via the inline timer).

The rescue guard keyed only on the attempt count, so a fatal error handed
back as a failed `Future` was retried -- contradicting the scaladoc, and
inconsistent with the synchronous path, where `Future(fn)`'s `Try` already
filters fatals out. Guard with `NonFatal` so both shapes propagate on the
first attempt, matching how the blocking backoff loops elsewhere in the repo
catch `Exception`.

Addresses review feedback on apache#7088.
@aglinxinyuan

Copy link
Copy Markdown
Contributor Author

On the title: chore is deliberate, and no release backport intended for this one -- it re-tunes a budget rather than fixing a broken behavior, so main-only is where I'd like it to sit. Thanks for spelling out the backport-auto-label.yml consequence though; that gate is easy to trip over silently, and it's the reason the type matters more than it looks.

Both notes are addressed now (NonFatal in 32228ff, details in the inline thread), and the branch has main merged in with everything re-run on the merged base.

@aglinxinyuan
aglinxinyuan added this pull request to the merge queue Jul 30, 2026
Merged via the queue into apache:main with commit 37ea97d Jul 30, 2026
21 checks passed
@aglinxinyuan
aglinxinyuan deleted the fix/region-termination-retry branch July 30, 2026 03:55
renovate-bot pushed a commit to renovate-bot/apache-_-texera that referenced this pull request Jul 30, 2026
### What changes were proposed in this PR?

The same doubling-backoff loop was written three times, and no module
could share the others' copy: amber's `Utils` sits above
`common/workflow-core` and `file-service` in the module graph.

New dependency-free **`common/util`** module with
`RetryUtil.withBackoff`, and both blocking copies now call it:

| Site | Before | After |
| --- | --- | --- |
| `LakeFSStorageClient.healthCheck` | private 27-line `retryWithBackoff`
| `RetryUtil.withBackoff("connect to lake fs server", 5, 200, ...)` |
| `FileService.awaitDependency` | private 36-line loop | 7-line
delegation keeping its 6/200 defaults + logger |
| `Utils.retry` (amber, non-blocking) | unchanged | cross-references the
blocking sibling |

```scala
RetryUtil.withBackoff(description, maxAttempts, initialDelayMillis, onRetry, sleep)(operation)
```

The util owns everything the copies duplicated: attempt counting, the
doubling, what counts as transient (`NonFatal`), interrupt fail-fast
with the interrupt status restored, and the message wording.
`description` is a verb phrase ("connect to lake fs server"),
interpolated into all three messages, so LakeFS's give-up text is
byte-identical to before. Retries are reported through an `onRetry` hook
carrying a `RetryAttempt` (attempt, budget, delay, cause, and the
standard `message`), which callers log with **their own** logger — so a
LakeFS retry still logs under LakeFS, not under the util.

**Bug fixed on the way in.** LakeFS's `sleep` call sat inside the
`catch` block, so an interrupt raised *while waiting* escaped raw with
the interrupt flag left cleared — a `catch` cannot catch what its own
body throws:

```
Before (LakeFS):  op fails -> catch -> sleep interrupted -> InterruptedException escapes, flag cleared
After  (shared):  op fails -> sleep interrupted -> interrupt restored + wrapped, fails fast
```

`FileService`'s copy already plugged that hole with an inner `try`; the
shared util keeps the better behavior for both.

**Why a new module rather than `common/workflow-core`**, which was
proposal 1 in apache#7095: `workflow-core` is not reachable from `Auth`,
`ConfigService`, or `AccessControlService`, so hosting it there would
have left future callers in those modules stuck writing their own loop —
the exact thing this PR is meant to stop. `common/config` is reachable
from everything and already carries
`org.apache.texera.amber.util.ConfigParserUtil`, but a retry helper
isn't configuration. A dependency-free module any other module may
depend on avoids both problems, at the cost of one `build.sbt` entry.

**Why the non-blocking variant stays in amber.** `Utils.retry` needs
`com.twitter:util-core`, declared only in `amber/build.sbt`. Moving it
down would put util-core on the classpath of every service that depends
on the new module and force LICENSE-binary resyncs, so the pair is:
blocking in `common/util`, non-blocking in `amber`, each documented in
terms of the other. The new module is dependency-free for the same
reason.

**One behavior change, deliberate.** Both replaced loops caught `case e:
Exception`; the util uses `NonFatal`, for parity with `Utils.retry`.
`NonFatal` is wider: non-fatal `Error`s — `AssertionError`,
`java.io.IOError`, `ServiceConfigurationError` — are now retried and
wrapped rather than propagating immediately. On the S3/LakeFS startup
paths that means such a failure costs the full backoff before surfacing.
`RetryUtilSpec` pins it in both directions (a non-fatal `Error` is
retried; a fatal throwable is not).

**Two retry sites deliberately left alone**, because converting them
changes behavior rather than just structure — tracked with the full
reasoning in apache#7124:

| Site | Why not now |
| --- | --- |
| `URLFetchUtil.getInputStreamFromURL` | Retries 5x with **no** delay
and returns `Option`. Adopting backoff adds up to ~3 s before a dead URL
gives up — arguably a fix (it is an unthrottled retry storm today), but
a behavior change in an operator path. |
| `PythonProxyClient` Flight connect loop | **Constant** delay, closes
the Flight client between attempts, and throws
`WorkflowRuntimeException` on give-up. Needs a delay-multiplier knob
plus a give-up type decision. |

### Any related issues, documentation, discussions?

Closes apache#7095

Follows apache#7088, which introduced the non-blocking variant; the
consolidation was split out of it to keep that fix narrow. Context: the
review discussion on apache#7103 (the LakeFS health-check backport) asked for
one util rather than a fourth copy.

### How was this PR tested?

`RetryUtilSpec` (new, in `common/util`, registered in `build.yml`'s
hand-maintained `jacoco` module list so it actually runs) is the union
of what the two hand-rolled loops were tested for, plus what neither
covered:

- returns the operation's value on first success without sleeping;
retries until success with a doubling progression; honors a custom base
delay; succeeds on the final permitted attempt (the `attempt >=
maxAttempts` boundary); gives up naming the description and preserving
the cause; `maxAttempts = 1` means no retry and no sleep;
- the `onRetry` hook fires once per wait with 1-based attempt numbers
and never after the last attempt, and its `message` wording is pinned;
- interrupt during the operation **and** interrupt during a backoff
sleep both restore the interrupt status and fail fast (the second is the
LakeFS hole above);
- a fatal throwable is not retried and passes through unwrapped, while a
non-fatal `Error` is retried (the `NonFatal`-vs-`Exception` widening
above).

At the call sites: `FileServiceSpec` keeps 8 tests that exercise the
delegation, its own 6/200 defaults, and the description in its messages
(the 4 that only re-tested util mechanics moved into `RetryUtilSpec`).
`LakeFSStorageClientSpec`'s 4 retry tests moved out with the loop; its
remaining 5 pass unchanged. amber's `UtilsSpec` and
`RegionExecutionManagerSpec` are untouched and still green.

```bash
sbt "Util/jacoco" "FileService/testOnly org.apache.texera.service.FileServiceSpec" "WorkflowCore/testOnly org.apache.texera.amber.core.storage.util.LakeFSStorageClientSpec"
```

```
[info] Tests: succeeded 11, failed 0, canceled 0, ignored 0, pending 0   (Util)
[info] Tests: succeeded 12, failed 0, canceled 0, ignored 0, pending 0   (FileService)
[info] Tests: succeeded 5,  failed 0, canceled 0, ignored 0, pending 0   (WorkflowCore / LakeFS)
```

Lint plus test-source compiles for every module the new dependency edge
touches, and amber's two retry specs:

```bash
sbt "Util/scalafixAll --check" "Util/scalafmtCheckAll" "WorkflowCore/Test/compile" "WorkflowCore/scalafixAll --check" "FileService/Test/compile" "FileService/scalafixAll --check" "WorkflowExecutionService/Test/compile" "WorkflowExecutionService/scalafixAll --check" "WorkflowExecutionService/testOnly org.apache.texera.amber.engine.common.UtilsSpec org.apache.texera.amber.engine.architecture.scheduling.RegionExecutionManagerSpec"
```

```
[success] all scalafix / scalafmt checks and Test/compile tasks
[info] Tests: succeeded 27, failed 0, canceled 0, ignored 0, pending 0
```

### Was this PR authored or co-authored using generative AI tooling?

Generated-by: Claude Code (Opus 5)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Region termination retries 150 times, stalling teardown for ~30 s when a worker will not die

3 participants