Thanks to visit codestin.com
Credit goes to Github.com

Skip to content

Conversation

@psolstice
Copy link
Contributor

PR intention

Reduces memory footprint.

Code changes brief

A number of measures including limiting a number of concurrently queried transactions and number of threads in the thread pool designated for the checks.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 17, 2025

Walkthrough

Add a pending-task query to the parallel thread pool, narrow lambda captures and adjust packaged-task/future moves, cap the gCheckProofThreadPool thread count at 4, and gate async Spark spend verification posting based on pending-task count to apply backpressure.

Changes

Cohort / File(s) Summary
Thread pool API & internals
src/liblelantus/threadpool.h
Added std::size_t GetPendingTaskCount() to ParallelOpThreadPool. Narrowed lambda capture in shutdown path ([=] -> [currentId]), changed PostTask() to construct packaged task without moving the input task and return the future by value (removed unnecessary std::move).
Spark proof verification scheduling
src/spark/state.cpp
Initialize gCheckProofThreadPool with std::min(boost::thread::hardware_concurrency(), 4u) (cap at 4 threads). In non-urgent CheckSparkSpendTransaction, check GetPendingTaskCount() and only post when pending < (threads / 2); posted lambda is mutable and clears spend and cover_sets inside the async task; if capacity exceeded, fall back to existing synchronous/alternative path.

Sequence Diagram

sequenceDiagram
    participant Caller
    participant CheckSparkSpendTransaction
    participant gCheckProofThreadPool
    participant AsyncTask

    Caller->>CheckSparkSpendTransaction: request non-urgent verification
    CheckSparkSpendTransaction->>gCheckProofThreadPool: GetPendingTaskCount()
    gCheckProofThreadPool-->>CheckSparkSpendTransaction: pending_count
    alt pending_count < (threads / 2)
        CheckSparkSpendTransaction->>gCheckProofThreadPool: PostTask(mutable lambda)
        gCheckProofThreadPool->>AsyncTask: schedule & run
        AsyncTask->>AsyncTask: perform verification
        AsyncTask->>AsyncTask: clear spend & cover_sets (free resources)
        AsyncTask-->>CheckSparkSpendTransaction: set fChecked/fResult and complete future
    else pending high
        CheckSparkSpendTransaction-->>Caller: skip posting (defer/handle inline)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • aleflm
  • levonpetrosyan93

Poem

🐰 I count the tasks that line the way,
Four threads to hum through night and day,
When queues grow full I pause and wait,
Free the crumbs when jobs complete their state,
A twitch, a hop — the pool runs straight. 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main objective of the PR: reducing memory consumption during Spark spend transaction verification.
Description check ✅ Passed The description covers both required sections: PR intention explains memory reduction goals, and code changes brief outlines the specific measures (limiting concurrent transactions and thread pool size).
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch zk-memory-fix

📜 Recent review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 844dca7 and 8f13be0.

📒 Files selected for processing (1)
  • src/spark/state.cpp
🧰 Additional context used
🧬 Code graph analysis (1)
src/spark/state.cpp (1)
src/libspark/spend_transaction.cpp (4)
  • verify (228-233)
  • verify (228-230)
  • verify (238-408)
  • verify (238-241)
⏰ 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). (16)
  • GitHub Check: build-linux-cmake
  • GitHub Check: build-guix-x86_64-apple-darwin
  • GitHub Check: build-windows-cmake
  • GitHub Check: build-guix-arm64-apple-darwin
  • GitHub Check: build-mac-cmake
  • GitHub Check: build-guix-x86_64-linux-gnu
  • GitHub Check: build-guix-aarch64-linux-gnu
  • GitHub Check: build-guix-x86_64-w64-mingw32
  • GitHub Check: build-guix-x86_64-apple-darwin
  • GitHub Check: build-guix-x86_64-linux-gnu
  • GitHub Check: build-guix-x86_64-w64-mingw32
  • GitHub Check: build-windows-cmake
  • GitHub Check: build-mac-cmake
  • GitHub Check: build-guix-arm64-apple-darwin
  • GitHub Check: build-guix-aarch64-linux-gnu
  • GitHub Check: build-linux-cmake
🔇 Additional comments (2)
src/spark/state.cpp (2)

27-27: LGTM! Thread pool cap correctly enforced and reduced.

The change from std::max to std::min correctly addresses the past review comment - this now properly enforces a maximum cap rather than setting a minimum. Additionally, reducing the cap from 8 to 4 threads aligns with the PR objective to reduce memory consumption by limiting concurrent verification work.


807-823: LGTM! Effective backpressure and memory management improvements.

The capacity gate at line 808 implements backpressure by limiting task posting when GetPendingTaskCount() >= GetNumberOfThreads()/2. With 4 threads, this means tasks are only posted if there are < 2 pending tasks - quite restrictive but intentional for memory reduction per the PR objectives.

The lambda memory management is well-designed:

  • Captures spend and cover_sets by value for thread safety
  • Marked mutable to allow resource cleanup inside the lambda
  • Explicitly releases resources after verification with spend.reset() and cover_sets.clear() (lines 812-813)

Regarding the atomicity concern raised by aleflm: the GetPendingTaskCount() check and PostTask() call are both executed under the cs_checkedSparkSpendTransactions lock (acquired at line 771), making them atomic relative to other threads using the same lock. Minor races with the thread pool's internal state (worker threads completing tasks between check and post) are acceptable for backpressure purposes, as the goal is approximate throttling rather than exact limits.


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

❤️ Share

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

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: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8a821fc and b75e2d3.

📒 Files selected for processing (2)
  • src/liblelantus/threadpool.h (3 hunks)
  • src/spark/state.cpp (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/liblelantus/threadpool.h (1)
src/httpserver.cpp (1)
  • task (452-452)
⏰ 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). (8)
  • GitHub Check: build-linux-cmake
  • GitHub Check: build-guix-x86_64-apple-darwin
  • GitHub Check: build-guix-arm64-apple-darwin
  • GitHub Check: build-windows-cmake
  • GitHub Check: build-mac-cmake
  • GitHub Check: build-guix-x86_64-linux-gnu
  • GitHub Check: build-guix-x86_64-w64-mingw32
  • GitHub Check: build-guix-aarch64-linux-gnu
🔇 Additional comments (3)
src/liblelantus/threadpool.h (2)

82-94: Good change: removing std::move on return.

Line 94 correctly removes std::move(ret) in favor of just ret. Modern C++ compilers apply RVO/NRVO, and explicitly using std::move can actually prevent these optimizations.

Line 82's change from std::move(task) to task means the function now copies instead of moves. While this could be slightly less efficient, the impact should be minimal given how the lambda is used in the calling code.


122-126: LGTM!

The new GetPendingTaskCount() method correctly acquires the lock before accessing the task queue size, providing thread-safe access to the queue's state for the backpressure mechanism.

src/spark/state.cpp (1)

807-823: Good memory optimization with backpressure gating.

The changes implement effective memory reduction:

  1. Line 808: Gating condition prevents posting new tasks when pending tasks exceed num_threads/2, providing backpressure to limit concurrent work and memory usage.

  2. Line 809: Lambda is now mutable to allow modifying captured spend and cover_sets.

  3. Lines 812-813: Explicitly clearing spend and cover_sets after verification frees memory early rather than waiting for task destruction.

  4. When gating blocks posting: Transaction is accepted without immediate verification (returns true at line 839). This is acceptable for the mempool path since verification will occur later during block validation with fStatefulSigmaCheck=true.

Note: There's a minor race condition between checking pending count and posting the task, but since this is a heuristic for backpressure rather than a hard limit, it's acceptable.

static CCriticalSection cs_checkedSparkSpendTransactions;

static ParallelOpThreadPool<bool> gCheckProofThreadPool(boost::thread::hardware_concurrency());
static ParallelOpThreadPool<bool> gCheckProofThreadPool(std::max(boost::thread::hardware_concurrency(), 8u));
Copy link
Contributor

@coderabbitai coderabbitai bot Dec 17, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash

# Read the file around line 27
echo "=== src/spark/state.cpp lines 20-35 ==="
sed -n '20,35p' src/spark/state.cpp

# Search for references to gCheckProofThreadPool
echo -e "\n=== All references to gCheckProofThreadPool ==="
rg -n "gCheckProofThreadPool" src/spark/state.cpp

# Search for line 808 mentioned in original comment (backpressure mechanism)
echo -e "\n=== Context around line 808 ==="
sed -n '800,820p' src/spark/state.cpp

# Look for any PR or design documentation
echo -e "\n=== Search for thread pool or cap mentions in comments ==="
rg -n -i "thread.*pool|cap.*thread|concurrency.*limit|hardware_concurrency" src/spark/state.cpp | head -20

Repository: firoorg/firo

Length of output: 3131


Use std::min instead of std::max to enforce the 8-thread cap.

The code uses std::max(boost::thread::hardware_concurrency(), 8u), which sets a minimum of 8 threads. On a 64-core system, this creates 64 threads, contradicting the PR goal of "constraining the ZK proof verification thread pool size" to an explicit cap of 8. The backpressure mechanism at line 808 only throttles task queueing, not thread creation. Change to std::min(boost::thread::hardware_concurrency(), 8u) to enforce the intended maximum.

🤖 Prompt for AI Agents
In src/spark/state.cpp around line 27, the thread-pool is constructed with
std::max(boost::thread::hardware_concurrency(), 8u) which sets a minimum of 8
threads instead of capping at 8; replace std::max with std::min so the
constructor uses std::min(boost::thread::hardware_concurrency(), 8u) to enforce
an 8-thread maximum and prevent creating more threads than the intended cap.

Copy link
Contributor

@aleflm aleflm Dec 19, 2025

Choose a reason for hiding this comment

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

@psolstice I confirm Coderabbit's comment here. std::max is not correct here.

Copy link
Contributor

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

aleflm
aleflm previously approved these changes Dec 19, 2025
Copy link
Contributor

@aleflm aleflm left a comment

Choose a reason for hiding this comment

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

Other than two minor issues I raised, I think this PR should correctly reduce memory footprint.

static CCriticalSection cs_checkedSparkSpendTransactions;

static ParallelOpThreadPool<bool> gCheckProofThreadPool(boost::thread::hardware_concurrency());
static ParallelOpThreadPool<bool> gCheckProofThreadPool(std::max(boost::thread::hardware_concurrency(), 8u));
Copy link
Contributor

@aleflm aleflm Dec 19, 2025

Choose a reason for hiding this comment

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

@psolstice I confirm Coderabbit's comment here. std::max is not correct here.

checkState.fResult = false;
checkState.checkInProgress = std::make_shared<boost::future<bool>>(std::move(future));
// don't post a request if there are too many tasks already
if (gCheckProofThreadPool.GetPendingTaskCount() < (std::size_t)gCheckProofThreadPool.GetNumberOfThreads()/2) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Just asking. I am not 100% sure. It wouldn't be better to have this:

if (gCheckProofThreadPool.GetPendingTaskCount() < ...) {
    gCheckProofThreadPool.PostTask(...);
}

as atomic operation?

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 that important right here, it's just an optimization, even if the number of tasks changes it won't cause any real damage

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: 0

♻️ Duplicate comments (1)
src/spark/state.cpp (1)

27-27: std::max still sets a minimum, not a maximum thread cap.

The past review correctly identified that std::max should be std::min to enforce a thread cap. Changing 8u to 4u doesn't address the core issue—on a 64-core machine, this still creates 64 threads since std::max(64, 4) = 64.

To achieve the stated PR goal of reducing memory consumption by limiting thread pool size:

🔎 Proposed fix
-static ParallelOpThreadPool<bool> gCheckProofThreadPool(std::max(boost::thread::hardware_concurrency(), 4u));
+static ParallelOpThreadPool<bool> gCheckProofThreadPool(std::min(boost::thread::hardware_concurrency(), 4u));
🧹 Nitpick comments (1)
src/spark/state.cpp (1)

809-818: Use move semantics to avoid copying cover_sets.

The lambda captures cover_sets by value, creating a full deep copy of a potentially large std::unordered_map<uint64_t, std::vector<Coin>>. Since cover_sets is not used after the lambda is created in this code path (function returns at line 839), moving it would reduce peak memory allocation—aligning better with the PR's goal of reducing memory consumption.

🔎 Proposed fix
-                        auto future = gCheckProofThreadPool.PostTask([spend, cover_sets]() mutable {
+                        auto future = gCheckProofThreadPool.PostTask([spend = std::move(spend), cover_sets = std::move(cover_sets)]() mutable {
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b75e2d3 and 844dca7.

📒 Files selected for processing (2)
  • src/liblelantus/threadpool.h (4 hunks)
  • src/spark/state.cpp (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/liblelantus/threadpool.h
🧰 Additional context used
🧬 Code graph analysis (1)
src/spark/state.cpp (1)
src/libspark/spend_transaction.cpp (4)
  • verify (228-233)
  • verify (228-230)
  • verify (238-408)
  • verify (238-241)
⏰ 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). (16)
  • GitHub Check: build-guix-arm64-apple-darwin
  • GitHub Check: build-mac-cmake
  • GitHub Check: build-guix-x86_64-w64-mingw32
  • GitHub Check: build-windows-cmake
  • GitHub Check: build-guix-x86_64-apple-darwin
  • GitHub Check: build-guix-x86_64-linux-gnu
  • GitHub Check: build-guix-aarch64-linux-gnu
  • GitHub Check: build-linux-cmake
  • GitHub Check: build-linux-cmake
  • GitHub Check: build-guix-x86_64-apple-darwin
  • GitHub Check: build-windows-cmake
  • GitHub Check: build-guix-x86_64-linux-gnu
  • GitHub Check: build-guix-x86_64-w64-mingw32
  • GitHub Check: build-guix-arm64-apple-darwin
  • GitHub Check: build-guix-aarch64-linux-gnu
  • GitHub Check: build-mac-cmake
🔇 Additional comments (1)
src/spark/state.cpp (1)

808-808: TOCTOU race on backpressure check is low severity but acknowledged.

Per aleflm's prior comment: there's a race between GetPendingTaskCount() and PostTask(). Multiple threads could pass the check simultaneously, allowing more tasks than the threshold. For a soft backpressure mechanism where slight overrun is acceptable, this is pragmatic. Making it atomic would require locking around the thread pool, adding complexity for marginal benefit.

If stricter enforcement is needed, consider an atomic compare-and-increment pattern or moving the gating logic into the thread pool itself.

@reubenyap
Copy link
Member

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 23, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@reubenyap reubenyap merged commit 565e329 into master Dec 24, 2025
21 checks passed
@reubenyap reubenyap deleted the zk-memory-fix branch December 24, 2025 06:34
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.

5 participants