Усилить безопасность и надёжность выполнения операций
CI / Rust Checks (push) Successful in 5m7s
CI / UI Checks (push) Successful in 4s
CI / Deployment Manifests (push) Successful in 3s
CI / Frontend E2E (push) Successful in 3m9s
CI / Deploy (push) Successful in 1m41s

This commit is contained in:
2026-07-11 14:08:07 +03:00
parent 626f2845e2
commit 8318e4b560
40 changed files with 1343 additions and 185 deletions
+39 -8
View File
@@ -1,13 +1,13 @@
use std::{
collections::HashMap,
sync::Arc,
time::{Duration, Instant},
time::{Duration, Instant, SystemTime, UNIX_EPOCH},
};
use crank_core::{CacheScope, CoordinationStateStore, CoordinationStateValue};
use crank_registry::{PostgresRegistry, PublishedAgentTool, RegistryError};
use serde::{Deserialize, Serialize};
use tokio::sync::RwLock;
use tokio::sync::{Mutex, RwLock};
use tracing::info;
#[derive(Clone)]
@@ -16,6 +16,7 @@ pub struct PublishedToolCatalog {
refresh_interval: Duration,
coordination_store: Arc<dyn CoordinationStateStore>,
cached: Arc<RwLock<HashMap<CatalogKey, CachedCatalog>>>,
refresh_locks: Arc<Mutex<HashMap<CatalogKey, Arc<Mutex<()>>>>>,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
@@ -33,6 +34,7 @@ struct CachedCatalog {
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct CatalogSnapshot {
tools: Vec<PublishedAgentTool>,
generated_at_ms: u64,
}
impl PublishedToolCatalog {
@@ -46,6 +48,7 @@ impl PublishedToolCatalog {
refresh_interval,
coordination_store,
cached: Arc::new(RwLock::new(HashMap::new())),
refresh_locks: Arc::new(Mutex::new(HashMap::new())),
}
}
@@ -81,12 +84,32 @@ impl PublishedToolCatalog {
return Ok(());
}
if let Some(tools) = self.load_shared_snapshot(workspace_slug, agent_slug).await {
let refresh_lock = {
let mut locks = self.refresh_locks.lock().await;
Arc::clone(
locks
.entry(key.clone())
.or_insert_with(|| Arc::new(Mutex::new(()))),
)
};
let _refresh_guard = refresh_lock.lock().await;
let still_stale = {
let guard = self.cached.read().await;
match guard.get(&key).and_then(|entry| entry.loaded_at) {
Some(loaded_at) => loaded_at.elapsed() >= self.refresh_interval,
None => true,
}
};
if !still_stale {
return Ok(());
}
if let Some((tools, age)) = self.load_shared_snapshot(workspace_slug, agent_slug).await {
let mut guard = self.cached.write().await;
guard.insert(
key,
CachedCatalog {
loaded_at: Some(Instant::now()),
loaded_at: Instant::now().checked_sub(age),
tools,
},
);
@@ -136,7 +159,7 @@ impl PublishedToolCatalog {
&self,
workspace_slug: &str,
agent_slug: &str,
) -> Option<Vec<PublishedAgentTool>> {
) -> Option<(Vec<PublishedAgentTool>, Duration)> {
if self.refresh_interval.is_zero() {
return None;
}
@@ -150,9 +173,9 @@ impl PublishedToolCatalog {
Ok(value) => value?,
Err(_) => return None,
};
serde_json::from_value::<CatalogSnapshot>(value.payload)
.ok()
.map(|snapshot| snapshot.tools)
let snapshot = serde_json::from_value::<CatalogSnapshot>(value.payload).ok()?;
let age = Duration::from_millis(now_unix_ms().saturating_sub(snapshot.generated_at_ms));
(age < self.refresh_interval).then_some((snapshot.tools, age))
}
async fn store_shared_snapshot(
@@ -167,6 +190,7 @@ impl PublishedToolCatalog {
let payload = match serde_json::to_value(CatalogSnapshot {
tools: tools.to_vec(),
generated_at_ms: now_unix_ms(),
}) {
Ok(payload) => payload,
Err(_) => return,
@@ -184,6 +208,13 @@ impl PublishedToolCatalog {
}
}
fn now_unix_ms() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|duration| u64::try_from(duration.as_millis()).unwrap_or(u64::MAX))
.unwrap_or_default()
}
impl CatalogKey {
fn new(workspace_slug: &str, agent_slug: &str) -> Self {
Self {