Compare commits

...
Author SHA1 Message Date
Mayukha Vadari d70f17e053 perf: Skip building proposed-tx JSON when nobody subscribes
pubProposedTransaction runs for every applied transaction while
masterLock is held. It built the full transaction JSON for each API
version before checking for subscribers to the transactions_proposed
stream, which usually has none. Check for subscribers first.

Add a test that a transactions_proposed subscriber receives a
transaction.
2026-10-14 13:12:36 -04:00
2 changed files with 41 additions and 2 deletions

No files matched your search

+29
View File
@@ -399,6 +399,34 @@ public:
}
}
void
testTransactionsProposed()
{
testcase("transactions_proposed");
using namespace std::chrono_literals;
using namespace jtx;
Env env(*this, envconfig(singleThreadIo));
auto wsc = makeWSClient(env.app().config());
json::Value stream{json::ValueType::Object};
stream[jss::streams] = json::ValueType::Array;
stream[jss::streams].append("transactions_proposed");
auto jv = wsc->invoke("subscribe", stream);
BEAST_EXPECT(jv[jss::status] == "success");
// A subscriber receives the transaction before the ledger closes.
env.fund(XRP(10000), "alice");
BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& msg) {
return msg[jss::type] == "transaction" && msg[jss::validated] == false &&
msg[jss::transaction][jss::TransactionType] == jss::Payment &&
msg[jss::transaction][jss::Destination] == Account("alice").human();
}));
jv = wsc->invoke("unsubscribe", stream);
BEAST_EXPECT(jv[jss::status] == "success");
}
void
testManifests()
{
@@ -2465,6 +2493,7 @@ public:
testLedger();
testTransactionsAPIv1();
testTransactionsAPIv2();
testTransactionsProposed();
testManifests();
testValidations(all - xrpFees);
testValidations(all);
+12 -2
View File
@@ -3277,9 +3277,19 @@ NetworkOPsImp::pubProposedTransaction(
if (transaction->isFlag(tfInnerBatchTxn))
return;
MultiApiJson const jvObj = transJson(transaction, result, false, ledger, std::nullopt);
// Building the JSON is expensive, and this stream usually has no
// subscribers. A client that subscribes after this check misses only
// this transaction.
bool hasSubscribers = false;
{
std::scoped_lock const sl(streamLock_);
hasSubscribers = !streamMaps_[SRtTransactions].empty();
}
if (hasSubscribers)
{
MultiApiJson const jvObj = transJson(transaction, result, false, ledger, std::nullopt);
// Hold each locked subscriber alive until after streamLock_ is
// released; a last-reference ~InfoSub would otherwise re-acquire this
// non-recursive mutex and self-deadlock. Declared before the lock,