gxs: decrypt completed sync transactions outside mNxsMutex

processTransactions() ran processTransactionForDecryption() -- per-item
circle RSA decryption of completed incoming transactions -- while holding
mNxsMutex. On channels with circle-restricted groups this was measured at
1-3 s of pure RSA per sync cycle (2026 ms of a 2028 ms pass, ~225 s
accumulated over a 73-minute session), blocking every other user of the
service (tick, item reception, API) for the whole duration.

Take the completed transactions out of the active map under the mutex,
decrypt them after it is released -- the thread is their only owner at
that point -- and re-take it only to queue them into mComplTransactions.
Behavior is otherwise unchanged: processTransactionForDecryption()
replaces encrypted items in place and always returns true nowadays;
items whose keys are unavailable stay encrypted and are dropped at
validation, as before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jolavillette 2026-08-05 13:32:25 +02:00
parent 339c765235
commit 6b6ed44c2d

View File

@ -2282,6 +2282,14 @@ bool RsGxsNetService::locked_checkTransacTimedOut(NxsTransaction* tr)
void RsGxsNetService::processTransactions()
{
// Completed incoming transactions collected below are decrypted after
// mNxsMutex is released: per-item circle decryption of a large sync
// transaction costs seconds of RSA, and holding the mutex for that long
// blocks every other user of the service (tick, item reception, API).
std::list<NxsTransaction*> completed_incoming;
{ // scope for mNxsMutex
RS_STACK_MUTEX(mNxsMutex) ;
for(TransactionsPeerMap::iterator mit = mTransactions.begin();mit != mTransactions.end(); ++mit)
@ -2463,31 +2471,16 @@ void RsGxsNetService::processTransactions()
// move to completed transactions
// Try to decrypt the items that need to be decrypted. This function returns true if the transaction is not encrypted.
// The transaction leaves the active map now; its items are
// decrypted once mNxsMutex is released, and it is queued
// into mComplTransactions afterwards.
if(processTransactionForDecryption(tr))
{
#ifdef NXS_NET_DEBUG_7
GXSNETDEBUG_P_(tr->mTransaction->PeerId()) << " successfully decrypted/processed transaction " << transN << ". Adding to completed list." << std::endl;
#endif
mComplTransactions.push_back(tr);
// transaction processing done
// for this id, add to removal list
toRemove.push_back(mmit->first);
completed_incoming.push_back(tr);
toRemove.push_back(mmit->first);
#ifdef NXS_NET_DEBUG_1
int total_transaction_time = (int)time(NULL) - (tr->mTimeOut - mTransactionTimeOut) ;
GXSNETDEBUG_P_(mit->first) << " incoming completed " << tr->mTransaction->nItems << " items transaction in " << total_transaction_time << " seconds." << std::endl;
int total_transaction_time = (int)time(NULL) - (tr->mTimeOut - mTransactionTimeOut) ;
GXSNETDEBUG_P_(mit->first) << " incoming completed " << tr->mTransaction->nItems << " items transaction in " << total_transaction_time << " seconds." << std::endl;
#endif
}
else
{
#ifdef NXS_NET_DEBUG_7
GXSNETDEBUG_P_(tr->mTransaction->PeerId()) << " no decryption occurred because of unloaded keys. Will retry later. TransN=" << transN << std::endl;
#endif
}
}
else if(flag & NxsTransaction::FLAG_STATE_STARTING)
{
@ -2528,6 +2521,25 @@ void RsGxsNetService::processTransactions()
}
}
} // end of scope for mNxsMutex
if(completed_incoming.empty())
return;
// Decrypt the completed incoming transactions outside the mutex. They were
// removed from mTransactions above, so this thread is their only owner.
// processTransactionForDecryption() replaces the encrypted items in place;
// items whose keys are not available simply stay encrypted and are dropped
// at validation, as before.
for(auto tr : completed_incoming)
processTransactionForDecryption(tr);
RS_STACK_MUTEX(mNxsMutex) ;
for(auto tr : completed_incoming)
mComplTransactions.push_back(tr);
}
bool RsGxsNetService::getGroupNetworkStats(const RsGxsGroupId& gid,RsGroupNetworkStats& stats)