From 8392f7da9400cfad1cdd7af1bb509dae527f1e03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 1 Jun 2026 16:21:27 +0100 Subject: [PATCH] fix(ns-api): lock assignment + ticketbook pre-check in ports-check request request_ports_check_testrun skipped the lock_testrun_assignment() guard and has_enough_ticketbooks() pre-check that request_testrun performs, so two concurrent ports-check requests could race on ticket materials and a depleted cache left the run InProgress until stale-refresh. Mirror the probe path. --- .../src/http/api/testruns.rs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/nym-node-status-api/nym-node-status-api/src/http/api/testruns.rs b/nym-node-status-api/nym-node-status-api/src/http/api/testruns.rs index 98219a8348..0e7384866c 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/api/testruns.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/api/testruns.rs @@ -156,6 +156,31 @@ async fn request_ports_check_testrun( return Err(HttpError::no_testruns_available()); } + // 1. guard against other agents attempting to get testruns so that we would be able to check + // for ticketbook count without cross-interaction + let _guard = state.lock_testrun_assignment().await; + + // 2. check if we have enough ticketbooks of ALL types before assigning the run + match state + .ticketbook_manager_state() + .has_enough_ticketbooks() + .await + { + Err(err) => { + return Err(HttpError::internal_with_logging(format!( + "failed to check ticketbook storage: {err}" + ))); + } + Ok(false) => { + tracing::warn!("not enough ticketbooks available, rejecting testrun assignment"); + return Err(HttpError::internal_with_logging( + "not enough ticketbooks available to assign a testrun", + )); + } + Ok(true) => (), + } + + // 3. attempt to assign the testrun itself match db::queries::testruns::assign_oldest_ports_check_testrun(&mut conn).await { Ok(res) => { let Some(assignment) = res else {