Skip to content

apollo_batcher: acquire view call semaphore permit before storage reads - #14987

Merged
asaf-sw merged 1 commit into
main-v0.14.3from
claude/perf/apollo_batcher-early-semaphore-check-48213
Aug 19, 2026
Merged

apollo_batcher: acquire view call semaphore permit before storage reads#14987
asaf-sw merged 1 commit into
main-v0.14.3from
claude/perf/apollo_batcher-early-semaphore-check-48213

Conversation

@gkaempfer

Copy link
Copy Markdown
Contributor

Summary

Follow-up performance optimization on #14972, which added view_call_semaphore (capacity MAX_CONCURRENT_VIEW_CALLS = 32) to Batcher::call_contract to bound how many view calls can occupy tokio blocking-pool threads at once.

The merged code acquired the semaphore permit (try_acquire_owned()) after two storage reads (get_height_from_storage, get_block_info) and after constructing the view state reader and BlockContext (which clones a storage reader, wraps a class-manager client, and boxes a StateReaderAndContractManager).

This moves the permit acquisition to the top of call_contract, before any of that setup work.

Why

The semaphore exists specifically to protect the node when view calls are piling up under load. In exactly that scenario — the slots are exhausted and the call is going to be rejected anyway — the old ordering still paid for two storage reads (DB transactions) and non-trivial allocation work for a request whose result is thrown away. Checking the permit first makes the rejection path near-free, avoiding wasted I/O and allocation precisely when the system is already resource-constrained.

No other behavior changes: on the success path the permit still moves into the spawn_blocking closure and is held until the blocking task ends, per #14972's original guarantee. The only observable difference is that if the semaphore is exhausted and a storage read would have separately failed, the caller now sees the more informative ContractCallFailed { "Too many concurrent contract calls..." } instead of InternalError — a strictly better error for a retryable backpressure condition.

Testing

  • SEED=0 cargo test -p apollo_batcher: 132 passed, 0 failed (including call_contract_rejected_when_all_view_call_slots_are_taken and view_call_slot_is_freed_only_when_the_blocking_task_ends).
  • Reviewed by an independent agent for correctness (permit lifetime/drop semantics, test coverage, error precedence) — no blocking issues found.

Generated by Claude Code

Move the view_call_semaphore try_acquire_owned() check in
Batcher::call_contract to the top of the function, before the storage
reads (get_height_from_storage, get_block_info) and view state reader
construction. Under load, when the semaphore is exhausted (the exact
scenario it exists to protect against), a rejected call previously
still paid for two storage reads and non-trivial allocation work for a
request that gets discarded anyway. Checking the permit first makes
the rejection path near-free.
@cursor

cursor Bot commented Aug 18, 2026

Copy link
Copy Markdown

PR Summary

Low Risk
Single reorder inside call_contract with no change to permit lifetime or successful execution path; improves rejection behavior under load.

Overview
Batcher::call_contract now takes a view_call_semaphore permit at the very start, before get_height_from_storage, get_block_info, view state reader construction, and BlockContext setup.

When all MAX_CONCURRENT_VIEW_CALLS slots are busy, rejections no longer pay for storage I/O or allocation on the backpressure path. Success-path behavior is unchanged: the permit still moves into spawn_blocking and is held until the blocking task finishes.

If the semaphore is full and storage would also fail, callers now get ContractCallFailed with the “too many concurrent contract calls” reason instead of InternalError.

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

@reviewable-StarkWare

Copy link
Copy Markdown

This change is Reviewable

@asaf-sw asaf-sw left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@asaf-sw reviewed 1 file and all commit messages.
Reviewable status: :shipit: complete! all files reviewed, all discussions resolved (waiting on gkaempfer).

@asaf-sw
asaf-sw added this pull request to the merge queue Aug 19, 2026
Merged via the queue into main-v0.14.3 with commit 482fdf4 Aug 19, 2026
18 checks passed

Copy link
Copy Markdown
Contributor Author

Security scan complete — no issues detected.


Generated by Claude Code

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.

4 participants