-
-
Notifications
You must be signed in to change notification settings - Fork 365
Reduce memory consumption of checking Spark spend transactions #1723
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughAdd 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
Sequence DiagramsequenceDiagram
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: Repository UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🧰 Additional context used🧬 Code graph analysis (1)src/spark/state.cpp (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). (16)
🔇 Additional comments (2)
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. Comment |
There was a problem hiding this 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
📒 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: removingstd::moveon return.Line 94 correctly removes
std::move(ret)in favor of justret. Modern C++ compilers apply RVO/NRVO, and explicitly usingstd::movecan actually prevent these optimizations.Line 82's change from
std::move(task)totaskmeans 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:
Line 808: Gating condition prevents posting new tasks when pending tasks exceed
num_threads/2, providing backpressure to limit concurrent work and memory usage.Line 809: Lambda is now
mutableto allow modifying capturedspendandcover_sets.Lines 812-813: Explicitly clearing
spendandcover_setsafter verification frees memory early rather than waiting for task destruction.When gating blocks posting: Transaction is accepted without immediate verification (returns
trueat line 839). This is acceptable for the mempool path since verification will occur later during block validation withfStatefulSigmaCheck=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.
src/spark/state.cpp
Outdated
| static CCriticalSection cs_checkedSparkSpendTransactions; | ||
|
|
||
| static ParallelOpThreadPool<bool> gCheckProofThreadPool(boost::thread::hardware_concurrency()); | ||
| static ParallelOpThreadPool<bool> gCheckProofThreadPool(std::max(boost::thread::hardware_concurrency(), 8u)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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 -20Repository: 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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
left a comment
There was a problem hiding this 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.
src/spark/state.cpp
Outdated
| static CCriticalSection cs_checkedSparkSpendTransactions; | ||
|
|
||
| static ParallelOpThreadPool<bool> gCheckProofThreadPool(boost::thread::hardware_concurrency()); | ||
| static ParallelOpThreadPool<bool> gCheckProofThreadPool(std::max(boost::thread::hardware_concurrency(), 8u)); |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
844dca7
There was a problem hiding this 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::maxstill sets a minimum, not a maximum thread cap.The past review correctly identified that
std::maxshould bestd::minto enforce a thread cap. Changing8uto4udoesn't address the core issue—on a 64-core machine, this still creates 64 threads sincestd::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 copyingcover_sets.The lambda captures
cover_setsby value, creating a full deep copy of a potentially largestd::unordered_map<uint64_t, std::vector<Coin>>. Sincecover_setsis 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
📒 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()andPostTask(). 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.
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
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.