Skip to content

fix(rs-dapi): keep the Core RPC permit until the call returns - #5507

Merged
QuantumExplorer merged 4 commits into
v5.0-devfrom
claude/cancelled-dapi-core-rpc-6711dc
Oct 21, 2026
Merged

QuantumExplorer merged 4 commits into
v5.0-devfrom
claude/cancelled-dapi-core-rpc-6711dc

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Oct 20, 2026 •

Copy link
Copy Markdown
Member

Basic explanation

What this does: DAPI lets at most two calls to Dash Core run at once and makes every other call wait its turn. Until now a call gave up its turn as soon as the request that started it went away (the client hung up or the request timed out), or after 10 seconds, even though the call was still running on Core. With this change a call keeps its turn until Core answers.

Value: the "two at a time" limit now really holds. Core-backed endpoints (getBlock, getTransaction, broadcastTransaction, getBlockchainStatus and others) can no longer build up a backlog of leftover Core calls that delays requests from everyone else.

Risks: Low. The change is limited to rs-dapi's Core client and has no consensus impact. One behaviour changes: a request waiting for its turn now times out after 30 seconds in total (waiting plus running), where before the wait had no limit. This only shows up when Core is already slow. Calls were already serialized on a single Core connection, so throughput when Core is healthy does not change.

Issue being fixed or feature implemented

CoreClient::guarded_blocking_call held the concurrency permit in the caller's future and ran the Core call in spawn_blocking. A blocking call cannot be stopped, so dropping the caller (gRPC cancellation, the outer REQUEST_TIMEOUT, or the 10 s PERMIT_TIMEOUT release) freed the permit while the call kept running. Each new request could then take a permit and start another blocking call. These calls queued on the transport's socket mutex and used blocking-pool threads, with no limit tied to CORE_RPC_GUARD_PERMITS.

Before, with two permits:

request A takes a permit, starts a Core call, is cancelled  -> permit free, call A still running
request B takes a permit, starts a Core call, is cancelled  -> permit free, call B still running
request C ...                                               -> 3+ Core calls in flight

After:

request A takes a permit, starts a Core call, is cancelled  -> call A keeps the permit until Core answers
request B takes a permit, starts a Core call, is cancelled  -> call B keeps the permit
request C waits for a permit (or times out at 30 s)          -> never more than 2 Core calls in flight

What was done?

  • The OwnedSemaphorePermit moves into the spawn_blocking closure, so it is dropped exactly when the Core call returns.
  • One REQUEST_TIMEOUT deadline (timeout_at, passed to guarded_blocking_call_until) covers both the permit wait and the call. A caller that hits it gets the same DapiError::Timeout as before. The call itself finishes in the background and is bounded by the jsonrpc transport's 15 s socket timeouts.
  • Removed PERMIT_TIMEOUT and the spawn + mpsc + select! loop. Releasing the permit early did not raise throughput, because jsonrpc's SimpleHttpTransport already sends one request at a time over a single socket behind a mutex.

How Has This Been Tested?

  • New clients::core_client::tests::should_keep_permits_until_cancelled_calls_return: starts two calls that block, cancels both callers, checks that no permit is free while the calls still run, releases them, and checks that the permits come back and a new call succeeds. Against the old implementation it fails with left: 2, right: 0 (both permits free while both calls still run).
  • New should_time_out_a_call_still_waiting_for_a_permit_at_its_deadline and should_time_out_a_running_call_at_the_same_deadline_and_keep_its_permit (through the private guarded_blocking_call_until, which takes the deadline so tests can use a short one): a call queued behind busy permits times out at its deadline without running, and a running call times out at the same deadline while its operation keeps the permit until it returns. Each fails if its half of the shared deadline is removed (fresh REQUEST_TIMEOUT for the call stage, or no limit on the permit wait).
  • cargo test -p rs-dapi --lib clients (16 passed) and cargo test -p rs-dapi --lib core_service (3 passed).
  • cargo clippy -p rs-dapi --all-targets: clean for rs-dapi. cargo fmt --all.

Breaking Changes

None.

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have added "!" to the title and described breaking changes in the corresponding section if my code contains any
  • I have made corresponding changes to the documentation if needed
  • If I added or changed GroveDB structure, I described it in the area's structure.rs, regenerated grovedb-structure.json, and checked the structure viewer link posted on this pull request

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

🤖 Generated with Claude Code

PR Hygiene · abf2344

  • Bots — coderabbitai ✓ · thepastaclaw ✓
  • Self-review — post /self-reviewed
  • Build green
  • Approvals
    • rust-dapi (packages/rs-dapi/src/clients/core_client.rs) — lklimek or shumkov

When every merge requirement is met, the PR Hygiene check passes. Reviewer limits do not block merging; other required GitHub checks and protections still apply.

Summary by CodeRabbit

  • Bug Fixes
    • Improved timeout handling for core operations by applying a consistent deadline across resource acquisition and execution.
    • Ensured operations already in progress continue safely after a caller timeout or cancellation.
    • Improved behavior when system capacity is temporarily unavailable, returning timeouts more predictably.

guarded_blocking_call held the concurrency permit in the caller's future.
When a gRPC request was cancelled, or after the 10 s permit timeout, the
permit was freed while the spawn_blocking Core call kept running, so the
two-permit limit did not bound the number of Core calls in flight.

The permit now moves into the blocking task and is dropped when the call
returns. One REQUEST_TIMEOUT deadline covers both the permit wait and the
call, so a queued caller gets a timeout instead of waiting without limit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added this to the v5.0.0 milestone Oct 20, 2026
@coderabbitai

coderabbitai Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 20 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Repository: dashpay/platform/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 015c7c71-8358-4f81-adb1-6c3f37da0ebd
📥 Commits

Reviewing files that changed from the base of the PR and between c212ae4 and abf2344.

📒 Files selected for processing: 1
  • packages/rs-dapi/src/clients/core_client.rs

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: dashpay/platform/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: c2712716-fd2f-4d3f-9ffc-b9c819ee6d44
📥 Commits

Reviewing files that changed from the base of the PR and between 8d903eb and c212ae4.

📒 Files selected for processing: 1
  • packages/rs-dapi/src/clients/core_client.rs

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

📝 Walkthrough

Core RPC calls now use one deadline for permit acquisition and blocking execution. The blocking task owns the permit, so a caller timeout does not stop an operation that has started or release its permit early. Tests cover queued calls, running calls, and permit retention.

Changes: Core RPC Timeout Handling

Layer / File(s) Summary
Shared deadline and permit lifecycle
packages/rs-dapi/src/clients/core_client.rs
Permit acquisition and RPC execution use the same deadline. The blocking task retains the permit until its operation completes, even if the caller times out. Tests cover timeout while waiting, timeout during execution, and permit retention after caller cancellation.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant guarded_blocking_call_until
  participant Semaphore
  participant BlockingTask
  Caller->>guarded_blocking_call_until: Call with shared deadline
  guarded_blocking_call_until->>Semaphore: Acquire permit before deadline
  Semaphore-->>guarded_blocking_call_until: Return owned permit
  guarded_blocking_call_until->>BlockingTask: Start Core RPC with permit
  guarded_blocking_call_until-->>Caller: Return timeout if deadline expires
  BlockingTask->>Semaphore: Release permit when RPC completes
Loading

Suggested reviewers: lklimek

Merge Risk: ⚪ Minimal · up to c212a

Core RPC calls now keep their concurrency slot until the underlying Core call finishes, so cancelled or timed-out requests can no longer pile up past the two-call limit. A single 30-second deadline now covers both waiting for a slot and running the call, and tests cover both cases. No outstanding issues block merging.

Pre-merge checks: Passed 5
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly summarizes the main change: Core RPC permits remain held until the underlying call returns.
Docstring Coverage Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 10 functions across 1 files.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

❤️ Share

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

@github-actions

github-actions Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Waiting for bot review — coderabbitai not yet · thepastaclaw not yet. Wait for the missing reviews, or a writer can post /skip-bots to proceed without them; blocking findings still need addressing.
Full checklist in the description.

@github-actions github-actions Bot added the waiting-bots Waiting for the review bots to report on this head label Oct 20, 2026
@thepastaclaw

thepastaclaw commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

✅ Final review complete — no blockers (commit abf2344) · triage: normal

@github-actions

github-actions Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Waiting for bot review — coderabbitai not yet · thepastaclaw not yet. Wait for the missing reviews, or a writer can post /skip-bots to proceed without them; blocking findings still need addressing.
Full checklist in the description.

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Final validation — Phase 1 + Phase 2

The Core RPC permit is now correctly owned by the blocking task, so caller cancellation cannot release capacity while the underlying operation is still running. The focused cancellation test covers the primary regression, but the newly introduced shared 30-second deadline and timeout error paths remain untested. The Rust workspace test check was still in progress at review time.

🟡 1 suggestion(s)

Review provenance

Source: reviewer 1: muse-spark-1.3-contributor (agent: phase1-reviewer, role: general); reviewer 2: muse-spark-1.3-contributor (agent: phase1-reviewer, role: rust-quality); reviewer 3: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 4: gpt-6.1-sol (agent: phase2-reviewer, role: architecture-layering); reviewer 5: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); reviewer 6: gpt-6.1-sol (agent: phase2-reviewer, role: security-auditor); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)

  • Triage: normal by gpt-6.1-sol (effort low) — This is a contained but behavior-changing asynchronous concurrency fix whose permit ownership, cancellation, timeout, and background task lifetimes require more than a straightforward low-effort check, without affecting a critical surface.
  • Phase 1 reviewers: muse-spark-1.3-contributor — general (completed, effort xhigh); agent phase1-reviewer, muse-spark-1.3-contributor — rust-quality (completed, effort xhigh); agent phase1-reviewer
  • Phase 1 model: muse-spark-1.3-contributor — not quota-gated; passed over gemini-3.8-flash-high (lane failed), glm-5.3-flash (not used above high effort; tier asks max)
  • Fresh verifier: gpt-6.1-sol — final-verifier; agent sol-verifier
  • Phase 2 reviewers: gpt-6.1-sol — general (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — architecture-layering (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — rust-quality (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — security-auditor (completed, effort high); agent phase2-reviewer
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `packages/rs-dapi/src/clients/core_client.rs`:
- [SUGGESTION] packages/rs-dapi/src/clients/core_client.rs:53-73: Cover the shared deadline timeout paths
  The new cancellation test verifies that permits remain held until detached blocking calls return, but it does not exercise the other behavior introduced here: one deadline covers both semaphore acquisition and result waiting, and either expiry maps to `DapiError::Timeout`. A regression that applies separate 30-second waits, returns a different error while waiting for a permit, or mishandles the permit after the caller timeout would pass the current test suite. Add a private deadline-injectable helper or otherwise make the timeout duration controllable in tests, then cover both a request queued behind occupied permits and a caller timing out while its blocking operation remains active.

@github-actions

Copy link
Copy Markdown
Contributor

Bots are done — your move: post /self-reviewed.
Full checklist in the description.

@github-actions github-actions Bot added waiting-self-review Waiting for the author to post /self-reviewed bot-review-skipped A required review bot did not report; it was skipped by the window or by a person. and removed waiting-bots Waiting for the review bots to report on this head labels Oct 20, 2026
guarded_blocking_call now delegates to guarded_blocking_call_until, which
takes the deadline, so tests can use a short one. New tests check that a
call queued behind busy permits times out at its deadline without running,
and that a running call times out at the same deadline while its operation
keeps the permit until it returns.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Waiting for bot review — coderabbitai ✓ · thepastaclaw not yet. Wait for the missing reviews, or a writer can post /skip-bots to proceed without them; blocking findings still need addressing.
Full checklist in the description.

@github-actions github-actions Bot added waiting-bots Waiting for the review bots to report on this head and removed waiting-self-review Waiting for the author to post /self-reviewed bot-review-skipped A required review bot did not report; it was skipped by the window or by a person. labels Oct 20, 2026

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Re-review — Final validation — Phase 1 + Phase 2

At c212ae4, the permit correctly remains owned by the blocking operation after caller cancellation or timeout, and both waiting stages use one deadline. The added tests address the prior timeout-coverage finding; static verification found no actionable in-scope issues. The current Rust workspace tests check failed in the unchanged drive-abci contract-fee-claim activation test, which expects an internal error instead of the returned StateTransitionNotActive consensus error; this is unrelated to the Core RPC change, and no local builds or tests were run.

🔴 0 blocking | 🟡 0 suggestion(s) | 💬 0 nitpick(s)

Review provenance

Source: reviewer 1: muse-spark-1.3-contributor (agent: phase1-reviewer, role: general); reviewer 2: muse-spark-1.3-contributor (agent: phase1-reviewer, role: rust-quality); reviewer 3: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 4: gpt-6.1-sol (agent: phase2-reviewer, role: architecture-layering); reviewer 5: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); reviewer 6: gpt-6.1-sol (agent: phase2-reviewer, role: security-auditor); reviewer 7: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 8: gpt-6.1-sol (agent: phase2-reviewer, role: architecture-layering); reviewer 9: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); reviewer 10: gpt-6.1-sol (agent: phase2-reviewer, role: security-auditor); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)

  • Triage: normal by gpt-6.1-sol (effort low) — This is a contained asynchronous Rust concurrency change in rs-dapi that alters request timeout and semaphore-lifetime behavior, but it does not modify a critical surface such as consensus, funds, cryptography, or storage.
  • Phase 1 reviewers: muse-spark-1.3-contributor — general (completed, effort xhigh); agent phase1-reviewer, muse-spark-1.3-contributor — rust-quality (completed, effort xhigh); agent phase1-reviewer
  • Phase 1 model: muse-spark-1.3-contributor — not quota-gated; passed over gemini-3.8-flash-high (lane failed), glm-5.3-flash (not used above high effort; tier asks max)
  • Fresh final gate: an independent Phase-2 review ran after iterative findings were reconciled
  • Fresh verifier: gpt-6.1-sol — final-verifier; agent sol-verifier
  • Phase 2 reviewers: gpt-6.1-sol — general (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — architecture-layering (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — rust-quality (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — security-auditor (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — general (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — architecture-layering (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — rust-quality (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — security-auditor (completed, effort high); agent phase2-reviewer
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify the current code and confirm that no unresolved issues remain.

No unresolved findings remain from the prior review on this head.

@github-actions

Copy link
Copy Markdown
Contributor

Bots are done — your move: post /self-reviewed.
Full checklist in the description.

@github-actions github-actions Bot added waiting-self-review Waiting for the author to post /self-reviewed and removed waiting-bots Waiting for the review bots to report on this head labels Oct 20, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Bots are done — your move: post /self-reviewed.
Full checklist in the description.

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Re-review — Final validation — Phase 1 + Phase 2

Independently reviewed the complete diff at abf2344 and found no actionable defects. The blocking closure retains the semaphore permit after caller cancellation or timeout, both waiting stages use the same deadline, and the added tests address the prior coverage finding. Validation was static only; current CI reports successful Rust workspace tests and no failed code checks, with PR Hygiene still pending.

🔴 0 blocking | 🟡 0 suggestion(s) | 💬 0 nitpick(s)

Review provenance

Source: reviewer 1: muse-spark-1.3-contributor (agent: phase1-reviewer, role: general); reviewer 2: muse-spark-1.3-contributor (agent: phase1-reviewer, role: rust-quality); reviewer 3: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 4: gpt-6.1-sol (agent: phase2-reviewer, role: architecture-layering); reviewer 5: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); reviewer 6: gpt-6.1-sol (agent: phase2-reviewer, role: security-auditor); reviewer 7: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 8: gpt-6.1-sol (agent: phase2-reviewer, role: architecture-layering); reviewer 9: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); reviewer 10: gpt-6.1-sol (agent: phase2-reviewer, role: security-auditor); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)

  • Triage: normal by gpt-6.1-sol (effort low) — The contained change modifies cancellation-sensitive semaphore ownership and shared timeout behavior across asynchronous and blocking execution, requiring concurrency reasoning but not changing a critical surface.
  • Phase 1 reviewers: muse-spark-1.3-contributor — general (completed, effort xhigh); agent phase1-reviewer, muse-spark-1.3-contributor — rust-quality (completed, effort xhigh); agent phase1-reviewer
  • Phase 1 model: muse-spark-1.3-contributor — not quota-gated; passed over gemini-3.8-flash-high (lane failed), glm-5.3-flash (not used above high effort; tier asks max)
  • Fresh final gate: an independent Phase-2 review ran after iterative findings were reconciled
  • Fresh verifier: gpt-6.1-sol — final-verifier; agent sol-verifier
  • Phase 2 reviewers: gpt-6.1-sol — general (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — architecture-layering (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — rust-quality (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — security-auditor (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — general (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — architecture-layering (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — rust-quality (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — security-auditor (completed, effort high); agent phase2-reviewer
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify the current code and confirm that no unresolved issues remain.

No unresolved findings remain from the prior review on this head.

@QuantumExplorer
QuantumExplorer merged commit 958aa54 into v5.0-dev Oct 21, 2026
23 of 24 checks passed
@QuantumExplorer
QuantumExplorer deleted the claude/cancelled-dapi-core-rpc-6711dc branch October 21, 2026 07:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-self-review Waiting for the author to post /self-reviewed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants