Skip to content

Release v0.48.2 - #3302

Merged
xgreenx merged 6 commits into
masterfrom
release/v0.48.2
May 17, 2026
Merged

xgreenx merged 6 commits into
masterfrom
release/v0.48.2

Conversation

@xgreenx

@xgreenx xgreenx commented May 14, 2026 •

Copy link
Copy Markdown
Collaborator

Version 0.48.2

Fixed

  • 3289: shutdown services in parallel to stop poa promptly
  • 3290: Use semantic JSON comparison in QuorumTransport
  • 3301: disable coredumps in the fuel-core binary so the rocksdb atexit SIGABRT does not block pod restart for tens of seconds; set FUEL_CORE_ENABLE_COREDUMP=1 to opt back in.

@xgreenx xgreenx self-assigned this May 14, 2026
@xgreenx
xgreenx requested review from a team and Dentosal as code owners May 14, 2026 21:11
@cursor

cursor Bot commented May 14, 2026 •

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches GraphQL request execution and AsyncProcessor task lifecycle tracking to ensure in-flight tasks are drained on shutdown; mistakes here could cause hangs or premature cancellation during service teardown.

Overview
Releases v0.48.2 by moving the .changes entries into CHANGELOG.md and bumping workspace crate versions from 0.48.1 to 0.48.2 (including Cargo.lock).

Hardens GraphQL shutdown behavior by tracking and draining all in-flight request tasks: AsyncProcessor now counts active tasks and exposes drain(), and GraphqlService wraps spawned hyper futures to stop on service shutdown and awaits processor.drain() during Task::shutdown to avoid detached tasks racing RocksDB destructors.

Updates the upgradable executor version map to include 0-48-2 with no state transition change, and adjusts a PoA integration test to anchor its sync timer earlier to reduce CI flakiness.

Reviewed by Cursor Bugbot for commit 197d096. Bugbot is set up for automated code reviews on this repo. Configure here.

@xgreenx xgreenx added pr release Used to trigger the github action to update versions and removed pr release Used to trigger the github action to update versions labels May 14, 2026
@xgreenx xgreenx added the no changelog Skip the CI check of the changelog modification label May 14, 2026
xgreenx and others added 2 commits May 15, 2026 18:09
Coredump analysis of a failing run pinpointed the cause:

  Thread 4 (main):
    #6  rocksdb::PosixEnv::~PosixEnv   <- env_posix.cc:222
    #7  __run_exit_handlers

  Thread 1 (still alive):
    ... AsyncProcessor::drop -> Runtime::shutdown_timeout ...
    ... tokio worker polling hyper::server::new_svc::NewSvcTask
        spawned via fuel_core_services::async_processor::spawn_reserved

  Threads 7..10:
    rocksdb::ThreadPoolImpl::Impl::BGThread parked on the
    Env::Default() condvar that Thread 4 was about to broadcast.

At the moment of the crash main was tearing down rocksdb's global
`Env::Default()` singleton from the libc atexit chain (a C++ static
destructor), while a tokio worker from the GraphQL `AsyncProcessor`'s
inner runtime was still polling a hyper request future. The worker
was no longer joined because `AsyncProcessor::drop` fell back to
`Runtime::shutdown_timeout(5s)`, which detaches any task still
running at its deadline. The detached worker then outlived the
service and raced rocksdb's `~PosixEnv` -> `JoinAllThreads`, producing
SIGABRT (or SIGSEGV) at process exit.

Fix: give `AsyncProcessor` an explicit `drain()` that waits for every
task spawned via `spawn_reserved` to complete (tracked via an
`AtomicUsize` + `Notify`), and call `processor.drain().await` from
`GraphQL Task::shutdown` before the surrounding teardown reaches
`AsyncProcessor::drop`. The runtime then has nothing in flight when
its workers are joined, so no task gets detached and no worker
outlives the service.

The previous SIGABRT/SIGSEGV-handler workaround (commits 2e54820b82
and 2ec05795a2) is no longer needed and has been reverted.

Verified locally:
  - `da_compression__...with_overridden_height` in a tight loop:
    50/50 clean, 0 signal aborts, 0 coredumps.
  - `da_compression::` + `gas_price::` combined batches: 10/10
    iterations clean (~250 test runs total).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`ExecutorWithMetrics::execute` now wraps every spawned request future
in a `tokio::select!` against `state.while_started()`. When the
service leaves the `Started` state, in-flight handlers resolve
immediately and the connection's `Drop` closes the socket cleanly.

This is a second layer of defense on top of `processor.drain()` in
`Task::shutdown`: the drain handles the common case where handlers
finish quickly after axum's graceful shutdown is signaled; the
watcher caps how long drain can possibly take if a handler is stuck
in a long-running await it can't observe on its own (a leaked
acquire, a backpressured queue, a never-completing await). Either
way, no task is detached at `AsyncProcessor::drop` and no tokio
worker outlives the service into the rocksdb static-destructor
phase at process atexit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit a61de80. Configure here.

Comment thread crates/services/src/async_processor.rs Resolved
xgreenx and others added 2 commits May 15, 2026 18:47
The previous decrement was inline at the end of the spawned async
block, after `future.await`. A panic or cancellation in `future`
would skip both the `fetch_sub` and the `notify_waiters`, leaving
`active` permanently above zero — `drain()` would then hang forever
and `Task::shutdown` would block the entire service teardown.

Move the decrement + notify into the `Drop` impl of an `ActiveGuard`
moved into the future. The guard's `Drop` runs whether the future
returns normally, panics, or is dropped before its first poll, so the
counter is always released.

Includes two regression tests:
  - `drain__returns_when_panicking_task_completes`
  - `drain__returns_when_cancelled_before_first_poll`

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The assertion `start_time.elapsed() >= TIME_UNTIL_SYNCED` was racy:
SyncTask transitions to Synced only `time_until_synced` after the last
block received from the network, and every received block restarts the
timer. Capturing start_time after `wait_for_blocks(3)` required at least
one additional block to arrive at second_producer afterward, which is
not guaranteed on slow CI where first_producer may stop before sending
another block over P2P. Anchoring start_time before `wait_for_blocks`
makes `T_last_received > start_time` invariantly true.
@xgreenx
xgreenx merged commit d7b0ac8 into master May 17, 2026
40 of 41 checks passed
@xgreenx
xgreenx deleted the release/v0.48.2 branch May 17, 2026 17:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no changelog Skip the CI check of the changelog modification pr release Used to trigger the github action to update versions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant