From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id 24D951FF14F for ; Fri, 08 May 2026 17:03:44 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 9F5641C18B; Fri, 8 May 2026 17:03:42 +0200 (CEST) From: Lukas Wagner To: pdm-devel@lists.proxmox.com Subject: [PATCH datacenter-manager 3/4] api: resources: subscriptions: switch over to pdm_cache Date: Fri, 8 May 2026 17:03:29 +0200 Message-ID: <20260508150330.363622-4-l.wagner@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260508150330.363622-1-l.wagner@proxmox.com> References: <20260508150330.363622-1-l.wagner@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1778252507216 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.054 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [resources.rs] Message-ID-Hash: AU76CLA4AHNY2W5U77LOK2DZCEKOL4KD X-Message-ID-Hash: AU76CLA4AHNY2W5U77LOK2DZCEKOL4KD X-MailFrom: l.wagner@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox Datacenter Manager development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Signed-off-by: Lukas Wagner --- server/src/api/resources.rs | 82 ++++++++++++++----------------------- 1 file changed, 31 insertions(+), 51 deletions(-) diff --git a/server/src/api/resources.rs b/server/src/api/resources.rs index 50315b11..c22e33c5 100644 --- a/server/src/api/resources.rs +++ b/server/src/api/resources.rs @@ -30,9 +30,10 @@ use proxmox_schema::{api, parse_boolean}; use proxmox_sortable_macro::sortable; use proxmox_subscription::SubscriptionStatus; use pve_api_types::{ClusterResource, ClusterResourceNetworkType, ClusterResourceType}; +use serde::{Deserialize, Serialize}; use crate::metric_collection::top_entities; -use crate::{connection, views}; +use crate::{connection, pdm_cache, views}; pub const ROUTER: Router = Router::new() .get(&list_subdirs_api_method!(SUBDIRS)) @@ -798,15 +799,11 @@ async fn get_top_entities( Ok(res) } -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] struct CachedSubscriptionState { node_info: HashMap>, - timestamp: i64, } -static SUBSCRIPTION_CACHE: LazyLock>> = - LazyLock::new(|| RwLock::new(HashMap::new())); - /// Get the subscription state for a given remote. /// /// If recent enough cached data is available, it is returned @@ -815,66 +812,49 @@ pub async fn get_subscription_info_for_remote( remote: &Remote, max_age: u64, ) -> Result>, Error> { - if let Some(cached_subscription) = get_cached_subscription_info(&remote.id, max_age) { + if let Some(cached_subscription) = + get_cached_subscription_info(remote.id.clone(), max_age).await? + { Ok(cached_subscription.node_info) } else { let node_info = fetch_remote_subscription_info(remote).await?; - let now = proxmox_time::epoch_i64(); - update_cached_subscription_info(&remote.id, &node_info, now); + update_cached_subscription_info(remote.id.clone(), node_info.clone()).await?; Ok(node_info) } } -fn get_cached_subscription_info(remote: &str, max_age: u64) -> Option { - let cache = SUBSCRIPTION_CACHE - .read() - .expect("subscription mutex poisoned"); +const SUBSCRIPTION_STATE_CACHE_KEY: &str = "subscription-state"; - if max_age == 0 { - return None; - } - if let Some(cached_subscription) = cache.get(remote) { - let now = proxmox_time::epoch_i64(); - let diff = now - cached_subscription.timestamp; +async fn get_cached_subscription_info( + remote: String, + max_age: u64, +) -> Result, Error> { + tokio::task::spawn_blocking(move || { + let cache = pdm_cache::instance().read_remote(&remote)?; - if diff >= max_age as i64 || diff < 0 { - // value is too old or from the future - None - } else { - Some(cached_subscription.clone()) - } - } else { - None - } + Ok(cache.get_with_max_age(SUBSCRIPTION_STATE_CACHE_KEY, max_age as i64)?) + }) + .await? } /// Update cached subscription data. /// /// If the cache already contains more recent data we don't insert the passed resources. -fn update_cached_subscription_info( - remote: &str, - node_info: &HashMap>, - now: i64, -) { - // there is no good way to recover from this, so panicking should be fine - let mut cache = SUBSCRIPTION_CACHE - .write() - .expect("subscription mutex poisoned"); +async fn update_cached_subscription_info( + remote: String, + node_info: HashMap>, +) -> Result<(), Error> { + tokio::task::spawn_blocking(move || { + let cache = pdm_cache::instance().write_remote(&remote)?; - if let Some(cached_resource) = cache.get(remote) { - // skip updating if the data is new enough - if cached_resource.timestamp >= now { - return; - } - } - - cache.insert( - remote.into(), - CachedSubscriptionState { - node_info: node_info.clone(), - timestamp: now, - }, - ); + Ok(cache.set( + SUBSCRIPTION_STATE_CACHE_KEY, + &CachedSubscriptionState { + node_info: node_info, + }, + )?) + }) + .await? } /// Maps a list of node subscription infos into a single [`RemoteSubscriptionState`] -- 2.47.3