Defense-in-depth against a GCC 14 bug where reference captures in
coroutine lambdas are corrupted in the coroutine frame. The root
cause is fixed in CoroTaskRunner::init() (heap storage), but using
explicit pointer captures avoids any residual risk.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rewrite all test coroutine code to use postCoroTask() and co_await
runner->suspend() instead of the old postCoro() / Coro::yield() API:
- Coroutine_test: Migrate correct_order, incorrect_order, and
thread_specific_storage tests. Replace shared_ptr<JobQueue::Coro>
with shared_ptr<JobQueue::CoroTaskRunner>, yield() with co_await
runner->suspend().
- JobQueue_test: Rename testPostCoro to testPostCoroTask. Migrate all
3 sub-tests (repeated post, repeated resume, shutdown rejection)
to the new API.
After this change, zero .cpp files reference postCoro() or
JobQueue::Coro. The old API declarations remain in JobQueue.h and
Coro.ipp for removal in the cleanup milestone.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add cppcoro, fcontext, gantt, pratik, repost, stackful to
cspell.config.yaml to fix cspell check failures.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove the extra {} that was for the now-deleted Context::coro field
in the RPC::JsonContext construction in Application::startGeometry().
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace all postCoro() call sites with postCoroTask() using C++20
coroutine lambdas. The key changes are:
- Remove Context::coro field (shared_ptr<JobQueue::Coro>) from
RPC::Context, eliminating it from all aggregate initializations
- Replace RipplePathFind's yield/post/resume pattern with a local
std::condition_variable that blocks until path-finding completes,
avoiding colored-function infection across the RPC call chain
- Switch ServerHandler entry points (onRequest, onWSMessage) from
postCoro to postCoroTask with co_return lambdas
- Switch GRPCServer::CallData::process() to use postCoroTask,
rename private handler to processRequest()
- Update Path_test and AMMTest to use postCoroTask (they set
context.coro which no longer exists)
The old postCoro() API remains available for Coroutine_test and
JobQueue_test, which will be migrated in a subsequent commit.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Change postCoroTask from async post() to synchronous resume() for the
initial coroutine dispatch. The async approach created a timing-dependent
race during Env destruction where the coroutine frame's shared_ptr
reference cycle could be broken in an indeterminate order, causing the
debug-only finished_ assertion to fire non-deterministically on GCC-12
and GCC-15 debug builds.
The synchronous resume runs the coroutine body to its first suspension
point (co_await) or completion (co_return) on the caller's thread,
ensuring the coroutine state is determinate before postCoroTask returns.
Subsequent resumes still happen on worker threads via post().
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace `task_ = {}` with `std::move(task_)` in resume() and
expectEarlyExit(). The move assignment operator calls
handle_.destroy() while task_.handle_ still holds the old (now
dangling) handle value. If frame destruction triggers re-entrant
runner cleanup on GCC-12, the destructor sees a non-null handle_
and destroys the same frame again — a double-free.
std::move(task_) immediately nulls task_.handle_ via the move
constructor, then the frame is destroyed when the local goes out
of scope. This eliminates the re-entrancy window.
Also remove storedFunc_.reset() from resume() — the callable does
not participate in the shared_ptr cycle and will be cleaned up by
the runner's destructor.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
After a coroutine completes, the frame remains alive holding a captured
shared_ptr<CoroTaskRunner> back to its owner. This creates an unreachable
cycle: runner -> task_ -> frame -> shared_ptr<runner>.
Break the cycle in resume() by destroying the coroutine frame (task_ = {})
and the stored callable when the coroutine is done. Also fix runnable() to
handle the null-handle state after cleanup.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This change enables all clang-tidy checks that are already passing. It also modifies the clang-tidy CI job, so it runs against all files if .clang-tidy changed.
Store the coroutine callable on the heap in CoroTaskRunner::init()
via a type-erased FuncStore wrapper. Coroutine frames store a
reference to the callable's implicit object parameter (the lambda);
if the callable is a temporary, that reference dangles after the
caller returns. This caused stack-use-after-scope (ASAN), assertion
failures, and hangs across multiple compilers.
Also fix expectEarlyExit() to destroy the coroutine frame when
postCoroTask() fails, breaking a potential shared_ptr cycle.
Switch all coroutine test lambda captures from [&] to explicit
pointer-by-value as defense-in-depth against GCC 14 coroutine
frame corruption. Add value-returning CoroTask<T> tests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
clang-format: collapse single-line initializer lists and function
arguments. prettier: add blank lines in markdown lists.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This change replaces `void const*` by `uint256 const&` for database fetches.
Object hashes are expressed using the `uint256` data type, and are converted to `void *` when calling the `fetch` or `fetchBatch` functions. However, in these fetch functions they are converted back to `uint256`, making the conversion process unnecessary. In a few cases the underlying pointer is needed, but that can then be easy obtained via `[hash variable].data()`.
Introduce the core building blocks for migrating from Boost.Coroutine to
C++20 stackless coroutines (Milestone 1):
- CoroTask<T>: RAII coroutine return type with promise_type, symmetric
transfer via FinalAwaiter, and lazy start (suspend_always)
- CoroTaskRunner: Lifecycle manager (nested in JobQueue) mirroring the
existing Coro class — handles LocalValues swap, nSuspend_ accounting,
mutex-guarded resume, and join/post semantics
- JobQueueAwaiter: Convenience awaiter combining suspend + auto-repost,
with graceful fallback when JobQueue is stopping
- postCoroTask(): JobQueue entry point for launching C++20 coroutines
- CoroTask_test.cpp: 8 unit tests covering completion, suspend/resume
ordering, LocalValue isolation, exception propagation, and shutdown
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Updates XRPLF/actions prepare-runner to version 2cbf48101 which fixes
pip upgrade failures on macOS runners with Homebrew-managed Python.
* This commit was cherry-picked from "release-3.1", but ended up empty
because the changes are already present. It is included only for
accounting - to indicate that all changes/commits from the previous
release will be in the next one.
For coverage builds, we try to link against the `gcov` library (specific to the environment). But as macOS doesn't have this library and thus doesn't have the coverage tools to generate reports, the coverage builds on that platform were failing on linking.
We actually don't need to explicitly force this linking, as the `CodeCoverage` file already has correct detection logic (currently on lines 177-193), which is invoked when the `--coverage` flag is provided:
* AppleClang: Uses `xcrun -f llvm-cov` to set `GCOV_TOOL="llvm-cov gcov"`.
* Clang: Finds `llvm-cov` to set `GCOV_TOOL="llvm-cov gcov"`.
* GCC: Finds `gcov` to set `GCOV_TOOL="gcov"`.
The `GCOV_TOOL` is then passed to `gcovr` on line 416, so the correct tool is used for processing coverage data.
This change therefore removes the `gcov` suffix from lines 473 and 475 in the `CodeCoverage.cmake` file.
The rdb module was not properly designed, which is fixed in this change. The module had three classes:
1) The abstract class `RelationalDB`.
2) The abstract class `SQLiteDatabase`, which inherited from `RelationalDB` and added some pure virtual methods.
3) The concrete class `SQLiteDatabaseImp`, which inherited from `SQLiteDatabase` and implemented all methods.
The updated code simplifies this as follows:
* The `SQLiteDatabaseImp` has become `SQLiteDatabase`, and
* The former `SQLiteDatabase `has merged with `RelationalDatabase`.