fix(ethexe/malachite-core): wait for WAL advisory lock release in MalachiteService::shutdown#5546
Conversation
…achiteService::shutdown Upstream Malachite owns the WAL file from a detached std::thread (arc-malachitebft-engine::wal::thread). The WAL actor's post_stop only sends a Shutdown message on a channel — the writer thread releases its advisory_lock when it later drops the log. The actor's JoinHandle exposed via EngineHandle is therefore not a sufficient barrier: the writer thread can still be alive (and the flock on consensus.wal still held) after MalachiteService::shutdown().await returns. The next MalachiteService::new on the same base dir then panics with 'Failed to acquire exclusive advisory lock'. Make shutdown deterministic for restart by probing the advisory lock on consensus.wal after draining engine + app handles, polling every 25 ms with a generous 10 s ceiling. The poll is a no-op when the WAL file never existed. Adds shutdown_releases_wal_advisory_lock — a regression test that restarts a 3-validator cohort 50 times and probes the WAL flock from a fresh fd the instant each shutdown().await returns; without the fix this fails in the first cycle. Closes #5545. Originally caught flaking on master at https://github.com/gear-tech/gear/actions/runs/26678194627/job/78634373130.
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request addresses a race condition in the Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces the advisory-lock dependency to manage the WAL advisory lock release during service shutdown in ethexe-malachite-core. It adds a helper function wait_wal_lock_released to poll and wait for the WAL file lock to be released, ensuring that subsequent service starts on the same base directory do not fail due to lock contention. A regression test is also added to verify this behavior. The reviewer suggests improving wait_wal_lock_released by opening the WAL file with read-only permissions to avoid sharing violations and handling file open errors more robustly by only returning early on NotFound errors.
Closes #5545.
Summary
`MalachiteService::shutdown().await` is the documented restart barrier — by the time it returns, every file lock the service held must be released so the same base dir can be re-opened. For RocksDB that's true (`Store` lives inside the app task and is dropped when the task is awaited). For Malachite's WAL it is not true today.
Upstream Malachite owns the WAL file from a detached `std::thread` (see `arc-malachitebft-engine::wal::thread`). The WAL actor's `post_stop` only sends a `Shutdown` message on a channel; the writer thread releases its `advisory_lock` only when it later drops the log. The actor's `JoinHandle` resolves as soon as `post_stop` returns, so the writer thread can still be alive — and `flock(LOCK_EX)` on `consensus.wal` still held — when `MalachiteService::shutdown().await` returns. The next `MalachiteService::new` on the same base dir then panics with `Failed to acquire exclusive advisory lock`.
Fix
After awaiting engine + app handles in `shutdown`, probe the WAL advisory lock from a fresh fd. Poll every 25 ms up to a 10 s ceiling. The poll is a no-op when the WAL never existed; on the healthy path the lock is free within milliseconds, so this adds no measurable shutdown latency.
Test
`shutdown_releases_wal_advisory_lock` (new): restarts a 3-validator cohort 50 times and `try_lock(LOCK_EX)` on each `consensus.wal` from a fresh fd the instant `shutdown().await` returns. Without the fix this fails in the first cycle locally with `AlreadyLocked` (~120 ms). With the fix all 50 cycles pass in <5 s.
Diagnosis trail
Test plan