ensure sufficient number of tickets before performing testrun assignment

This commit is contained in:
Jędrzej Stuczyński
2026-05-23 14:25:05 +01:00
parent 46c67440bb
commit dc48750271
5 changed files with 55 additions and 3 deletions
Generated
+1 -1
View File
@@ -7768,7 +7768,7 @@ dependencies = [
[[package]]
name = "nym-node-status-api"
version = "4.6.1"
version = "4.6.2-rc"
dependencies = [
"ammonia",
"anyhow",
@@ -3,7 +3,7 @@
[package]
name = "nym-node-status-api"
version = "4.6.1"
version = "4.6.2-rc"
authors.workspace = true
edition.workspace = true
license.workspace = true
@@ -58,6 +58,31 @@ async fn request_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_testrun(&mut conn).await {
Ok(res) => {
let Some(assignment) = res else {
@@ -71,6 +96,7 @@ async fn request_testrun(
assignment.gateway_identity_key,
);
// 4. retrieve required ticketbooks (we should always have sufficient number as we hold an exclusive lock)
let materials = state
.ticketbook_manager_state()
.attempt_assign_ticket_materials(assignment.testrun_id)
@@ -11,7 +11,7 @@ use semver::Version;
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, sync::Arc, time::Duration};
use time::UtcDateTime;
use tokio::sync::RwLock;
use tokio::sync::{Mutex, MutexGuard, RwLock};
use tracing::{error, instrument, trace, warn};
use utoipa::ToSchema;
@@ -42,6 +42,7 @@ pub(crate) struct AppState {
node_delegations: Arc<RwLock<DelegationsCache>>,
bin_info: BinaryInfo,
ticketbook_manager_state: TicketbookManagerState,
testrun_assignment_guard: Arc<Mutex<()>>,
}
impl AppState {
@@ -66,6 +67,7 @@ impl AppState {
node_delegations,
bin_info: BinaryInfo::new(),
ticketbook_manager_state,
testrun_assignment_guard: Arc::new(Default::default()),
}
}
@@ -81,6 +83,10 @@ impl AppState {
self.agent_key_list.contains(agent_pubkey)
}
pub(crate) async fn lock_testrun_assignment(&self) -> MutexGuard<'_, ()> {
self.testrun_assignment_guard.lock().await
}
pub(crate) fn agent_max_count(&self) -> i64 {
self.agent_max_count
}
@@ -53,6 +53,26 @@ impl TicketbookManagerState {
}
}
pub async fn has_enough_ticketbooks(&self) -> anyhow::Result<bool> {
let mut required = HashMap::new();
for typ in &self.buffered_ticket_types {
let count = required.entry(*typ).or_insert(0);
*count += 1;
}
for (typ, required_count) in required {
let available = self.storage.available_tickets_of_type(typ).await?;
if available < required_count {
warn!(
"not enough tickets of type {typ} available in storage, required {required_count}, available {available}"
);
return Ok(false);
}
}
Ok(true)
}
pub async fn attempt_assign_ticket_materials(
&self,
testrun_id: i32,