Skip to content

fix: use isolated runtimes for critical components - #682

Merged
bmuddha merged 1 commit into
masterfrom
bmuddha/fix/isolated-runtimes
Nov 26, 2025
Merged

bmuddha merged 1 commit into
masterfrom
bmuddha/fix/isolated-runtimes

Conversation

@bmuddha

@bmuddha bmuddha commented Nov 25, 2025 •

Copy link
Copy Markdown
Collaborator

Separate async runtimes with manual thread allocation, which allows to isolate different components of the system in such a way that no blocking operations can interfere with the whole system.

Fixes shutdown sequence of RPC server by avoiding
infinite cancel branch in the shutdown loop.

Summary by CodeRabbit

  • Bug Fixes

    • Improved graceful connection shutdown handling to prevent repeated termination attempts.
    • Enhanced ledger shutdown behavior for better state flushing.
  • Improvements

    • Added enhanced logging capabilities throughout the application.
    • Optimized RPC server execution model.
    • Streamlined ledger truncation operations for better performance.
    • Refined runtime initialization for more efficient multi-threaded execution.

✏️ Tip: You can customize this high-level summary in your review settings.

Separate async runtimes with manual thread allocation,
which allows to isolate different components of the
system in such a way that no blocking operations can
interfere with the whole system.

Fixes shutdown sequence of RPC server by avoiding
infinite cancel branch in the shutdown loop.
@coderabbitai

coderabbitai Bot commented Nov 25, 2025 •

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This pull request converts multiple async methods to synchronous implementations while introducing explicit thread-based runtime management. Key changes include: moving the RPC server from Tokio task spawning to a dedicated std::thread with its own runtime in the MagicValidator; converting asynchronous ledger truncation methods to synchronous equivalents executed on a dedicated worker thread; replacing the Tokio main macro with manual runtime construction in the validator; updating the task scheduler service to take ownership instead of borrowing; and adding connection-level termination guards. The refactoring also adds logging capabilities and increases the EventProcessor instance count from 1 to 2.

Possibly related PRs

Suggested reviewers

  • thlorenz
  • GabrielePicco
  • taco-paco
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch bmuddha/fix/isolated-runtimes

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

Copy link
Copy Markdown
Contributor

Manual Deploy Available

You can trigger a manual deploy of this PR branch to testnet:

Deploy to Testnet 🚀

Alternative: Comment /deploy on this PR to trigger deployment directly.

⚠️ Note: Manual deploy requires authorization. Only authorized users can trigger deployments.

Comment updated automatically when the PR is synchronized.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
magicblock-ledger/src/ledger_truncator.rs (2)

126-135: Potential integer underflow when num_slots_to_truncate is 0.

If excess < slot_size, integer division yields num_slots_to_truncate = 0. Line 129 then computes lowest_slot + 0 - 1, which underflows when lowest_slot is 0, resulting in u64::MAX.

Consider adding an early return when there are no slots to truncate:

         let num_slots_to_truncate = excess / slot_size;
+        if num_slots_to_truncate == 0 {
+            info!("Excess size smaller than single slot, skipping truncation");
+            return Ok(());
+        }

         // Calculating up to which slot we're truncating
         let truncate_to_slot = lowest_slot + num_slots_to_truncate - 1;

235-276: Stale comment and minor documentation issues.

  1. Lines 247-250: The comment mentions offloading to a "separate thread," but compaction now runs inline within compact_slot_range. Since the entire worker already runs on a dedicated thread (spawned at line 322), this comment is misleading.

  2. Line 236: Typo - "inclusing ramge" → "inclusive range".

  3. Lines 269-272: These columns use (None, None) for full-table compaction, which contradicts the docstring claiming slot-range compaction. Consider clarifying that some columns require full compaction.

-    /// Synchronous utility function that triggers and awaits compaction on all the columns
-    /// Compacts [from_slot; to_slot] inclusing ramge
+    /// Synchronous utility function that triggers compaction on all columns.
+    /// Compacts [from_slot; to_slot] inclusive range for slot-keyed columns;
+    /// performs full compaction for transaction-keyed columns.
     pub fn compact_slot_range(
         ledger: &Arc<Ledger>,
         from_slot: u64,
         to_slot: u64,
     ) {
         if to_slot < from_slot {
             warn!("LedgerTruncator: Nani2?");
             return;
         }

-        // Compaction can be run concurrently for different cf
-        // but it utilizes rocksdb threads, in order not to drain
-        // our tokio rt threads, we split offload the effort to a
-        // separate thread
+        // Compaction runs sequentially on this worker's dedicated thread
+        // to avoid blocking the main async runtime.
         let mut measure = Measure::start("Manual compaction");
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7eaaaf3 and 9674e9c.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • magicblock-aperture/src/lib.rs (3 hunks)
  • magicblock-aperture/src/server/http/mod.rs (2 hunks)
  • magicblock-api/src/magic_validator.rs (5 hunks)
  • magicblock-ledger/src/ledger_truncator.rs (9 hunks)
  • magicblock-ledger/tests/test_ledger_truncator.rs (4 hunks)
  • magicblock-task-scheduler/src/service.rs (2 hunks)
  • magicblock-validator/Cargo.toml (1 hunks)
  • magicblock-validator/src/main.rs (2 hunks)
🧰 Additional context used
🧠 Learnings (8)
📓 Common learnings
Learnt from: Dodecahedr0x
Repo: magicblock-labs/magicblock-validator PR: 614
File: magicblock-task-scheduler/src/db.rs:26-0
Timestamp: 2025-11-12T09:46:27.553Z
Learning: In magicblock-task-scheduler, task parameter validation (including ensuring iterations > 0 and enforcing minimum execution intervals) is performed in the Magic program (on-chain) before ScheduleTaskRequest instances reach the scheduler service. The From<&ScheduleTaskRequest> conversion in db.rs does not need additional validation because inputs are already validated at the program level.
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 589
File: magicblock-processor/src/scheduler/mod.rs:217-219
Timestamp: 2025-11-04T10:48:00.070Z
Learning: In magicblock-validator, the codebase uses a pattern where types containing non-Send/non-Sync fields (like Rc<RefCell<...>>) are marked with unsafe impl Send when they are guaranteed to be confined to a single thread through careful API design and thread spawning patterns.
📚 Learning: 2025-11-04T10:48:00.070Z
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 589
File: magicblock-processor/src/scheduler/mod.rs:217-219
Timestamp: 2025-11-04T10:48:00.070Z
Learning: In magicblock-validator, the codebase uses a pattern where types containing non-Send/non-Sync fields (like Rc<RefCell<...>>) are marked with unsafe impl Send when they are guaranteed to be confined to a single thread through careful API design and thread spawning patterns.

Applied to files:

  • magicblock-api/src/magic_validator.rs
📚 Learning: 2025-10-21T14:00:54.642Z
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 578
File: magicblock-aperture/src/requests/websocket/account_subscribe.rs:18-27
Timestamp: 2025-10-21T14:00:54.642Z
Learning: In magicblock-aperture account_subscribe handler (src/requests/websocket/account_subscribe.rs), the RpcAccountInfoConfig fields data_slice, commitment, and min_context_slot are currently ignored—only encoding is applied. This is tracked as technical debt in issue #579: https://github.com/magicblock-labs/magicblock-validator/issues/579

Applied to files:

  • magicblock-api/src/magic_validator.rs
📚 Learning: 2025-11-24T14:21:00.948Z
Learnt from: Dodecahedr0x
Repo: magicblock-labs/magicblock-validator PR: 639
File: Cargo.toml:58-58
Timestamp: 2025-11-24T14:21:00.948Z
Learning: In the magicblock-validator codebase, magicblock-api/Cargo.toml intentionally uses borsh = "1.5.3" (instead of the workspace version 0.10.4) because it needs to deserialize types from the magic-domain-program external dependency, which requires borsh 1.5.x compatibility. This is an intentional exception for interoperability with the magic domain program.

Applied to files:

  • magicblock-api/src/magic_validator.rs
  • magicblock-validator/src/main.rs
  • magicblock-validator/Cargo.toml
📚 Learning: 2025-11-19T12:55:48.931Z
Learnt from: Dodecahedr0x
Repo: magicblock-labs/magicblock-validator PR: 639
File: test-integration/test-tools/src/validator.rs:193-197
Timestamp: 2025-11-19T12:55:48.931Z
Learning: In the magicblock-validator codebase, when constructing arguments for the light CLI test validator in start_light_validator_with_config (test-integration/test-tools/src/validator.rs), the shlex::split approach is necessary because the light CLI does not handle whitespaces properly in validator arguments. The string concatenation + shlex parsing pattern should not be refactored to direct argument construction in this specific case.

Applied to files:

  • magicblock-api/src/magic_validator.rs
📚 Learning: 2025-11-21T10:22:07.520Z
Learnt from: taco-paco
Repo: magicblock-labs/magicblock-validator PR: 661
File: magicblock-committor-service/src/intent_executor/single_stage_executor.rs:20-28
Timestamp: 2025-11-21T10:22:07.520Z
Learning: In magicblock-committor-service's SingleStageExecutor and TwoStageExecutor (single_stage_executor.rs and two_stage_executor.rs), the fields transaction_strategy, junk, and patched_errors are intentionally public because these executors are designed to be used independently outside of the IntentExecutor scope, and callers need access to these execution reports for cleanup and error handling.

Applied to files:

  • magicblock-api/src/magic_validator.rs
📚 Learning: 2025-11-07T13:09:52.253Z
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 589
File: test-kit/src/lib.rs:275-0
Timestamp: 2025-11-07T13:09:52.253Z
Learning: In test-kit, the transaction scheduler in ExecutionTestEnv is not expected to shut down during tests. Therefore, using `.unwrap()` in test helper methods like `schedule_transaction` is acceptable and will not cause issues in the test environment.

Applied to files:

  • magicblock-ledger/tests/test_ledger_truncator.rs
📚 Learning: 2025-11-07T13:20:13.793Z
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 589
File: magicblock-processor/src/scheduler/coordinator.rs:227-238
Timestamp: 2025-11-07T13:20:13.793Z
Learning: In magicblock-processor's ExecutionCoordinator (scheduler/coordinator.rs), the `account_contention` HashMap intentionally does not call `shrink_to_fit()`. Maintaining slack capacity is beneficial for performance by avoiding frequent reallocations during high transaction throughput. As long as empty entries are removed from the map (which `clear_account_contention` does), the capacity overhead is acceptable.

Applied to files:

  • magicblock-ledger/src/ledger_truncator.rs
🧬 Code graph analysis (3)
magicblock-api/src/magic_validator.rs (1)
magicblock-processor/src/executor/mod.rs (1)
  • spawn (140-151)
magicblock-ledger/tests/test_ledger_truncator.rs (1)
magicblock-ledger/src/ledger_truncator.rs (1)
  • join (350-361)
magicblock-task-scheduler/src/service.rs (3)
magicblock-processor/src/executor/mod.rs (1)
  • run (165-204)
magicblock-aperture/src/lib.rs (1)
  • run (50-57)
magicblock-ledger/src/ledger_truncator.rs (1)
  • run (49-83)
🔇 Additional comments (16)
magicblock-aperture/src/server/http/mod.rs (1)

107-122: LGTM! Correctly prevents repeated graceful_shutdown calls.

The termination guard elegantly solves the infinite cancel branch issue mentioned in the PR objectives. Once cancel.cancelled() completes, it would remain ready in every subsequent iteration, potentially calling graceful_shutdown() multiple times. The terminating flag ensures it's invoked exactly once per connection.

magicblock-validator/Cargo.toml (1)

19-19: LGTM!

The num_cpus dependency is appropriate for determining worker thread counts for the isolated runtimes introduced in this PR.

magicblock-task-scheduler/src/service.rs (3)

60-65: Safety comment is accurate for the ownership-based spawning pattern.

The unsafe impl Send and unsafe impl Sync are justified given that TaskSchedulerService is moved into a single Tokio task in start() and maintains exclusive ownership throughout its lifetime. Based on learnings, this pattern is consistent with the codebase's approach for types confined to a single thread.


102-123: LGTM!

The ownership transfer pattern (mut self instead of &mut self) cleanly moves the service into the spawned Tokio task, eliminating borrowing issues across await points. The simplified tokio::spawn(self.run()) is idiomatic.


251-284: LGTM!

The run(mut self) signature is consistent with the ownership transfer in start(). The select loop correctly handles cancellation, task execution, and scheduled task processing.

magicblock-api/src/magic_validator.rs (2)

136-136: LGTM!

Changing rpc_handle from tokio::task::JoinHandle<()> to thread::JoinHandle<()> correctly reflects the new architecture where the RPC server runs on a dedicated OS thread with its own Tokio runtime, isolating it from the main async runtime.


760-763: No issues found — the shutdown parameter change is correct.

The shutdown(true) call properly invokes the ledger's graceful shutdown method, which flushes pending data and waits for background work cancellation. This is the safer, more correct approach for shutdown sequences where data integrity matters. The parameter aligns with the surrounding flush operations and the comment indicating data should be written to disk before exit.

magicblock-ledger/tests/test_ledger_truncator.rs (2)

69-69: LGTM! Synchronous join correctly implemented.

The removal of .await correctly reflects the API change to a synchronous join() method. Calling the blocking thread::join() at test cleanup time (after all async work completes) is acceptable in this test context.

Based on learnings, blocking operations in tests are acceptable in this codebase.

Also applies to: 96-96, 156-156


69-69: All join() call sites have been correctly updated to remove .await.

Verification confirms that the LedgerTruncator::join() method is synchronous (returns thread::Result<()> at line 350), and all four test call sites in this file (lines 69, 96, 156, 195) are properly updated without .await.

magicblock-validator/src/main.rs (2)

69-81: LGTM! Runtime construction looks correct.

The manual runtime construction with dedicated worker threads for the async runtime is appropriate for isolating critical components. Using num_cpus::get() / 4 with .max(1) ensures at least one worker thread even on systems with fewer than 4 CPUs.


83-143: LGTM! Clean separation of runtime setup and application logic.

Moving the startup logic to run() keeps the sync main() focused on runtime construction while maintaining the existing async flow.

magicblock-ledger/src/ledger_truncator.rs (5)

1-11: LGTM! Imports updated appropriately for the thread-based model.


77-79: LGTM! Correctly updated to call synchronous truncate.


190-233: LGTM! Clean synchronous conversion.

The sequential slot range deletion followed by flush and compaction is appropriate for the isolated thread model.


322-328: LGTM! Proper thread isolation with dedicated runtime.

Spawning the worker on a dedicated std::thread with its own current_thread runtime achieves the isolation goal, ensuring blocking operations in the truncator cannot affect other system components.


350-361: LGTM! Correctly adapted for std::thread lifecycle.

The join method properly handles the thread-based worker and returns thread::Result<()>.

Comment thread magicblock-aperture/src/lib.rs
Comment thread magicblock-aperture/src/lib.rs
Comment thread magicblock-aperture/src/lib.rs
Comment thread magicblock-api/src/magic_validator.rs
Comment thread magicblock-ledger/tests/test_ledger_truncator.rs

@GabrielePicco GabrielePicco left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM!

@thlorenz thlorenz 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.

LGTM.

let builder = conn::auto::Builder::new(TokioExecutor::new());
let connection = builder.serve_connection(io, handler);
tokio::pin!(connection);
let mut terminating = false;

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.

Nit: I wonder if there is a better way than using this flag here. Maybe putting the connection inside an Option and then doing something like if let Some(..)?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

it will be ugly to pinning requirements of the connection future, plain flag is the most straightforward solution

@bmuddha
bmuddha merged commit 7c931d6 into master Nov 26, 2025
20 checks passed
@bmuddha
bmuddha deleted the bmuddha/fix/isolated-runtimes branch November 26, 2025 12:01
thlorenz added a commit that referenced this pull request Nov 26, 2025
* master:
  fix: use isolated runtimes for critical components (#682)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants