Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions src/test/jtx/Env.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include <xrpl/protocol/STTx.h>

#include <functional>
#include <future>
#include <source_location>
#include <string>
#include <tuple>
Expand Down Expand Up @@ -393,6 +394,49 @@ class Env
return close(std::chrono::seconds(5));
}

/** Close and advance the ledger, then synchronize with the server's
io_context to ensure all async operations initiated by the close have
been started.

This function performs the same ledger close as close(), but additionally
ensures that all tasks posted to the server's io_context (such as
WebSocket subscription message sends) have been initiated before returning.

What it guarantees:
- All async operations posted before syncClose() have been STARTED
- For WebSocket sends: async_write_some() has been called
- The actual I/O completion may still be pending (async)

What it does NOT guarantee:
- Async operations have COMPLETED
- WebSocket messages have been received by clients
- However, for localhost connections, the remaining latency is typically
microseconds, making tests reliable

Use this instead of close() when:
- Test code immediately checks for subscription messages
- Race conditions between test and worker threads must be avoided
- Deterministic test behavior is required

@param timeout Maximum time to wait for the barrier task to execute
@return true if close succeeded and barrier executed within timeout,
false otherwise
*/
[[nodiscard]] bool
syncClose(std::chrono::system_clock::duration timeout = std::chrono::seconds{1})
{
XRPL_ASSERT(
app().getNumberOfThreads() == 1,
"syncClose() is only useful on an application with a single thread");
auto const result = close();
auto server_barrier = std::make_shared<std::promise<void>>();
auto future = server_barrier->get_future();
boost::asio::post(
app().getIOContext(), [server_barrier]() { server_barrier->set_value(); });
auto const status = future.wait_for(timeout);
return result && status == std::future_status::ready;
}

/** Turn on JSON tracing.
With no arguments, trace all
*/
Expand Down
2 changes: 2 additions & 0 deletions src/test/jtx/envconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ std::unique_ptr<Config> admin_localnet(std::unique_ptr<Config>);

std::unique_ptr<Config> secure_gateway_localnet(std::unique_ptr<Config>);

std::unique_ptr<Config> single_thread_io(std::unique_ptr<Config>);

/// @brief adjust configuration with params needed to be a validator
///
/// this is intended for use with envconfig, as in
Expand Down
6 changes: 6 additions & 0 deletions src/test/jtx/impl/envconfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ secure_gateway_localnet(std::unique_ptr<Config> cfg)
(*cfg)[PORT_WS].set("secure_gateway", "127.0.0.0/8");
return cfg;
}
std::unique_ptr<Config>
single_thread_io(std::unique_ptr<Config> cfg)
{
cfg->IO_WORKERS = 1;
return cfg;
}

auto constexpr defaultseed = "shUwVw52ofnCUX5m7kPTKzJdr4HEH";

Expand Down
Loading
Loading