Усилить безопасность и надёжность выполнения операций
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user