Skip to content

Conversation

@bmuddha
Copy link
Contributor

@bmuddha bmuddha commented Oct 27, 2025

This introduces a new multi-threaded transaction scheduler to the magicblock-processor, replacing the previous single-threaded implementation. This new design significantly improves performance by processing non-conflicting transactions in parallel.

The core of this feature is a central scheduler that dispatches transactions to a pool of worker threads. A lock-free coordination mechanism based on bitmasks is used to manage account access, ensuring that only transactions with non-overlapping write sets are executed concurrently.

Changes in this PR include:

  • Multi-threaded Scheduler: Implementation of the main scheduler loop, worker pool, and event handling.
  • Execution Coordinator: A central component that manages transaction dependencies, queues blocked transactions, and tracks ready executors.
  • Account Locking: A highly efficient, bitmask-based locking mechanism for read/write access to accounts.

Summary by CodeRabbit

  • Performance Improvements

    • New multi-executor scheduler with per-account locking, contention tracking, automatic rescheduling, and executor count tuned to CPU cores.
    • Event processor now starts 2 instances; validator startup uses a tuned Tokio runtime.
  • Behavior Changes

    • Airdrop: clearer "free airdrop faucet is disabled" message and lamports must be > 0.
    • RPC: adjusted block-range upper bound; mocks report current block height.
    • Token balances: improved UI amount conversion.
    • Transaction results may include execution logs.
  • Tests

    • Extensive new scheduler and end-to-end multi-executor tests.
  • Chores

    • Dependency/workspace updates (rand, rustc-hash, num_cpus) and other test harness improvements.

Copy link
Collaborator

@GabrielePicco GabrielePicco left a comment

Choose a reason for hiding this comment

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

LGTM!

Copy link
Contributor

@taco-paco taco-paco left a comment

Choose a reason for hiding this comment

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

Early review with one critical issue.

Copy link
Contributor

@taco-paco taco-paco left a comment

Choose a reason for hiding this comment

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

We need tests simulating various load scenarios and seeing that our Scheduler copes with them

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e808bdb and da6777d.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (7)
  • Cargo.toml (1 hunks)
  • magicblock-processor/Cargo.toml (1 hunks)
  • magicblock-processor/src/executor/mod.rs (4 hunks)
  • magicblock-processor/src/scheduler/coordinator.rs (1 hunks)
  • magicblock-processor/src/scheduler/locks.rs (1 hunks)
  • magicblock-processor/src/scheduler/mod.rs (1 hunks)
  • magicblock-processor/src/scheduler/tests.rs (1 hunks)
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 589
File: magicblock-processor/src/scheduler/locks.rs:110-122
Timestamp: 2025-11-04T10:53:50.896Z
Learning: In magicblock-processor, the TransactionScheduler runs in a single, dedicated thread and will always remain single-threaded. The `next_transaction_id()` function in scheduler/locks.rs uses `unsafe static mut` which is safe given this architectural guarantee.
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 578
File: magicblock-accounts-db/src/lib.rs:63-72
Timestamp: 2025-10-21T10:34:59.140Z
Learning: In magicblock-validator, the AccountsDb "stop-the-world" synchronizer is managed at the processor/executor level, not at the AccountsDb API level. Transaction executors in magicblock-processor hold a read lock (sync.read()) for the duration of each slot and release it only at slot boundaries, ensuring all account writes happen under the read lock. Snapshot operations acquire a write lock, blocking until all executors release their read locks. This pattern ensures mutual exclusion between writes and snapshots without requiring read guards in AccountsDb write APIs.
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 596
File: magicblock-processor/src/scheduler.rs:1-1
Timestamp: 2025-10-28T13:15:42.706Z
Learning: In magicblock-processor, transaction indexes were always set to 0 even before the changes in PR #596. The proper transaction indexing within slots will be addressed during the planned ledger rewrite.
📚 Learning: 2025-11-04T10:53:50.896Z
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 589
File: magicblock-processor/src/scheduler/locks.rs:110-122
Timestamp: 2025-11-04T10:53:50.896Z
Learning: In magicblock-processor, the TransactionScheduler runs in a single, dedicated thread and will always remain single-threaded. The `next_transaction_id()` function in scheduler/locks.rs uses `unsafe static mut` which is safe given this architectural guarantee.

Applied to files:

  • magicblock-processor/src/scheduler/mod.rs
  • magicblock-processor/src/scheduler/tests.rs
  • magicblock-processor/src/executor/mod.rs
  • magicblock-processor/src/scheduler/locks.rs
  • magicblock-processor/src/scheduler/coordinator.rs
📚 Learning: 2025-11-04T10:48:00.047Z
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.047Z
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-processor/src/scheduler/mod.rs
  • magicblock-processor/src/scheduler/locks.rs
📚 Learning: 2025-10-21T10:34:59.140Z
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 578
File: magicblock-accounts-db/src/lib.rs:63-72
Timestamp: 2025-10-21T10:34:59.140Z
Learning: In magicblock-validator, the AccountsDb "stop-the-world" synchronizer is managed at the processor/executor level, not at the AccountsDb API level. Transaction executors in magicblock-processor hold a read lock (sync.read()) for the duration of each slot and release it only at slot boundaries, ensuring all account writes happen under the read lock. Snapshot operations acquire a write lock, blocking until all executors release their read locks. This pattern ensures mutual exclusion between writes and snapshots without requiring read guards in AccountsDb write APIs.

Applied to files:

  • magicblock-processor/src/scheduler/mod.rs
  • magicblock-processor/src/executor/mod.rs
  • magicblock-processor/src/scheduler/locks.rs
  • magicblock-processor/src/scheduler/coordinator.rs
📚 Learning: 2025-10-28T13:15:42.706Z
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 596
File: magicblock-processor/src/scheduler.rs:1-1
Timestamp: 2025-10-28T13:15:42.706Z
Learning: In magicblock-processor, transaction indexes were always set to 0 even before the changes in PR #596. The proper transaction indexing within slots will be addressed during the planned ledger rewrite.

Applied to files:

  • magicblock-processor/src/scheduler/mod.rs
  • magicblock-processor/src/scheduler/locks.rs
  • magicblock-processor/src/scheduler/coordinator.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-processor/src/scheduler/mod.rs
📚 Learning: 2025-10-14T09:56:14.047Z
Learnt from: taco-paco
Repo: magicblock-labs/magicblock-validator PR: 564
File: test-integration/programs/flexi-counter/src/processor/call_handler.rs:122-125
Timestamp: 2025-10-14T09:56:14.047Z
Learning: The file test-integration/programs/flexi-counter/src/processor/call_handler.rs contains a test smart contract used for integration testing, not production code.

Applied to files:

  • magicblock-processor/src/scheduler/tests.rs
🧬 Code graph analysis (3)
magicblock-processor/src/scheduler/mod.rs (4)
magicblock-core/src/link.rs (1)
  • link (56-82)
magicblock-processor/src/executor/mod.rs (4)
  • new (66-107)
  • spawn (131-142)
  • run (156-195)
  • transition_to_new_slot (199-204)
magicblock-processor/src/scheduler/coordinator.rs (3)
  • new (32-37)
  • new (57-65)
  • is_ready (106-108)
magicblock-processor/src/scheduler.rs (2)
  • TransactionScheduler (40-148)
  • TransactionScheduler (26-38)
magicblock-processor/src/scheduler/tests.rs (1)
magicblock-processor/src/scheduler/coordinator.rs (5)
  • new (32-37)
  • new (57-65)
  • try_acquire_locks (142-181)
  • get_ready_executor (111-113)
  • next_blocked_transaction (130-135)
magicblock-processor/src/scheduler/coordinator.rs (2)
magicblock-processor/src/scheduler/locks.rs (1)
  • next_transaction_id (111-122)
magicblock-processor/src/scheduler/mod.rs (1)
  • new (58-91)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: run_make_ci_lint
🔇 Additional comments (2)
magicblock-processor/Cargo.toml (1)

42-42: Workspace dependencies appropriately added for multi-threaded scheduler.

The additions of rustc-hash and solana-keypair are consistent with the new lock-based scheduler design. rustc-hash is a fast hasher for fixed-size account keys used in contention tracking, and solana-keypair provides test utilities for the executor suite.

Also applies to: 46-46

Cargo.toml (1)

148-148: rustc-hash version pinned appropriately for workspace.

Version 2.1 is a stable release of the Rust compiler's hash implementation, ideal for the fixed-size account key hashing in the new lock contention tracking subsystem introduced by the multi-threaded scheduler.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (3)
magicblock-aperture/src/requests/http/mocked.rs (1)

197-201: Type mismatch remains unresolved: commitment should be Vec, not array.

The previously flagged issue still exists. RpcBlockCommitment { commitment: Option<Vec<_>> } expects a Vec, but an array [0; 32] is provided.

Apply this diff to fix:

-        let response = RpcBlockCommitment {
-            commitment: Some([0; 32]),
-            total_stake: 0,
-        };
+        let response = RpcBlockCommitment {
+            commitment: Some(vec![0; 32]),
+            total_stake: 0,
+        };
magicblock-aperture/src/requests/http/get_blocks_with_limit.rs (1)

22-24: Address overflow risks in end_slot calculation.

The addition of + 1 to block_height() on line 24 partially addresses the past review comment's off-by-one concern, but critical overflow issues remain unresolved:

  1. Line 22: start_slot + limit can overflow when both values are large (e.g., high slot number + 500,000 limit).
  2. Line 24: block_height() + 1 can overflow if block_height() returns u64::MAX.

Both operations should use saturating_add to prevent panic in release builds or wrap-around behavior.

Apply this diff to fix the overflow issues:

-        let end_slot = start_slot + limit;
+        let end_slot = start_slot.saturating_add(limit);
         // Calculate the end slot, ensuring it does not exceed the latest block height.
-        let end_slot = (end_slot).min(self.blocks.block_height() + 1);
+        let end_slot = end_slot.min(self.blocks.block_height().saturating_add(1));
magicblock-processor/src/scheduler/mod.rs (1)

172-190: Restore reserved executors when a reschedule attempt fails.

reschedule_blocked_transactions pulls the next ready executor before calling schedule_transaction. If lock acquisition still fails, we break out of the loop without putting that executor back into ready_executors, so one contention cycle silently shrinks the pool. After a few such misses every worker gets leaked, is_ready() stays false, and the scheduler stops dispatching even though threads are idle. Please defer taking the next executor until after a successful schedule (or explicitly release it on failure).

-        while let Some(exec) = executor {
+        while let Some(exec) = executor {
             let txn = self.coordinator.next_blocked_transaction(blocker);
             let scheduled = if let Some(txn) = txn {
-                executor = self.coordinator.get_ready_executor();
-                self.schedule_transaction(exec, txn, true).await
+                let scheduled =
+                    self.schedule_transaction(exec, txn, true).await;
+                if scheduled {
+                    executor = self.coordinator.get_ready_executor();
+                } else {
+                    executor = None;
+                }
+                scheduled
             } else {
                 self.coordinator.release_executor(exec);
                 break;
             };
             if !scheduled {
                 break;
             }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between da6777d and 57ff682.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • Cargo.toml (2 hunks)
  • magicblock-aperture/Cargo.toml (1 hunks)
  • magicblock-aperture/src/requests/http/get_blocks_with_limit.rs (1 hunks)
  • magicblock-aperture/src/requests/http/get_token_account_balance.rs (2 hunks)
  • magicblock-aperture/src/requests/http/mocked.rs (3 hunks)
  • magicblock-aperture/src/requests/http/request_airdrop.rs (1 hunks)
  • magicblock-processor/src/scheduler/mod.rs (1 hunks)
  • magicblock-table-mania/src/manager.rs (1 hunks)
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 589
File: magicblock-processor/src/scheduler/locks.rs:110-122
Timestamp: 2025-11-04T10:53:50.896Z
Learning: In magicblock-processor, the TransactionScheduler runs in a single, dedicated thread and will always remain single-threaded. The `next_transaction_id()` function in scheduler/locks.rs uses `unsafe static mut` which is safe given this architectural guarantee.
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 578
File: magicblock-accounts-db/src/lib.rs:63-72
Timestamp: 2025-10-21T10:34:59.140Z
Learning: In magicblock-validator, the AccountsDb "stop-the-world" synchronizer is managed at the processor/executor level, not at the AccountsDb API level. Transaction executors in magicblock-processor hold a read lock (sync.read()) for the duration of each slot and release it only at slot boundaries, ensuring all account writes happen under the read lock. Snapshot operations acquire a write lock, blocking until all executors release their read locks. This pattern ensures mutual exclusion between writes and snapshots without requiring read guards in AccountsDb write APIs.
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 596
File: magicblock-processor/src/scheduler.rs:1-1
Timestamp: 2025-10-28T13:15:42.706Z
Learning: In magicblock-processor, transaction indexes were always set to 0 even before the changes in PR #596. The proper transaction indexing within slots will be addressed during the planned ledger rewrite.
📚 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-aperture/src/requests/http/request_airdrop.rs
  • magicblock-aperture/src/requests/http/get_token_account_balance.rs
  • magicblock-aperture/src/requests/http/mocked.rs
  • magicblock-processor/src/scheduler/mod.rs
  • magicblock-aperture/src/requests/http/get_blocks_with_limit.rs
📚 Learning: 2025-10-21T11:00:18.396Z
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 578
File: magicblock-aperture/src/encoder.rs:176-187
Timestamp: 2025-10-21T11:00:18.396Z
Learning: In the magicblock validator, the current slot is always the root slot. The SlotEncoder in magicblock-aperture/src/encoder.rs correctly sets `root: slot` because there is no lag between current and root slots in this architecture.

Applied to files:

  • magicblock-aperture/src/requests/http/get_token_account_balance.rs
  • magicblock-aperture/src/requests/http/mocked.rs
  • magicblock-aperture/src/requests/http/get_blocks_with_limit.rs
📚 Learning: 2025-11-04T10:53:50.896Z
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 589
File: magicblock-processor/src/scheduler/locks.rs:110-122
Timestamp: 2025-11-04T10:53:50.896Z
Learning: In magicblock-processor, the TransactionScheduler runs in a single, dedicated thread and will always remain single-threaded. The `next_transaction_id()` function in scheduler/locks.rs uses `unsafe static mut` which is safe given this architectural guarantee.

Applied to files:

  • magicblock-processor/src/scheduler/mod.rs
📚 Learning: 2025-11-04T10:48:00.047Z
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.047Z
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-processor/src/scheduler/mod.rs
📚 Learning: 2025-10-21T10:34:59.140Z
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 578
File: magicblock-accounts-db/src/lib.rs:63-72
Timestamp: 2025-10-21T10:34:59.140Z
Learning: In magicblock-validator, the AccountsDb "stop-the-world" synchronizer is managed at the processor/executor level, not at the AccountsDb API level. Transaction executors in magicblock-processor hold a read lock (sync.read()) for the duration of each slot and release it only at slot boundaries, ensuring all account writes happen under the read lock. Snapshot operations acquire a write lock, blocking until all executors release their read locks. This pattern ensures mutual exclusion between writes and snapshots without requiring read guards in AccountsDb write APIs.

Applied to files:

  • magicblock-processor/src/scheduler/mod.rs
📚 Learning: 2025-10-28T13:15:42.706Z
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 596
File: magicblock-processor/src/scheduler.rs:1-1
Timestamp: 2025-10-28T13:15:42.706Z
Learning: In magicblock-processor, transaction indexes were always set to 0 even before the changes in PR #596. The proper transaction indexing within slots will be addressed during the planned ledger rewrite.

Applied to files:

  • magicblock-processor/src/scheduler/mod.rs
🧬 Code graph analysis (3)
magicblock-aperture/src/requests/http/request_airdrop.rs (2)
magicblock-aperture/src/error.rs (2)
  • invalid_request (99-104)
  • invalid_params (78-83)
magicblock-aperture/src/requests/mod.rs (1)
  • params (22-26)
magicblock-table-mania/src/manager.rs (1)
magicblock-committor-service/src/intent_execution_manager/intent_execution_engine.rs (1)
  • rand (587-587)
magicblock-processor/src/scheduler/mod.rs (4)
magicblock-core/src/link.rs (1)
  • link (56-82)
magicblock-processor/src/scheduler/coordinator.rs (3)
  • new (32-37)
  • new (57-65)
  • is_ready (106-108)
magicblock-processor/src/executor/mod.rs (4)
  • new (66-107)
  • spawn (131-142)
  • run (156-195)
  • transition_to_new_slot (199-204)
magicblock-processor/src/scheduler.rs (2)
  • TransactionScheduler (40-148)
  • TransactionScheduler (26-38)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: run_make_ci_lint
  • GitHub Check: run_make_ci_test
  • GitHub Check: Build Project
🔇 Additional comments (12)
magicblock-aperture/src/requests/http/mocked.rs (3)

84-89: LGTM! Slot context standardized.

This change addresses the previous review comment by standardizing on block_height() for consistency with other handlers.


103-108: LGTM! Slot context standardized.

Consistent with the standardization applied to other handlers.


120-125: LGTM! Slot context standardized.

This completes the standardization across all three handlers flagged in the previous review.

magicblock-aperture/src/requests/http/get_token_account_balance.rs (2)

4-7: LGTM: Necessary imports for canonical token amount formatter.

The added imports enable the use of Solana's token_amount_to_ui_amount_v3 function, which provides standardized UI amount formatting for SPL tokens.


64-71: Excellent refactor: adopts Solana's canonical UI amount formatter.

This change addresses the previous concern about ui_amount_string formatting by delegating to token_amount_to_ui_amount_v3, which handles edge cases like scientific notation and precision trimming correctly.

The None values for interest_bearing_config and scaled_ui_amount_config are appropriate for standard SPL tokens. If Token-2022 extended tokens (interest-bearing, scaled UI amounts) are in scope, additional parsing of mint extensions would be required—acceptable technical debt for now.

magicblock-aperture/src/requests/http/request_airdrop.rs (3)

18-20: Improved error message for disabled faucet.

The error message has been updated to clearly indicate that the faucet is disabled, which is more informative than the previous generic "method is not supported" message.


27-29: Good addition of lamports validation.

The zero-amount validation correctly prevents no-op airdrop requests and provides a clear error message.


25-26: Verify: Previous review suggested explicit Pubkey type cast.

The earlier review comment on these lines recommended adding an explicit type annotation to cast pubkey to Pubkey type (i.e., let pubkey: Pubkey = some_or_err!(pubkey);), stating this matched the project's existing pattern. While the lamports validation has been added, the type cast was not implemented.

Please confirm whether the explicit cast is necessary for type consistency across the codebase or if the current implicit conversion is sufficient.

magicblock-aperture/Cargo.toml (1)

69-69: LGTM! Workspace reference applied.

This change addresses the previous review comment by using a workspace reference for rand, ensuring centralized version management and consistency with other dependencies.

Cargo.toml (2)

94-94: Verify the intentional removal of log level filtering.

The release_max_level_info feature has been removed, which means log statements will no longer be filtered at compile time in release builds. This could impact performance and binary size.

Is this intentional for improved production debugging, or should the feature be retained for performance?


148-148: LGTM! rustc-hash addition is appropriate.

The addition of rustc-hash for lock contention tracking with fixed 32-byte keys is well-justified and faster than siphash for this use case, as discussed in previous comments.

magicblock-table-mania/src/manager.rs (1)

380-381: API migration is correct.

The code properly uses the rand 0.9 API: rand::rng() replaces rand::thread_rng(), and Rng::random_range(a..b) replaces gen_range(a, b) with support for inclusive ranges. The range 0..=u64::MAX is valid syntax for the new API.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
test-kit/src/lib.rs (1)

86-115: Update the constructor docs to match the new behavior.

Bullet 3 still claims we “spawn a TransactionScheduler with one worker thread,” but new_with_config now accepts an executor count and an option to defer startup. Please revise the doc comment so it explains the configurable worker pool and optional deferred spawn.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 57ff682 and 8cb00c2.

📒 Files selected for processing (5)
  • magicblock-core/src/link/transactions.rs (2 hunks)
  • magicblock-processor/src/scheduler/mod.rs (1 hunks)
  • magicblock-processor/tests/fees.rs (1 hunks)
  • magicblock-processor/tests/scheduling.rs (1 hunks)
  • test-kit/src/lib.rs (6 hunks)
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 589
File: magicblock-processor/src/scheduler/locks.rs:110-122
Timestamp: 2025-11-04T10:53:50.922Z
Learning: In magicblock-processor, the TransactionScheduler runs in a single, dedicated thread and will always remain single-threaded. The `next_transaction_id()` function in scheduler/locks.rs uses `unsafe static mut` which is safe given this architectural guarantee.
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 596
File: magicblock-processor/src/scheduler.rs:1-1
Timestamp: 2025-10-28T13:15:42.706Z
Learning: In magicblock-processor, transaction indexes were always set to 0 even before the changes in PR #596. The proper transaction indexing within slots will be addressed during the planned ledger rewrite.
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 578
File: magicblock-accounts-db/src/lib.rs:63-72
Timestamp: 2025-10-21T10:34:59.140Z
Learning: In magicblock-validator, the AccountsDb "stop-the-world" synchronizer is managed at the processor/executor level, not at the AccountsDb API level. Transaction executors in magicblock-processor hold a read lock (sync.read()) for the duration of each slot and release it only at slot boundaries, ensuring all account writes happen under the read lock. Snapshot operations acquire a write lock, blocking until all executors release their read locks. This pattern ensures mutual exclusion between writes and snapshots without requiring read guards in AccountsDb write APIs.
📚 Learning: 2025-11-04T10:53:50.922Z
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 589
File: magicblock-processor/src/scheduler/locks.rs:110-122
Timestamp: 2025-11-04T10:53:50.922Z
Learning: In magicblock-processor, the TransactionScheduler runs in a single, dedicated thread and will always remain single-threaded. The `next_transaction_id()` function in scheduler/locks.rs uses `unsafe static mut` which is safe given this architectural guarantee.

Applied to files:

  • magicblock-processor/src/scheduler/mod.rs
  • test-kit/src/lib.rs
  • magicblock-processor/tests/scheduling.rs
📚 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-processor/src/scheduler/mod.rs
📚 Learning: 2025-10-21T10:34:59.140Z
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 578
File: magicblock-accounts-db/src/lib.rs:63-72
Timestamp: 2025-10-21T10:34:59.140Z
Learning: In magicblock-validator, the AccountsDb "stop-the-world" synchronizer is managed at the processor/executor level, not at the AccountsDb API level. Transaction executors in magicblock-processor hold a read lock (sync.read()) for the duration of each slot and release it only at slot boundaries, ensuring all account writes happen under the read lock. Snapshot operations acquire a write lock, blocking until all executors release their read locks. This pattern ensures mutual exclusion between writes and snapshots without requiring read guards in AccountsDb write APIs.

Applied to files:

  • magicblock-processor/src/scheduler/mod.rs
📚 Learning: 2025-10-28T13:15:42.706Z
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 596
File: magicblock-processor/src/scheduler.rs:1-1
Timestamp: 2025-10-28T13:15:42.706Z
Learning: In magicblock-processor, transaction indexes were always set to 0 even before the changes in PR #596. The proper transaction indexing within slots will be addressed during the planned ledger rewrite.

Applied to files:

  • magicblock-processor/src/scheduler/mod.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-processor/src/scheduler/mod.rs
📚 Learning: 2025-10-14T09:56:14.047Z
Learnt from: taco-paco
Repo: magicblock-labs/magicblock-validator PR: 564
File: test-integration/programs/flexi-counter/src/processor/call_handler.rs:122-125
Timestamp: 2025-10-14T09:56:14.047Z
Learning: The file test-integration/programs/flexi-counter/src/processor/call_handler.rs contains a test smart contract used for integration testing, not production code.

Applied to files:

  • magicblock-processor/tests/scheduling.rs
🧬 Code graph analysis (4)
magicblock-processor/src/scheduler/mod.rs (4)
magicblock-core/src/link.rs (1)
  • link (56-82)
magicblock-processor/src/scheduler/coordinator.rs (3)
  • new (32-37)
  • new (57-65)
  • is_ready (106-108)
magicblock-processor/src/executor/mod.rs (4)
  • new (66-107)
  • spawn (131-142)
  • run (156-195)
  • transition_to_new_slot (199-204)
magicblock-processor/src/scheduler.rs (2)
  • TransactionScheduler (40-148)
  • TransactionScheduler (26-38)
magicblock-processor/tests/fees.rs (1)
test-kit/src/lib.rs (1)
  • new_with_config (94-152)
test-kit/src/lib.rs (2)
magicblock-accounts/src/scheduled_commits_processor.rs (2)
  • new (66-90)
  • new (418-433)
magicblock-aperture/tests/setup.rs (1)
  • new (83-138)
magicblock-processor/tests/scheduling.rs (2)
test-kit/src/lib.rs (4)
  • new_with_config (94-152)
  • new (82-84)
  • get_transaction (200-208)
  • get_account (297-307)
magicblock-processor/src/scheduler/mod.rs (1)
  • new (58-91)
🪛 GitHub Actions: Run CI - Format
magicblock-processor/tests/scheduling.rs

[error] 1-7: cargo fmt --check failed. Detected unstaged formatting changes in tests/scheduling.rs. Run 'cargo fmt' (or update formatting) to fix code style issues.

🪛 GitHub Actions: Run CI - Lint
magicblock-processor/tests/scheduling.rs

[error] 562-562: the loop variable i is only used to index accounts (clippy: needless_range_loop). The lint suggests using an iterator, e.g., for in accounts.iter().take(num_txs).skip(1) { ... }

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build Project
  • GitHub Check: run_make_ci_test
🔇 Additional comments (3)
magicblock-core/src/link/transactions.rs (2)

54-54: LGTM: Debug derives improve observability.

The Debug trait additions on TransactionStatus and TransactionExecutionResult are beneficial for debugging and logging, especially valuable in the new multi-threaded scheduler context.

Also applies to: 80-80


84-84: All construction sites have been properly updated with the logs field.

Verification confirms that the single construction site in magicblock-processor/src/executor/processing.rs:202-210 correctly initializes all three struct fields, including the new logs field (logs: meta.log_messages.clone(),). No other construction sites or alternative instantiation patterns exist in the codebase.

magicblock-processor/tests/fees.rs (1)

270-308: Zero-fee setup still behaves as before.

Using new_with_config(0, 1, false) keeps the slot in gasless mode while spawning the scheduler eagerly, so the test’s intent is preserved.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a3e0029 and b1e0933.

📒 Files selected for processing (14)
  • magicblock-aperture/src/lib.rs (1 hunks)
  • magicblock-aperture/src/tests.rs (1 hunks)
  • magicblock-aperture/tests/mocked.rs (2 hunks)
  • magicblock-aperture/tests/node.rs (1 hunks)
  • magicblock-aperture/tests/setup.rs (1 hunks)
  • magicblock-api/src/magic_validator.rs (1 hunks)
  • magicblock-processor/src/executor/mod.rs (5 hunks)
  • magicblock-processor/src/scheduler/coordinator.rs (1 hunks)
  • magicblock-processor/src/scheduler/locks.rs (1 hunks)
  • magicblock-processor/src/scheduler/mod.rs (1 hunks)
  • magicblock-processor/src/scheduler/tests.rs (1 hunks)
  • magicblock-processor/tests/fees.rs (2 hunks)
  • magicblock-processor/tests/scheduling.rs (1 hunks)
  • test-kit/src/lib.rs (10 hunks)
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 589
File: magicblock-processor/src/scheduler/locks.rs:110-122
Timestamp: 2025-11-04T10:53:50.922Z
Learning: In magicblock-processor, the TransactionScheduler runs in a single, dedicated thread and will always remain single-threaded. The `next_transaction_id()` function in scheduler/locks.rs uses `unsafe static mut` which is safe given this architectural guarantee.
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 596
File: magicblock-processor/src/scheduler.rs:1-1
Timestamp: 2025-10-28T13:15:42.706Z
Learning: In magicblock-processor, transaction indexes were always set to 0 even before the changes in PR #596. The proper transaction indexing within slots will be addressed during the planned ledger rewrite.
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 578
File: magicblock-accounts-db/src/lib.rs:63-72
Timestamp: 2025-10-21T10:34:59.140Z
Learning: In magicblock-validator, the AccountsDb "stop-the-world" synchronizer is managed at the processor/executor level, not at the AccountsDb API level. Transaction executors in magicblock-processor hold a read lock (sync.read()) for the duration of each slot and release it only at slot boundaries, ensuring all account writes happen under the read lock. Snapshot operations acquire a write lock, blocking until all executors release their read locks. This pattern ensures mutual exclusion between writes and snapshots without requiring read guards in AccountsDb write APIs.
📚 Learning: 2025-10-14T09:56:14.047Z
Learnt from: taco-paco
Repo: magicblock-labs/magicblock-validator PR: 564
File: test-integration/programs/flexi-counter/src/processor/call_handler.rs:122-125
Timestamp: 2025-10-14T09:56:14.047Z
Learning: The file test-integration/programs/flexi-counter/src/processor/call_handler.rs contains a test smart contract used for integration testing, not production code.

Applied to files:

  • magicblock-aperture/src/tests.rs
  • magicblock-processor/tests/scheduling.rs
  • magicblock-processor/src/scheduler/tests.rs
  • magicblock-processor/tests/fees.rs
📚 Learning: 2025-11-04T10:53:50.922Z
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 589
File: magicblock-processor/src/scheduler/locks.rs:110-122
Timestamp: 2025-11-04T10:53:50.922Z
Learning: In magicblock-processor, the TransactionScheduler runs in a single, dedicated thread and will always remain single-threaded. The `next_transaction_id()` function in scheduler/locks.rs uses `unsafe static mut` which is safe given this architectural guarantee.

Applied to files:

  • magicblock-api/src/magic_validator.rs
  • magicblock-processor/src/executor/mod.rs
  • magicblock-processor/tests/scheduling.rs
  • test-kit/src/lib.rs
  • magicblock-processor/src/scheduler/mod.rs
  • magicblock-processor/src/scheduler/locks.rs
  • magicblock-processor/src/scheduler/coordinator.rs
  • magicblock-processor/src/scheduler/tests.rs
📚 Learning: 2025-10-28T13:15:42.706Z
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 596
File: magicblock-processor/src/scheduler.rs:1-1
Timestamp: 2025-10-28T13:15:42.706Z
Learning: In magicblock-processor, transaction indexes were always set to 0 even before the changes in PR #596. The proper transaction indexing within slots will be addressed during the planned ledger rewrite.

Applied to files:

  • magicblock-api/src/magic_validator.rs
  • magicblock-processor/src/scheduler/mod.rs
  • magicblock-processor/src/scheduler/locks.rs
  • magicblock-processor/src/scheduler/coordinator.rs
📚 Learning: 2025-10-21T10:34:59.140Z
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 578
File: magicblock-accounts-db/src/lib.rs:63-72
Timestamp: 2025-10-21T10:34:59.140Z
Learning: In magicblock-validator, the AccountsDb "stop-the-world" synchronizer is managed at the processor/executor level, not at the AccountsDb API level. Transaction executors in magicblock-processor hold a read lock (sync.read()) for the duration of each slot and release it only at slot boundaries, ensuring all account writes happen under the read lock. Snapshot operations acquire a write lock, blocking until all executors release their read locks. This pattern ensures mutual exclusion between writes and snapshots without requiring read guards in AccountsDb write APIs.

Applied to files:

  • magicblock-processor/src/executor/mod.rs
  • magicblock-processor/src/scheduler/mod.rs
  • magicblock-processor/src/scheduler/locks.rs
  • magicblock-processor/src/scheduler/coordinator.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-processor/tests/scheduling.rs
  • magicblock-processor/src/scheduler/mod.rs
📚 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:

  • test-kit/src/lib.rs
  • magicblock-processor/src/scheduler/mod.rs
  • magicblock-processor/src/scheduler/locks.rs
🧬 Code graph analysis (11)
magicblock-aperture/src/lib.rs (1)
magicblock-aperture/src/processor.rs (1)
  • start (73-83)
magicblock-api/src/magic_validator.rs (1)
magicblock-processor/src/scheduler/mod.rs (1)
  • new (58-91)
magicblock-processor/src/executor/mod.rs (1)
magicblock-committor-service/src/service.rs (1)
  • try_send (327-340)
magicblock-aperture/tests/node.rs (1)
test-kit/src/lib.rs (1)
  • get_payer (321-327)
magicblock-processor/tests/scheduling.rs (1)
test-kit/src/lib.rs (4)
  • new_with_config (99-160)
  • new (87-89)
  • get_transaction (208-216)
  • get_account (309-319)
test-kit/src/lib.rs (2)
magicblock-aperture/tests/setup.rs (1)
  • new (83-138)
magicblock-processor/src/scheduler/mod.rs (1)
  • new (58-91)
magicblock-aperture/tests/mocked.rs (1)
test-kit/src/lib.rs (1)
  • get_payer (321-327)
magicblock-processor/src/scheduler/mod.rs (3)
magicblock-core/src/link.rs (1)
  • link (56-82)
magicblock-processor/src/executor/mod.rs (4)
  • new (66-107)
  • spawn (131-142)
  • run (156-196)
  • transition_to_new_slot (200-205)
magicblock-processor/src/scheduler/coordinator.rs (3)
  • new (32-37)
  • new (59-68)
  • is_ready (109-111)
magicblock-processor/src/scheduler/coordinator.rs (2)
magicblock-processor/src/scheduler/locks.rs (1)
  • next_transaction_id (98-109)
magicblock-processor/src/scheduler/mod.rs (1)
  • new (58-91)
magicblock-processor/src/scheduler/tests.rs (2)
magicblock-processor/src/scheduler/coordinator.rs (5)
  • new (32-37)
  • new (59-68)
  • try_acquire_locks (147-213)
  • get_ready_executor (115-117)
  • next_blocked_transaction (135-140)
magicblock-processor/src/scheduler/mod.rs (1)
  • new (58-91)
magicblock-processor/tests/fees.rs (1)
test-kit/src/lib.rs (2)
  • get_payer (321-327)
  • new_with_config (99-160)
🪛 GitHub Actions: Run CI - Format
magicblock-processor/src/scheduler/tests.rs

[error] 10-12: Rustfmt: code formatting check failed. Run 'cargo +nightly fmt --check -- --config-path rustfmt-nightly.toml' to fix formatting.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: run_make_ci_lint
🔇 Additional comments (34)
magicblock-aperture/src/tests.rs (1)

60-60: LGTM!

The change correctly adopts the new get_payer() accessor pattern introduced in test-kit, replacing direct field access with the method-based approach.

magicblock-aperture/tests/setup.rs (1)

96-96: LGTM!

The change correctly migrates to the new get_payer() accessor. Note the API shift from payer.pubkey() (method) to get_payer().pubkey (field access on returned CommitableAccount).

magicblock-aperture/tests/node.rs (1)

37-37: LGTM!

The test assertion correctly adopts the new accessor pattern, maintaining the same test logic while using the updated API.

magicblock-aperture/tests/mocked.rs (2)

19-19: LGTM!

The test assertion correctly adopts the new get_payer() accessor pattern.


195-195: LGTM!

The string conversion correctly uses the new accessor pattern, maintaining the same test logic.

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

305-313: LGTM! Dynamic executor sizing is well-designed.

The calculation correctly handles edge cases (1-2 CPUs result in 1 executor, scaling appropriately with higher CPU counts). The formula reserves half the available CPUs for execution while accounting for the scheduler thread itself.

magicblock-processor/tests/fees.rs (2)

174-174: LGTM! Payer access updated correctly.

The change from direct field access to the getter method aligns with the refactored test-kit API.


270-270: LGTM! Test configuration correctly updated.

The new API call properly configures the environment for gasless mode testing with appropriate parameters (fee=0, single executor, no deferred startup).

magicblock-processor/tests/scheduling.rs (2)

101-198: LGTM! Assertion helpers are well-designed.

The timeout and polling logic correctly handles both unordered and strict-order validation scenarios. The 100ms poll interval is appropriate for test scenarios.


230-579: Excellent test coverage for concurrent scheduling behavior.

The scenarios comprehensively test:

  • Parallel non-conflicting execution
  • Serialization on write conflicts
  • Read-only parallelism
  • Mixed workloads
  • Order preservation guarantees

Particularly valuable are scenarios 6 and 7, which validate strict ordering for dependent transactions.

magicblock-processor/src/executor/mod.rs (1)

24-24: LGTM! ExecutorId type migration is consistent.

The rename from WorkerId to ExecutorId is applied correctly throughout the executor implementation, maintaining type safety across the module.

Also applies to: 34-34, 54-54, 67-67, 70-70

magicblock-processor/src/scheduler/tests.rs (4)

52-96: LGTM: Comprehensive basic test coverage.

The basic tests properly verify non-conflicting transactions and concurrent read locks. The test design is clean and the assertions are appropriate.


100-213: LGTM: Contention scenarios are well covered.

The contention tests thoroughly verify write-write, write-read, and read-write blocking behavior with appropriate assertions on the blocker IDs.


215-293: LGTM: Advanced scenarios demonstrate complex scheduling flows.

The dependency chain and full executor pool tests verify important edge cases like transaction queuing, rescheduling, and executor pool exhaustion. These scenarios are critical for correctness.


295-475: LGTM: Edge cases provide excellent coverage.

The edge case tests cover important scenarios like transactions with no accounts, rapid lock cycles, contention on multiple accounts, and blocked transaction chains. This comprehensive coverage will help catch regressions.

test-kit/src/lib.rs (4)

50-68: LGTM: Fields appropriately support multi-executor testing.

The addition of payer_index, payers pool, and optional scheduler enables proper multi-executor test scenarios with deferred startup control.


99-160: LGTM: Constructor properly initializes multi-payer environment.

The new_with_config constructor correctly creates multiple payers, allows deferred scheduler startup, and funds all payers. The conditional spawning logic is sound.


162-166: LGTM: Deferred scheduler startup helper is clean.

The run_scheduler method appropriately spawns a previously deferred scheduler when called.


250-261: Verify AtomicUsize ordering is sufficient for payer selection.

The fetch_add(Ordering::Relaxed) in build_transaction is appropriate for round-robin payer selection since there's no cross-thread synchronization requirement. However, get_payer uses load(Ordering::Relaxed) which may return a stale index if called immediately after another thread's build_transaction.

If tests rely on get_payer returning the payer of the most recently built transaction across threads, consider using Ordering::SeqCst or documenting that get_payer may return any payer from the pool.

magicblock-processor/src/scheduler/coordinator.rs (5)

30-38: LGTM: TransactionWithId wrapper is clean.

The wrapper correctly assigns unique IDs via next_transaction_id() for tracking transactions through the scheduling pipeline.

Based on learnings.


57-68: LGTM: Coordinator initialization is sound.

The constructor properly initializes per-executor queues and locks, and pre-populates the ready executor pool.


75-105: LGTM: Transaction queuing with blocker resolution is correct.

The queue_transaction method correctly resolves transaction ID blockers to executor IDs and maintains sorted order via binary search. The panic on unknown transaction ID is appropriate given the architectural invariant.


194-200: Clarify write-only contention tracking rationale.

The comment states "We only set contention for write locks, in order to prevent writer starvation" (line 196). This design choice means read-only transactions don't register contention, which could lead to reader starvation if many write transactions queue up.

Is this intentional? Consider documenting the trade-off more explicitly, or confirm that the system prioritizes writer throughput over reader fairness.


146-213: Rollback on partial lock acquisition failure is complete and sound.

Verification confirms:

  1. No side effects beyond unlock(): The write() and read() methods in AccountLock only perform bitwise operations on the rw field; unlock() correctly reverses these via bitwise AND.

  2. Vector tracking is exhaustive: The acquired_locks vector contains only locks that were successfully acquired (pushed after successful write()/read() calls). On failure, all tracked locks are drained and unlocked, ensuring no locks leak.

The implementation correctly handles partial failures: lock state is fully reversed via unlock(), and contention markers are properly set on failure and cleared on success. The code is sound.

magicblock-processor/src/scheduler/mod.rs (6)

58-91: LGTM: Scheduler initialization is well-structured.

The constructor properly clamps executor count, sets up channels, spawns executor threads, and initializes the coordinator. The sequential setup ensures all components are ready before the scheduler starts.


98-107: LGTM: Spawn pattern follows established architecture.

The spawn method correctly creates a dedicated OS thread with a single-threaded Tokio runtime for the scheduler, consistent with the architectural guarantee that enables the unsafe Send impl.

Based on learnings.


119-144: LGTM: Event loop priorities are appropriate.

The biased select ensures ready executors are processed before new transactions, which maximizes throughput by keeping executors busy. The readiness guard on transaction receipt prevents queueing when no executors are available.


208-225: LGTM: Transaction scheduling with proper error handling.

The schedule_transaction method correctly attempts lock acquisition, queues on failure, and sends to the executor on success. The try_send is safe given the capacity guarantee.


234-236: unsafe Send impl is appropriate given architectural guarantees.

The unsafe Send impl is justified because the scheduler runs in a single dedicated thread and the Rc<RefCell<...>> never escapes. This follows the established pattern in the codebase.

Based on learnings.


165-169: Program cache thread-safety is verified and correct.

The architecture uses separate OS threads for the scheduler and each executor, all sharing a single Arc<RwLock<ProgramCache>>. Both the scheduler (transition_to_new_slot at lines 165-169) and executors write to the cache, and access is properly serialized by the RwLock:

  • Multiple executors run concurrently in parallel threads (spawned at line 79 in scheduler/mod.rs)
  • The scheduler runs in its own dedicated thread (confirmed from learnings)
  • RwLock ensures mutual exclusion: write operations serialize all access (both readers and writers must wait)
  • This is the correct and expected behavior—RwLock trades some concurrency for thread safety

The implementation is sound. The concerns about concurrent reads being blocked during writes are not issues but rather the intended synchronization guarantees of RwLock. No changes are needed.

magicblock-processor/src/scheduler/locks.rs (4)

56-69: LGTM: Write lock implementation is correct.

The write lock correctly checks for any existing locks (line 60), returns the conflicting executor via trailing_zeros() (line 64), and sets both the write bit and executor bit atomically (line 67).


73-86: LGTM: Read lock implementation is correct.

The read lock properly checks for write locks (line 78), identifies the write lock holder (line 81), and sets the executor's read bit (line 84). Multiple readers can coexist since they only set their individual bits.


90-95: LGTM: Unlock clears both write and read bits.

The unlock operation correctly clears both the write flag and the executor's bit using a single bitwise AND operation. This works for both write locks (which have both bits set) and read locks (which only have the executor bit set).


97-109: LGTM: Transaction ID generation is safe given architectural guarantees.

The use of unsafe static mut is acceptable given the documented guarantee that the scheduler runs in a single dedicated thread. The wrapping add with max ensures IDs stay above MAX_SVM_EXECUTORS to distinguish transaction IDs from executor IDs.

Based on learnings.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a3e0029 and e077eab.

📒 Files selected for processing (14)
  • magicblock-aperture/src/lib.rs (1 hunks)
  • magicblock-aperture/src/tests.rs (1 hunks)
  • magicblock-aperture/tests/mocked.rs (2 hunks)
  • magicblock-aperture/tests/node.rs (1 hunks)
  • magicblock-aperture/tests/setup.rs (1 hunks)
  • magicblock-api/src/magic_validator.rs (1 hunks)
  • magicblock-processor/src/executor/mod.rs (5 hunks)
  • magicblock-processor/src/scheduler/coordinator.rs (1 hunks)
  • magicblock-processor/src/scheduler/locks.rs (1 hunks)
  • magicblock-processor/src/scheduler/mod.rs (1 hunks)
  • magicblock-processor/src/scheduler/tests.rs (1 hunks)
  • magicblock-processor/tests/fees.rs (2 hunks)
  • magicblock-processor/tests/scheduling.rs (1 hunks)
  • test-kit/src/lib.rs (10 hunks)
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 589
File: magicblock-processor/src/scheduler/locks.rs:110-122
Timestamp: 2025-11-04T10:53:50.922Z
Learning: In magicblock-processor, the TransactionScheduler runs in a single, dedicated thread and will always remain single-threaded. The `next_transaction_id()` function in scheduler/locks.rs uses `unsafe static mut` which is safe given this architectural guarantee.
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 596
File: magicblock-processor/src/scheduler.rs:1-1
Timestamp: 2025-10-28T13:15:42.706Z
Learning: In magicblock-processor, transaction indexes were always set to 0 even before the changes in PR #596. The proper transaction indexing within slots will be addressed during the planned ledger rewrite.
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 578
File: magicblock-accounts-db/src/lib.rs:63-72
Timestamp: 2025-10-21T10:34:59.140Z
Learning: In magicblock-validator, the AccountsDb "stop-the-world" synchronizer is managed at the processor/executor level, not at the AccountsDb API level. Transaction executors in magicblock-processor hold a read lock (sync.read()) for the duration of each slot and release it only at slot boundaries, ensuring all account writes happen under the read lock. Snapshot operations acquire a write lock, blocking until all executors release their read locks. This pattern ensures mutual exclusion between writes and snapshots without requiring read guards in AccountsDb write APIs.
📚 Learning: 2025-11-04T10:53:50.922Z
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 589
File: magicblock-processor/src/scheduler/locks.rs:110-122
Timestamp: 2025-11-04T10:53:50.922Z
Learning: In magicblock-processor, the TransactionScheduler runs in a single, dedicated thread and will always remain single-threaded. The `next_transaction_id()` function in scheduler/locks.rs uses `unsafe static mut` which is safe given this architectural guarantee.

Applied to files:

  • magicblock-processor/src/scheduler/tests.rs
  • magicblock-processor/src/executor/mod.rs
  • magicblock-processor/src/scheduler/mod.rs
  • magicblock-processor/tests/scheduling.rs
  • magicblock-api/src/magic_validator.rs
  • test-kit/src/lib.rs
  • magicblock-processor/src/scheduler/coordinator.rs
  • magicblock-processor/src/scheduler/locks.rs
📚 Learning: 2025-10-14T09:56:14.047Z
Learnt from: taco-paco
Repo: magicblock-labs/magicblock-validator PR: 564
File: test-integration/programs/flexi-counter/src/processor/call_handler.rs:122-125
Timestamp: 2025-10-14T09:56:14.047Z
Learning: The file test-integration/programs/flexi-counter/src/processor/call_handler.rs contains a test smart contract used for integration testing, not production code.

Applied to files:

  • magicblock-processor/src/scheduler/tests.rs
  • magicblock-processor/tests/scheduling.rs
  • magicblock-processor/tests/fees.rs
📚 Learning: 2025-10-21T10:34:59.140Z
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 578
File: magicblock-accounts-db/src/lib.rs:63-72
Timestamp: 2025-10-21T10:34:59.140Z
Learning: In magicblock-validator, the AccountsDb "stop-the-world" synchronizer is managed at the processor/executor level, not at the AccountsDb API level. Transaction executors in magicblock-processor hold a read lock (sync.read()) for the duration of each slot and release it only at slot boundaries, ensuring all account writes happen under the read lock. Snapshot operations acquire a write lock, blocking until all executors release their read locks. This pattern ensures mutual exclusion between writes and snapshots without requiring read guards in AccountsDb write APIs.

Applied to files:

  • magicblock-processor/src/executor/mod.rs
  • magicblock-processor/src/scheduler/mod.rs
  • magicblock-processor/src/scheduler/coordinator.rs
  • magicblock-processor/src/scheduler/locks.rs
📚 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-processor/src/scheduler/mod.rs
  • test-kit/src/lib.rs
  • magicblock-processor/src/scheduler/locks.rs
📚 Learning: 2025-10-28T13:15:42.706Z
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 596
File: magicblock-processor/src/scheduler.rs:1-1
Timestamp: 2025-10-28T13:15:42.706Z
Learning: In magicblock-processor, transaction indexes were always set to 0 even before the changes in PR #596. The proper transaction indexing within slots will be addressed during the planned ledger rewrite.

Applied to files:

  • magicblock-processor/src/scheduler/mod.rs
  • magicblock-api/src/magic_validator.rs
  • magicblock-processor/src/scheduler/coordinator.rs
  • magicblock-processor/src/scheduler/locks.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-processor/src/scheduler/mod.rs
  • magicblock-processor/tests/scheduling.rs
🧬 Code graph analysis (11)
magicblock-processor/src/scheduler/tests.rs (1)
magicblock-processor/src/scheduler/coordinator.rs (5)
  • new (32-37)
  • new (59-68)
  • try_acquire_locks (147-213)
  • get_ready_executor (115-117)
  • next_blocked_transaction (135-140)
magicblock-aperture/tests/node.rs (1)
test-kit/src/lib.rs (1)
  • get_payer (321-327)
magicblock-aperture/src/lib.rs (1)
magicblock-aperture/src/processor.rs (1)
  • start (73-83)
magicblock-processor/src/executor/mod.rs (1)
magicblock-committor-service/src/service.rs (1)
  • try_send (327-340)
magicblock-processor/src/scheduler/mod.rs (3)
magicblock-core/src/link.rs (1)
  • link (56-82)
magicblock-processor/src/executor/mod.rs (4)
  • new (66-107)
  • spawn (131-142)
  • run (156-196)
  • transition_to_new_slot (200-205)
magicblock-processor/src/scheduler/coordinator.rs (3)
  • new (32-37)
  • new (59-68)
  • is_ready (109-111)
magicblock-aperture/tests/mocked.rs (1)
test-kit/src/lib.rs (1)
  • get_payer (321-327)
magicblock-processor/tests/scheduling.rs (2)
test-kit/src/lib.rs (4)
  • new_with_config (99-160)
  • new (87-89)
  • get_transaction (208-216)
  • get_account (309-319)
magicblock-processor/src/scheduler/mod.rs (1)
  • new (58-91)
magicblock-api/src/magic_validator.rs (2)
magicblock-processor/src/scheduler/coordinator.rs (2)
  • new (32-37)
  • new (59-68)
magicblock-processor/src/scheduler/mod.rs (1)
  • new (58-91)
test-kit/src/lib.rs (2)
magicblock-aperture/tests/setup.rs (1)
  • new (83-138)
magicblock-processor/src/scheduler/mod.rs (1)
  • new (58-91)
magicblock-processor/tests/fees.rs (1)
test-kit/src/lib.rs (2)
  • get_payer (321-327)
  • new_with_config (99-160)
magicblock-processor/src/scheduler/coordinator.rs (3)
magicblock-processor/src/scheduler/locks.rs (1)
  • next_transaction_id (98-109)
magicblock-processor/src/executor/mod.rs (1)
  • new (66-107)
magicblock-processor/src/scheduler/mod.rs (1)
  • new (58-91)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: Integration Tests - task-scheduler
  • GitHub Check: Integration Tests - cloning
  • GitHub Check: Integration Tests - schedule_intents
  • GitHub Check: Integration Tests - restore_ledger
  • GitHub Check: Integration Tests - table_mania
  • GitHub Check: Integration Tests - magicblock_api
  • GitHub Check: Integration Tests - config
  • GitHub Check: Integration Tests - committor
  • GitHub Check: Integration Tests - schedulecommit
🔇 Additional comments (8)
magicblock-aperture/src/lib.rs (1)

35-37: LGTM! EventProcessor scaling aligns with multi-executor architecture.

The increase from 1 to 2 EventProcessor instances is consistent with the PR's goal of improving throughput via parallel execution. The comment correctly reflects the new startup count.

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

305-313: LGTM! Dynamic executor count computed correctly.

The formula (num_cpus::get() / 2).saturating_sub(1).max(1) properly accounts for:

  • Dedicating half the CPU resources to execution
  • Reserving one thread for the scheduler (per learnings)
  • Ensuring at least 1 executor via .max(1)
  • Preventing underflow via .saturating_sub(1)

Note that on systems with 2-4 cores, this yields only 1 executor, effectively matching the previous behavior. The logging helps operators understand the configured parallelism level.

Based on learnings

test-kit/src/lib.rs (3)

99-166: LGTM! Multi-payer and deferred startup design is sound.

The implementation correctly:

  • Creates one payer per executor for parallel transaction submission
  • Uses deferred startup to allow tests to configure state before the scheduler runs
  • Funds all payers during initialization
  • Ensures run_scheduler() can only spawn once via Option::take()

The new new_with_config API provides good flexibility for tests with varying executor counts and startup requirements.


250-260: Verify payer indexing consistency with multi-executor tests.

There's a potential indexing mismatch between build_transaction and get_payer:

  • build_transaction uses fetch_add(1, ...) which returns the old index before incrementing
  • get_payer uses load(...) which returns the current (already incremented) index

This means after calling build_transaction, get_payer() returns the next payer to be used, not the one that was just used. For tests that verify the fee payer's balance after execution, this could cause failures.

Currently, all tests use executors=1, so payers[i % 1] always returns payers[0], masking this issue. With executors > 1, tests expecting get_payer() to return the last-used payer will break.

Consider either:

  1. Documenting that get_payer() returns the next payer (if intentional), or
  2. Adjusting the indexing so get_payer() returns the last-used payer, e.g.:
pub fn get_payer(&self) -> CommitableAccount {
    let payer = {
        let index = self.payer_index.load(Ordering::Relaxed);
        let last_used = if index == 0 { 0 } else { index - 1 };
        &self.payers[last_used % self.payers.len()]
    };
    self.get_account(payer.pubkey())
}

Please verify this behavior with tests using executors > 1.

Also applies to: 322-326


275-280: LGTM! schedule_transaction helper is straightforward.

The helper appropriately uses unwrap() for test environments where panicking on failure is acceptable behavior.

magicblock-processor/tests/fees.rs (1)

174-174: LGTM! Test API updates align with test-kit changes.

Both changes correctly migrate to the new test-kit API:

  • Line 174: Uses get_payer() accessor instead of direct field access
  • Line 270: Migrates to new_with_config(fee, executors, defer_startup) with explicit parameters

The tests continue to use executors=1, maintaining existing behavior while adopting the new API surface.

Also applies to: 270-270

magicblock-aperture/tests/setup.rs (1)

96-96: LGTM! Test setup migrates to new get_payer() accessor.

The change correctly adopts the new test-kit API for accessing the payer's public key.

magicblock-processor/tests/scheduling.rs (1)

585-733: Comprehensive scenario matrix appreciated.

Great coverage across executor counts and contention patterns—this suite should surface scheduler regressions quickly.

Copy link
Contributor

@taco-paco taco-paco left a comment

Choose a reason for hiding this comment

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

Couldn't find any critical bugs this run. Tho there're some performance related comments that we should address.

Overall, I think it is ready to be merged after addressing comments. Good job

As a side note for PR:

  • There're still cases when rescheduling isn't optimal, added case in one of the comments
  • We lack stealing mechanism that we should add(later)
  • Code complexity is very high, especially after accout_contention addition

// NOTE: the channel will always have enough capacity, since the executor was
// marked ready, which means that its transaction queue is currently empty.
let _ = self.executors[executor as usize].try_send(txn.txn).inspect_err(|e| {
error!("Executor {executor} has shutdown or crashed, should not be possible: {e}")
Copy link
Contributor

Choose a reason for hiding this comment

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

In case TrySendError::Full we need to ask @lucacillario to setup an alert

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it's going a bit overboard, the Full case will be caught in tests (via failing them due transaction not being executed, as it was dropped), since that can only happen if some logic error has crept in.

Copy link
Contributor

Choose a reason for hiding this comment

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

only happen if some logic error has crept in

Yeap, the idea is to know about this right away as this would mean a critical logic error that we missed and need to investigate

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well I guess we can alert on any ERROR log then, since those are meant to be catastrophic.

Copy link
Contributor

Choose a reason for hiding this comment

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

Not in our codebase at the moment :) But that is another topic already, we can take things slow

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I meant just for any log that has error log level (which is easy to detect), we can raise an alert, no changes to codebase are necessary.

fn contend_account(&mut self, acc: Pubkey, txn: TransactionId) {
let contenders = self.account_contention.entry(acc).or_default();
if let Err(index) = contenders.binary_search(&txn) {
contenders.insert(index, txn);
Copy link
Contributor

Choose a reason for hiding this comment

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

This takes O(log'n) for searching and then O(n) for insertion on each el-t.
VecDeque isn't suitable for the job.

We can wether use BinaryTree, or simply use HashMap

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The HashMap is not suitable, since it is an unordered DS. an efficient BinaryTree uses Vec under the hood anyway, BTreeSet is a bit slower than VecDeque due to the following reasons:

  1. The size of the queue is usually very small (less than 10 entries)
  2. The common insertion cases are to the end or start of the queue (those are O(1)), since transactions are ordered by their IDs which only ever goes up. And the cases when transaction hop queues and inserted in the middle are rare, even then VecDeque beats the BTreeSet due to small size of array.
  3. BTreeSet keeps large nodes (which are arrays essentially) and any insertion to them is still O(log N) + O(N), since the number is small, the Tree will have only one node usually.

The benchmarks (admittedly artificial) confirm this

Workload 1: Pop Smallest, Insert Largest (N=10)/BTreeSet
                        time:   [12.146 ns 12.149 ns 12.152 ns]

Workload 1: Pop Smallest, Insert Largest (N=10)/VecDeque (binary_search)
                        time:   [9.2427 ns 9.3330 ns 9.4225 ns]

Copy link
Contributor

@taco-paco taco-paco Nov 13, 2025

Choose a reason for hiding this comment

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

We mixing data structures with our implementation.
Insertion/removal in VecDeque is O(n)
Insertion/removal in BTree is O(log_n)
Hope that not up to debate

The HashMap is not suitable, since it is an unordered DS.

Not sure why its not suitable if we search by TransactionId. We don't necessarily require sorting in our case, you use it for searching more efficiently.

an efficient BinaryTree uses Vec under the hood
is still O(log N) + O(N)

it would be O(logn) + O(m), where m - is constant and small, and as we know O(c) - O(1). Hope we won't argue that with large Ns, insertions in BTree are cheaper than in vecdeque, with random access.

Now with regards to our implementation, while as I pointed here for clear_account_contention we indeed pop only from front, for contend_accounts its not obvious that we pop_back all the time. And from analysis of contend_accounts one would clearly see O(n) complexity.

We should:

  1. modify clear_account_contention to pop_front or not to pop at all, as its always the case.
  2. Have a gauge metric for executors load and blocked messages, metric for number of idle executors, would be useful anyway.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The ordering is needed, since we need to ensure the priority access to transactions which first registered contention, which is why HashMap is unacceptable. The N is always small (but I agree that metric wouldn't hurt), and the access is not random, since the elements are using incrementing counter (which is quite bad for BTree). In any case I don't see any significant performance gains by using BTree or other DS, VecDeque has an advantage of cache locality and is faster than more complex structures for small Ns. Let's not turn this into an unproductive argument. If you think that we can speed things up significantly for common case and provide some benchmarks, I'm happy to rewrite the implementation.

return;
};
if let Ok(index) = contenders.binary_search(&txn) {
contenders.remove(index);
Copy link
Contributor

Choose a reason for hiding this comment

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

This makes a function O(n) performance.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's not, since in almost every case the contender is removed from front of the queue which is O(1), the binary search is just future proofing the logic, so we don't accidentally remove wrong transaction from queue. So the whole operation is O(log N), where N is usually very small.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
magicblock-processor/src/scheduler/mod.rs (1)

11-21: Fail fast when an executor channel closes

Line [226] logs and continues after try_send fails, but the locks acquired in try_schedule stay recorded under that executor. If a worker panics or its channel closes, the transaction is dropped, the locks are never released, and every contending transaction will block forever—effectively wedging the scheduler. Please release the locks and treat the condition as a hard failure instead of soldiering on.

-use log::{error, info};
+use log::info;
 use magicblock_core::link::transactions::{
     ProcessableTransaction, TransactionToProcessRx,
 };
@@
-use tokio::{
-    runtime::Builder,
-    sync::mpsc::{channel, Receiver, Sender},
-};
+use tokio::{
+    runtime::Builder,
+    sync::mpsc::{channel, error::TrySendError, Receiver, Sender},
+};
@@
-        let txn = match self.coordinator.try_schedule(executor, txn) {
+        let txn = match self.coordinator.try_schedule(executor, txn) {
             Ok(txn) => txn,
             Err(blocker) => {
                 return Some(blocker);
             }
         };
-        // It's safe to ignore the result of the send operation. If the send fails,
-        // it means the executor's channel is closed, which only happens on shutdown.
-        // NOTE: the channel will always have enough capacity, since the executor was
-        // marked ready, which means that its transaction queue is currently empty.
-        let _ = self.executors[executor as usize].try_send(txn).inspect_err(|e| {
-            error!("Executor {executor} has shutdown or crashed, should not be possible: {e}")
-        });
-        None
+        match self.executors[executor as usize].try_send(txn) {
+            Ok(()) => None,
+            Err(TrySendError::Full(_)) => unreachable!(
+                "executor {executor} queue should be empty when scheduling"
+            ),
+            Err(TrySendError::Closed(_)) => {
+                self.coordinator.unlock_accounts(executor);
+                self.coordinator.release_executor(executor);
+                panic!(
+                    "executor {executor} channel closed while scheduling; locks released to prevent deadlock"
+                );
+            }
+        }

Also applies to: 222-228

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c54053c and 7bd51ab.

📒 Files selected for processing (6)
  • magicblock-processor/src/scheduler/coordinator.rs (1 hunks)
  • magicblock-processor/src/scheduler/locks.rs (1 hunks)
  • magicblock-processor/src/scheduler/mod.rs (1 hunks)
  • magicblock-processor/src/scheduler/tests.rs (1 hunks)
  • magicblock-processor/tests/scheduling.rs (1 hunks)
  • test-kit/src/lib.rs (9 hunks)
🧰 Additional context used
🧠 Learnings (11)
📓 Common learnings
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 589
File: magicblock-processor/src/scheduler/locks.rs:110-122
Timestamp: 2025-11-04T10:53:50.922Z
Learning: In magicblock-processor, the TransactionScheduler runs in a single, dedicated thread and will always remain single-threaded. The `next_transaction_id()` function in scheduler/locks.rs uses `unsafe static mut` which is safe given this architectural guarantee.
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 578
File: magicblock-accounts-db/src/lib.rs:63-72
Timestamp: 2025-10-21T10:34:59.140Z
Learning: In magicblock-validator, the AccountsDb "stop-the-world" synchronizer is managed at the processor/executor level, not at the AccountsDb API level. Transaction executors in magicblock-processor hold a read lock (sync.read()) for the duration of each slot and release it only at slot boundaries, ensuring all account writes happen under the read lock. Snapshot operations acquire a write lock, blocking until all executors release their read locks. This pattern ensures mutual exclusion between writes and snapshots without requiring read guards in AccountsDb write APIs.
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 596
File: magicblock-processor/src/scheduler.rs:1-1
Timestamp: 2025-10-28T13:15:42.706Z
Learning: In magicblock-processor, transaction indexes were always set to 0 even before the changes in PR #596. The proper transaction indexing within slots will be addressed during the planned ledger rewrite.
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.
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.
📚 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-processor/src/scheduler/coordinator.rs
  • magicblock-processor/tests/scheduling.rs
  • magicblock-processor/src/scheduler/tests.rs
  • test-kit/src/lib.rs
  • magicblock-processor/src/scheduler/locks.rs
  • magicblock-processor/src/scheduler/mod.rs
📚 Learning: 2025-11-04T10:53:50.922Z
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 589
File: magicblock-processor/src/scheduler/locks.rs:110-122
Timestamp: 2025-11-04T10:53:50.922Z
Learning: In magicblock-processor, the TransactionScheduler runs in a single, dedicated thread and will always remain single-threaded. The `next_transaction_id()` function in scheduler/locks.rs uses `unsafe static mut` which is safe given this architectural guarantee.

Applied to files:

  • magicblock-processor/src/scheduler/coordinator.rs
  • magicblock-processor/tests/scheduling.rs
  • magicblock-processor/src/scheduler/tests.rs
  • test-kit/src/lib.rs
  • magicblock-processor/src/scheduler/locks.rs
  • magicblock-processor/src/scheduler/mod.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-processor/src/scheduler/coordinator.rs
  • magicblock-processor/tests/scheduling.rs
  • magicblock-processor/src/scheduler/tests.rs
  • test-kit/src/lib.rs
  • magicblock-processor/src/scheduler/locks.rs
  • magicblock-processor/src/scheduler/mod.rs
📚 Learning: 2025-10-28T13:15:42.706Z
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 596
File: magicblock-processor/src/scheduler.rs:1-1
Timestamp: 2025-10-28T13:15:42.706Z
Learning: In magicblock-processor, transaction indexes were always set to 0 even before the changes in PR #596. The proper transaction indexing within slots will be addressed during the planned ledger rewrite.

Applied to files:

  • magicblock-processor/src/scheduler/coordinator.rs
  • test-kit/src/lib.rs
  • magicblock-processor/src/scheduler/locks.rs
  • magicblock-processor/src/scheduler/mod.rs
📚 Learning: 2025-10-21T10:34:59.140Z
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 578
File: magicblock-accounts-db/src/lib.rs:63-72
Timestamp: 2025-10-21T10:34:59.140Z
Learning: In magicblock-validator, the AccountsDb "stop-the-world" synchronizer is managed at the processor/executor level, not at the AccountsDb API level. Transaction executors in magicblock-processor hold a read lock (sync.read()) for the duration of each slot and release it only at slot boundaries, ensuring all account writes happen under the read lock. Snapshot operations acquire a write lock, blocking until all executors release their read locks. This pattern ensures mutual exclusion between writes and snapshots without requiring read guards in AccountsDb write APIs.

Applied to files:

  • magicblock-processor/src/scheduler/coordinator.rs
  • magicblock-processor/src/scheduler/locks.rs
  • magicblock-processor/src/scheduler/mod.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-processor/src/scheduler/coordinator.rs
  • magicblock-processor/tests/scheduling.rs
  • magicblock-processor/src/scheduler/mod.rs
📚 Learning: 2025-11-12T09:46:27.533Z
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.533Z
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.

Applied to files:

  • magicblock-processor/tests/scheduling.rs
  • magicblock-processor/src/scheduler/tests.rs
  • test-kit/src/lib.rs
  • magicblock-processor/src/scheduler/locks.rs
  • magicblock-processor/src/scheduler/mod.rs
📚 Learning: 2025-10-14T09:56:14.047Z
Learnt from: taco-paco
Repo: magicblock-labs/magicblock-validator PR: 564
File: test-integration/programs/flexi-counter/src/processor/call_handler.rs:122-125
Timestamp: 2025-10-14T09:56:14.047Z
Learning: The file test-integration/programs/flexi-counter/src/processor/call_handler.rs contains a test smart contract used for integration testing, not production code.

Applied to files:

  • magicblock-processor/tests/scheduling.rs
  • magicblock-processor/src/scheduler/tests.rs
📚 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:

  • test-kit/src/lib.rs
  • magicblock-processor/src/scheduler/locks.rs
  • magicblock-processor/src/scheduler/mod.rs
📚 Learning: 2025-11-07T14:20:31.457Z
Learnt from: thlorenz
Repo: magicblock-labs/magicblock-validator PR: 621
File: magicblock-chainlink/src/remote_account_provider/chain_pubsub_actor.rs:457-495
Timestamp: 2025-11-07T14:20:31.457Z
Learning: In magicblock-chainlink/src/remote_account_provider/chain_pubsub_client.rs, the unsubscribe closure returned by PubSubConnection::account_subscribe(...) resolves to () (unit), not a Result. Downstream code should not attempt to inspect an unsubscribe result and can optionally wrap it in a timeout to guard against hangs.

Applied to files:

  • magicblock-processor/src/scheduler/mod.rs
🧬 Code graph analysis (5)
magicblock-processor/src/scheduler/coordinator.rs (2)
magicblock-processor/src/scheduler/locks.rs (1)
  • next_transaction_id (106-117)
magicblock-processor/src/scheduler/mod.rs (1)
  • new (58-91)
magicblock-processor/tests/scheduling.rs (1)
test-kit/src/lib.rs (4)
  • new_with_config (99-160)
  • new (87-89)
  • get_transaction (208-216)
  • get_account (309-319)
magicblock-processor/src/scheduler/tests.rs (2)
magicblock-processor/src/scheduler/coordinator.rs (5)
  • new (32-37)
  • new (59-68)
  • try_acquire_locks (145-218)
  • get_ready_executor (113-115)
  • next_blocked_transaction (133-138)
magicblock-processor/src/scheduler/mod.rs (1)
  • new (58-91)
test-kit/src/lib.rs (2)
magicblock-processor/src/scheduler/mod.rs (1)
  • new (58-91)
magicblock-aperture/tests/setup.rs (1)
  • new (83-138)
magicblock-processor/src/scheduler/mod.rs (3)
magicblock-core/src/link.rs (1)
  • link (56-82)
magicblock-processor/src/scheduler/coordinator.rs (3)
  • new (32-37)
  • new (59-68)
  • is_ready (107-109)
magicblock-processor/src/executor/mod.rs (4)
  • new (66-107)
  • spawn (131-142)
  • run (156-196)
  • transition_to_new_slot (200-205)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build Project
  • GitHub Check: run_make_ci_test
  • GitHub Check: run_make_ci_lint

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
magicblock-processor/src/scheduler/mod.rs (1)

222-228: Fail fast when an executor channel closes

Logging and continuing on try_send failure silently drops the transaction while leaving all of its locks recorded against the executor. If a worker panics and its channel closes, the coordinator now believes the executor still holds those locks, so every contender will block forever and the scheduler wedges.

Treat this as a fatal invariant breach and unwind only after releasing the locks and putting the executor back into the ready pool:

-        let _ = self.executors[executor as usize].try_send(txn).inspect_err(|e| {
-            error!("Executor {executor} has shutdown or crashed, should not be possible: {e}")
-        });
+        self.executors[executor as usize]
+            .try_send(txn)
+            .unwrap_or_else(|e| {
+                error!(
+                    "executor {executor} channel closed while scheduling; cannot recover: {e}"
+                );
+                self.coordinator.unlock_accounts(executor);
+                self.coordinator.release_executor(executor);
+                panic!(
+                    "executor {executor} channel unexpectedly closed during scheduling"
+                );
+            });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c54053c and 908e788.

📒 Files selected for processing (6)
  • magicblock-processor/src/scheduler/coordinator.rs (1 hunks)
  • magicblock-processor/src/scheduler/locks.rs (1 hunks)
  • magicblock-processor/src/scheduler/mod.rs (1 hunks)
  • magicblock-processor/src/scheduler/tests.rs (1 hunks)
  • magicblock-processor/tests/scheduling.rs (1 hunks)
  • test-kit/src/lib.rs (9 hunks)
🧰 Additional context used
🧠 Learnings (11)
📓 Common learnings
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 589
File: magicblock-processor/src/scheduler/locks.rs:110-122
Timestamp: 2025-11-04T10:53:50.922Z
Learning: In magicblock-processor, the TransactionScheduler runs in a single, dedicated thread and will always remain single-threaded. The `next_transaction_id()` function in scheduler/locks.rs uses `unsafe static mut` which is safe given this architectural guarantee.
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 596
File: magicblock-processor/src/scheduler.rs:1-1
Timestamp: 2025-10-28T13:15:42.706Z
Learning: In magicblock-processor, transaction indexes were always set to 0 even before the changes in PR #596. The proper transaction indexing within slots will be addressed during the planned ledger rewrite.
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 578
File: magicblock-accounts-db/src/lib.rs:63-72
Timestamp: 2025-10-21T10:34:59.140Z
Learning: In magicblock-validator, the AccountsDb "stop-the-world" synchronizer is managed at the processor/executor level, not at the AccountsDb API level. Transaction executors in magicblock-processor hold a read lock (sync.read()) for the duration of each slot and release it only at slot boundaries, ensuring all account writes happen under the read lock. Snapshot operations acquire a write lock, blocking until all executors release their read locks. This pattern ensures mutual exclusion between writes and snapshots without requiring read guards in AccountsDb write APIs.
📚 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-processor/tests/scheduling.rs
  • magicblock-processor/src/scheduler/tests.rs
  • magicblock-processor/src/scheduler/coordinator.rs
  • test-kit/src/lib.rs
  • magicblock-processor/src/scheduler/locks.rs
  • magicblock-processor/src/scheduler/mod.rs
📚 Learning: 2025-10-14T09:56:14.047Z
Learnt from: taco-paco
Repo: magicblock-labs/magicblock-validator PR: 564
File: test-integration/programs/flexi-counter/src/processor/call_handler.rs:122-125
Timestamp: 2025-10-14T09:56:14.047Z
Learning: The file test-integration/programs/flexi-counter/src/processor/call_handler.rs contains a test smart contract used for integration testing, not production code.

Applied to files:

  • magicblock-processor/tests/scheduling.rs
  • magicblock-processor/src/scheduler/tests.rs
📚 Learning: 2025-11-04T10:53:50.922Z
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 589
File: magicblock-processor/src/scheduler/locks.rs:110-122
Timestamp: 2025-11-04T10:53:50.922Z
Learning: In magicblock-processor, the TransactionScheduler runs in a single, dedicated thread and will always remain single-threaded. The `next_transaction_id()` function in scheduler/locks.rs uses `unsafe static mut` which is safe given this architectural guarantee.

Applied to files:

  • magicblock-processor/tests/scheduling.rs
  • magicblock-processor/src/scheduler/tests.rs
  • magicblock-processor/src/scheduler/coordinator.rs
  • test-kit/src/lib.rs
  • magicblock-processor/src/scheduler/locks.rs
  • magicblock-processor/src/scheduler/mod.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-processor/tests/scheduling.rs
  • magicblock-processor/src/scheduler/coordinator.rs
  • magicblock-processor/src/scheduler/mod.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-processor/tests/scheduling.rs
  • magicblock-processor/src/scheduler/tests.rs
  • magicblock-processor/src/scheduler/coordinator.rs
  • test-kit/src/lib.rs
  • magicblock-processor/src/scheduler/locks.rs
  • magicblock-processor/src/scheduler/mod.rs
📚 Learning: 2025-11-12T09:46:27.533Z
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.533Z
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.

Applied to files:

  • magicblock-processor/tests/scheduling.rs
  • magicblock-processor/src/scheduler/tests.rs
  • test-kit/src/lib.rs
  • magicblock-processor/src/scheduler/locks.rs
  • magicblock-processor/src/scheduler/mod.rs
📚 Learning: 2025-10-28T13:15:42.706Z
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 596
File: magicblock-processor/src/scheduler.rs:1-1
Timestamp: 2025-10-28T13:15:42.706Z
Learning: In magicblock-processor, transaction indexes were always set to 0 even before the changes in PR #596. The proper transaction indexing within slots will be addressed during the planned ledger rewrite.

Applied to files:

  • magicblock-processor/src/scheduler/coordinator.rs
  • test-kit/src/lib.rs
  • magicblock-processor/src/scheduler/locks.rs
  • magicblock-processor/src/scheduler/mod.rs
📚 Learning: 2025-10-21T10:34:59.140Z
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 578
File: magicblock-accounts-db/src/lib.rs:63-72
Timestamp: 2025-10-21T10:34:59.140Z
Learning: In magicblock-validator, the AccountsDb "stop-the-world" synchronizer is managed at the processor/executor level, not at the AccountsDb API level. Transaction executors in magicblock-processor hold a read lock (sync.read()) for the duration of each slot and release it only at slot boundaries, ensuring all account writes happen under the read lock. Snapshot operations acquire a write lock, blocking until all executors release their read locks. This pattern ensures mutual exclusion between writes and snapshots without requiring read guards in AccountsDb write APIs.

Applied to files:

  • magicblock-processor/src/scheduler/coordinator.rs
  • magicblock-processor/src/scheduler/locks.rs
  • magicblock-processor/src/scheduler/mod.rs
📚 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:

  • test-kit/src/lib.rs
  • magicblock-processor/src/scheduler/locks.rs
  • magicblock-processor/src/scheduler/mod.rs
📚 Learning: 2025-11-07T14:20:31.457Z
Learnt from: thlorenz
Repo: magicblock-labs/magicblock-validator PR: 621
File: magicblock-chainlink/src/remote_account_provider/chain_pubsub_actor.rs:457-495
Timestamp: 2025-11-07T14:20:31.457Z
Learning: In magicblock-chainlink/src/remote_account_provider/chain_pubsub_client.rs, the unsubscribe closure returned by PubSubConnection::account_subscribe(...) resolves to () (unit), not a Result. Downstream code should not attempt to inspect an unsubscribe result and can optionally wrap it in a timeout to guard against hangs.

Applied to files:

  • magicblock-processor/src/scheduler/mod.rs
🧬 Code graph analysis (5)
magicblock-processor/tests/scheduling.rs (1)
test-kit/src/lib.rs (4)
  • new_with_config (99-160)
  • new (87-89)
  • get_transaction (208-216)
  • get_account (309-319)
magicblock-processor/src/scheduler/tests.rs (1)
magicblock-processor/src/scheduler/coordinator.rs (5)
  • new (32-37)
  • new (59-68)
  • try_acquire_locks (145-218)
  • get_ready_executor (113-115)
  • next_blocked_transaction (133-138)
magicblock-processor/src/scheduler/coordinator.rs (2)
magicblock-processor/src/scheduler/locks.rs (1)
  • next_transaction_id (106-117)
magicblock-processor/src/scheduler/mod.rs (1)
  • new (58-91)
test-kit/src/lib.rs (2)
magicblock-processor/src/scheduler/mod.rs (1)
  • new (58-91)
magicblock-aperture/tests/setup.rs (1)
  • new (83-138)
magicblock-processor/src/scheduler/mod.rs (3)
magicblock-core/src/link.rs (1)
  • link (56-82)
magicblock-processor/src/scheduler/coordinator.rs (3)
  • new (32-37)
  • new (59-68)
  • is_ready (107-109)
magicblock-processor/src/executor/mod.rs (4)
  • new (66-107)
  • spawn (131-142)
  • run (156-196)
  • transition_to_new_slot (200-205)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: run_make_ci_lint
  • GitHub Check: Build Project
  • GitHub Check: run_make_ci_test

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 908e788 and 0b90a54.

📒 Files selected for processing (2)
  • magicblock-metrics/src/metrics/mod.rs (2 hunks)
  • magicblock-processor/src/scheduler/coordinator.rs (1 hunks)
🧰 Additional context used
🧠 Learnings (8)
📓 Common learnings
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 589
File: magicblock-processor/src/scheduler/locks.rs:110-122
Timestamp: 2025-11-04T10:53:50.922Z
Learning: In magicblock-processor, the TransactionScheduler runs in a single, dedicated thread and will always remain single-threaded. The `next_transaction_id()` function in scheduler/locks.rs uses `unsafe static mut` which is safe given this architectural guarantee.
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 589
File: magicblock-processor/src/scheduler/locks.rs:64-102
Timestamp: 2025-11-13T09:38:43.788Z
Learning: In magicblock-processor's TransactionScheduler (scheduler/mod.rs line 59), the executor count is clamped to MAX_SVM_EXECUTORS (63) at initialization time, and executor IDs are assigned sequentially from 0 to count-1. This architectural guarantee ensures that executor IDs used in the bitmask-based AccountLock (scheduler/locks.rs) will always be within valid bounds for bit shifting operations, making runtime bounds checks unnecessary.
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 596
File: magicblock-processor/src/scheduler.rs:1-1
Timestamp: 2025-10-28T13:15:42.706Z
Learning: In magicblock-processor, transaction indexes were always set to 0 even before the changes in PR #596. The proper transaction indexing within slots will be addressed during the planned ledger rewrite.
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 578
File: magicblock-accounts-db/src/lib.rs:63-72
Timestamp: 2025-10-21T10:34:59.140Z
Learning: In magicblock-validator, the AccountsDb "stop-the-world" synchronizer is managed at the processor/executor level, not at the AccountsDb API level. Transaction executors in magicblock-processor hold a read lock (sync.read()) for the duration of each slot and release it only at slot boundaries, ensuring all account writes happen under the read lock. Snapshot operations acquire a write lock, blocking until all executors release their read locks. This pattern ensures mutual exclusion between writes and snapshots without requiring read guards in AccountsDb write APIs.
📚 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-metrics/src/metrics/mod.rs
  • magicblock-processor/src/scheduler/coordinator.rs
📚 Learning: 2025-11-13T09:38:43.788Z
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 589
File: magicblock-processor/src/scheduler/locks.rs:64-102
Timestamp: 2025-11-13T09:38:43.788Z
Learning: In magicblock-processor's TransactionScheduler (scheduler/mod.rs line 59), the executor count is clamped to MAX_SVM_EXECUTORS (63) at initialization time, and executor IDs are assigned sequentially from 0 to count-1. This architectural guarantee ensures that executor IDs used in the bitmask-based AccountLock (scheduler/locks.rs) will always be within valid bounds for bit shifting operations, making runtime bounds checks unnecessary.

Applied to files:

  • magicblock-processor/src/scheduler/coordinator.rs
📚 Learning: 2025-11-04T10:53:50.922Z
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 589
File: magicblock-processor/src/scheduler/locks.rs:110-122
Timestamp: 2025-11-04T10:53:50.922Z
Learning: In magicblock-processor, the TransactionScheduler runs in a single, dedicated thread and will always remain single-threaded. The `next_transaction_id()` function in scheduler/locks.rs uses `unsafe static mut` which is safe given this architectural guarantee.

Applied to files:

  • magicblock-processor/src/scheduler/coordinator.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-processor/src/scheduler/coordinator.rs
📚 Learning: 2025-10-28T13:15:42.706Z
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 596
File: magicblock-processor/src/scheduler.rs:1-1
Timestamp: 2025-10-28T13:15:42.706Z
Learning: In magicblock-processor, transaction indexes were always set to 0 even before the changes in PR #596. The proper transaction indexing within slots will be addressed during the planned ledger rewrite.

Applied to files:

  • magicblock-processor/src/scheduler/coordinator.rs
📚 Learning: 2025-10-21T10:34:59.140Z
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 578
File: magicblock-accounts-db/src/lib.rs:63-72
Timestamp: 2025-10-21T10:34:59.140Z
Learning: In magicblock-validator, the AccountsDb "stop-the-world" synchronizer is managed at the processor/executor level, not at the AccountsDb API level. Transaction executors in magicblock-processor hold a read lock (sync.read()) for the duration of each slot and release it only at slot boundaries, ensuring all account writes happen under the read lock. Snapshot operations acquire a write lock, blocking until all executors release their read locks. This pattern ensures mutual exclusion between writes and snapshots without requiring read guards in AccountsDb write APIs.

Applied to files:

  • magicblock-processor/src/scheduler/coordinator.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-processor/src/scheduler/coordinator.rs
🧬 Code graph analysis (1)
magicblock-processor/src/scheduler/coordinator.rs (2)
magicblock-processor/src/scheduler/locks.rs (1)
  • next_transaction_id (106-117)
magicblock-processor/src/scheduler/mod.rs (1)
  • new (58-91)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: run_make_ci_test
  • GitHub Check: Build Project
  • GitHub Check: run_make_ci_lint
🔇 Additional comments (6)
magicblock-metrics/src/metrics/mod.rs (1)

262-262: LGTM!

The metric registration follows the correct pattern and is properly placed in the registration block.

magicblock-processor/src/scheduler/coordinator.rs (5)

25-39: LGTM: Clean wrapper with centralized ID generation.

The TransactionWithId struct provides a simple, clear wrapper that pairs transactions with unique IDs. The use of next_transaction_id() is appropriate and safe given the single-threaded scheduler architecture.


58-69: LGTM: Proper initialization of coordinator state.

The constructor correctly initializes all data structures for the given executor count, starting with all executors in the ready pool. The initialization is clean and comprehensive.


71-106: LGTM: Robust transaction queuing with proper ordering.

The method correctly resolves blockers and maintains sorted transaction queues. The use of .expect() on line 94 is appropriate as this indicates a hard logic error that should fail fast. The binary search insertion prevents duplicates and maintains ordering by transaction ID, which is essential for fairness. Metrics are properly updated.


223-238: LGTM: Clean coordination wrapper.

The method properly orchestrates lock acquisition, handles failures by releasing the executor and queueing the transaction, and returns appropriate results for both success and failure cases.


240-263: LGTM: Proper contention tracking with cleanup.

The contention management methods correctly maintain sorted transaction ordering using binary search. The cleanup in clear_account_contention removes empty entries to prevent unbounded growth while maintaining slack capacity for performance, as per the established design decision.

Based on learnings.

Comment on lines +176 to +179
pub static ref MAX_LOCK_CONTENTION_QUEUE_SIZE: IntGauge = IntGauge::new(
"max_lock_contention_queue_size",
"Maximum observed queue size for an account lock contention"
).unwrap();
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider adding a helper function for atomic max-tracking.

The metric is correctly declared, but since it tracks a "maximum observed" value, consider adding a helper function to encapsulate the compare-and-set logic. This would ensure consistent usage across the codebase and follow the pattern of other IntGauge metrics in this file.

Add a helper function at the end of the file:

pub fn update_max_lock_contention_queue_size(size: i64) {
    let current = MAX_LOCK_CONTENTION_QUEUE_SIZE.get();
    if size > current {
        MAX_LOCK_CONTENTION_QUEUE_SIZE.set(size);
    }
}
🤖 Prompt for AI Agents
In magicblock-metrics/src/metrics/mod.rs around lines 176 to 179, add a helper
function to atomically update the "maximum observed" gauge so callers don't
duplicate compare-and-set logic; implement a public function (e.g.,
update_max_lock_contention_queue_size) that reads
MAX_LOCK_CONTENTION_QUEUE_SIZE.get(), compares the provided size, and only calls
MAX_LOCK_CONTENTION_QUEUE_SIZE.set(size) when the new size is greater than the
current value, placing the function near the other IntGauge helpers at the end
of the file for consistent usage.

Comment on lines +180 to +182



Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Remove excessive blank lines.

Three consecutive blank lines are inconsistent with the spacing pattern elsewhere in this file. Most sections use 0-1 blank lines between declarations.

-
-
-
+
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
🤖 Prompt for AI Agents
In magicblock-metrics/src/metrics/mod.rs around lines 180 to 182 there are three
consecutive blank lines; remove the excess blank lines and leave at most one
blank line to match the file's spacing convention (or run rustfmt to enforce
consistent formatting), ensuring declarations above and below remain separated
by a single blank line.

Comment on lines +153 to +221
let message = transaction.txn.transaction.message();
let accounts_to_lock = message.account_keys().iter().enumerate();
let acquired_locks = &mut self.acquired_locks[executor as usize];

for (i, &acc) in accounts_to_lock.clone() {
// Get or create the lock for the account.
let lock = self.locks.entry(acc).or_default().clone();
// See whether there's a contention for the given account
// if there's one, then we need to follow the breadcrumbs
// (txns->executor) to find out where our transaction
// needs to be queued.
let mut result =
if let Some(contenders) = self.account_contention.get(&acc) {
match contenders.binary_search(&transaction.id) {
// If we are the first contender, then we can proceed
// and try to acquire the needed account locks
// 1. Ok(0) => the transaction has already contended this account,
// and now it is its turn to be scheduled
// 2. Err(0) => the transaction didn't contend the account, but it
// has smaller ID (was received earlier), so we don't
// block it and let it proceed with lock acquisition
Ok(0) | Err(0) => Ok(()),
// If we are not, then we need to get queued after
// the transaction contending right in front of us
Ok(index) | Err(index) => {
Err(BlockerId::Transaction(contenders[index - 1]))
}
}
} else {
Ok(())
};

if result.is_ok() {
// Attempt to acquire a write or read lock.
result = if message.is_writable(i) {
lock.borrow_mut().write(executor)
} else {
lock.borrow_mut().read(executor)
}
.map_err(BlockerId::Executor);
}

// We couldn't lock all of the accounts, so we are bailing, but
// first we need to set contention, and unlock successful locks
if let Err(e) = result {
for lock in acquired_locks.drain(..) {
let mut lock = lock.borrow_mut();
lock.unlock(executor);
}
for (i, &acc) in accounts_to_lock {
// We only set contention for write locks,
// in order to prevent writer starvation
if message.is_writable(i) {
self.contend_account(acc, transaction.id);
}
}
return Err(e);
}

acquired_locks.push(lock);
}

// On success, the transaction is no longer blocking anything.
self.transaction_contention.remove(&transaction.id);
for (_, acc) in accounts_to_lock {
self.clear_account_contention(acc, transaction.id);
}
Ok(())
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider refactoring the multi-pass iteration pattern for clarity.

The method clones the accounts_to_lock iterator on line 157 to enable reuse on lines 202 and 217. While this works correctly (since Enumerate<Iter<&Pubkey>> implements Clone), the pattern is unusual and may reduce maintainability.

Consider collecting the accounts once and iterating over the collection:

     let message = transaction.txn.transaction.message();
-    let accounts_to_lock = message.account_keys().iter().enumerate();
+    let accounts_to_lock: Vec<_> = message.account_keys().iter().enumerate().collect();
     let acquired_locks = &mut self.acquired_locks[executor as usize];

-    for (i, &acc) in accounts_to_lock.clone() {
+    for &(i, acc) in &accounts_to_lock {
         // ... (rest remains the same)
-            for lock in acquired_locks.drain(..) {
-                let mut lock = lock.borrow_mut();
-                lock.unlock(executor);
-            }
-            for (i, &acc) in accounts_to_lock {
+            // Cleanup code...
+            for &(i, acc) in &accounts_to_lock {
                 // We only set contention for write locks,
                 // in order to prevent writer starvation
                 if message.is_writable(i) {
@@ -214,7 +214,7 @@
 
     // On success, the transaction is no longer blocking anything.
     self.transaction_contention.remove(&transaction.id);
-    for (_, acc) in accounts_to_lock {
+    for (_, acc) in &accounts_to_lock {
         self.clear_account_contention(acc, transaction.id);
     }

This makes the multi-pass iteration explicit and more idiomatic. The performance impact is negligible since account lists are typically small.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let message = transaction.txn.transaction.message();
let accounts_to_lock = message.account_keys().iter().enumerate();
let acquired_locks = &mut self.acquired_locks[executor as usize];
for (i, &acc) in accounts_to_lock.clone() {
// Get or create the lock for the account.
let lock = self.locks.entry(acc).or_default().clone();
// See whether there's a contention for the given account
// if there's one, then we need to follow the breadcrumbs
// (txns->executor) to find out where our transaction
// needs to be queued.
let mut result =
if let Some(contenders) = self.account_contention.get(&acc) {
match contenders.binary_search(&transaction.id) {
// If we are the first contender, then we can proceed
// and try to acquire the needed account locks
// 1. Ok(0) => the transaction has already contended this account,
// and now it is its turn to be scheduled
// 2. Err(0) => the transaction didn't contend the account, but it
// has smaller ID (was received earlier), so we don't
// block it and let it proceed with lock acquisition
Ok(0) | Err(0) => Ok(()),
// If we are not, then we need to get queued after
// the transaction contending right in front of us
Ok(index) | Err(index) => {
Err(BlockerId::Transaction(contenders[index - 1]))
}
}
} else {
Ok(())
};
if result.is_ok() {
// Attempt to acquire a write or read lock.
result = if message.is_writable(i) {
lock.borrow_mut().write(executor)
} else {
lock.borrow_mut().read(executor)
}
.map_err(BlockerId::Executor);
}
// We couldn't lock all of the accounts, so we are bailing, but
// first we need to set contention, and unlock successful locks
if let Err(e) = result {
for lock in acquired_locks.drain(..) {
let mut lock = lock.borrow_mut();
lock.unlock(executor);
}
for (i, &acc) in accounts_to_lock {
// We only set contention for write locks,
// in order to prevent writer starvation
if message.is_writable(i) {
self.contend_account(acc, transaction.id);
}
}
return Err(e);
}
acquired_locks.push(lock);
}
// On success, the transaction is no longer blocking anything.
self.transaction_contention.remove(&transaction.id);
for (_, acc) in accounts_to_lock {
self.clear_account_contention(acc, transaction.id);
}
Ok(())
}
let message = transaction.txn.transaction.message();
let accounts_to_lock: Vec<_> = message.account_keys().iter().enumerate().collect();
let acquired_locks = &mut self.acquired_locks[executor as usize];
for &(i, acc) in &accounts_to_lock {
// Get or create the lock for the account.
let lock = self.locks.entry(acc).or_default().clone();
// See whether there's a contention for the given account
// if there's one, then we need to follow the breadcrumbs
// (txns->executor) to find out where our transaction
// needs to be queued.
let mut result =
if let Some(contenders) = self.account_contention.get(&acc) {
match contenders.binary_search(&transaction.id) {
// If we are the first contender, then we can proceed
// and try to acquire the needed account locks
// 1. Ok(0) => the transaction has already contended this account,
// and now it is its turn to be scheduled
// 2. Err(0) => the transaction didn't contend the account, but it
// has smaller ID (was received earlier), so we don't
// block it and let it proceed with lock acquisition
Ok(0) | Err(0) => Ok(()),
// If we are not, then we need to get queued after
// the transaction contending right in front of us
Ok(index) | Err(index) => {
Err(BlockerId::Transaction(contenders[index - 1]))
}
}
} else {
Ok(())
};
if result.is_ok() {
// Attempt to acquire a write or read lock.
result = if message.is_writable(i) {
lock.borrow_mut().write(executor)
} else {
lock.borrow_mut().read(executor)
}
.map_err(BlockerId::Executor);
}
// We couldn't lock all of the accounts, so we are bailing, but
// first we need to set contention, and unlock successful locks
if let Err(e) = result {
for lock in acquired_locks.drain(..) {
let mut lock = lock.borrow_mut();
lock.unlock(executor);
}
for &(i, acc) in &accounts_to_lock {
// We only set contention for write locks,
// in order to prevent writer starvation
if message.is_writable(i) {
self.contend_account(acc, transaction.id);
}
}
return Err(e);
}
acquired_locks.push(lock);
}
// On success, the transaction is no longer blocking anything.
self.transaction_contention.remove(&transaction.id);
for (_, acc) in &accounts_to_lock {
self.clear_account_contention(acc, transaction.id);
}
Ok(())
🤖 Prompt for AI Agents
In magicblock-processor/src/scheduler/coordinator.rs around lines 153 to 221,
the code relies on cloning the Enumerate iterator (accounts_to_lock.clone()) to
iterate multiple times, which is unusual; collect the enumerated account keys
into a small Vec once (preserving the (index, &Pubkey) pairs), then iterate over
that Vec in the three places currently using accounts_to_lock (the main
acquisition loop, the loop that sets contention on failure, and the final loop
that clears contention), ensuring you keep the index for message.is_writable(i)
checks and adjust types accordingly.

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.

6 participants