[pbs-devel] [PATCH proxmox-backup 3/3] pbs-config: add TTL window to token secret cache

Samuel Rufinatscha s.rufinatscha at proxmox.com
Fri Dec 5 14:25:56 CET 2025


Verify_secret() currently calls refresh_cache_if_file_changed() on every
request, which performs a metadata() call on token.shadow each time.
Under load this adds unnecessary overhead, considering also the file
usually should rarely change.

This patch introduces a TTL boundary, controlled by
TOKEN_SECRET_CACHE_TTL_SECS. File metadata is only re-loaded once the
TTL has expired.

This patch partly fixes bug #6049 [1].

[1] https://bugzilla.proxmox.com/show_bug.cgi?id=7017

Signed-off-by: Samuel Rufinatscha <s.rufinatscha at proxmox.com>
---
 pbs-config/src/token_shadow.rs | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/pbs-config/src/token_shadow.rs b/pbs-config/src/token_shadow.rs
index ed54cdfa..23837c60 100644
--- a/pbs-config/src/token_shadow.rs
+++ b/pbs-config/src/token_shadow.rs
@@ -10,6 +10,7 @@ use serde::{Deserialize, Serialize};
 use serde_json::{from_value, Value};
 
 use proxmox_sys::fs::CreateOptions;
+use proxmox_time::epoch_i64;
 
 use pbs_api_types::Authid;
 //use crate::auth;
@@ -24,6 +25,8 @@ const CONF_FILE: &str = pbs_buildcfg::configdir!("/token.shadow");
 /// subsequent authentications for the same token+secret combination, avoiding
 /// recomputing the password hash on every request.
 static TOKEN_SECRET_CACHE: OnceCell<RwLock<ApiTokenSecretCache>> = OnceCell::new();
+/// Max age in seconds of the token secret cache before checking for file changes.
+const TOKEN_SECRET_CACHE_TTL_SECS: i64 = 60;
 
 #[derive(Serialize, Deserialize)]
 #[serde(rename_all = "kebab-case")]
@@ -63,6 +66,15 @@ fn write_file(data: HashMap<Authid, String>) -> Result<(), Error> {
 fn refresh_cache_if_file_changed() -> Result<(), Error> {
     let mut cache = token_secret_cache().write().unwrap();
 
+    let now = epoch_i64();
+
+    // Fast path: Within TTL boundary
+    if let Some(last) = cache.last_checked {
+        if now - last < TOKEN_SECRET_CACHE_TTL_SECS {
+            return Ok(());
+        }
+    }
+
     // Fetch the current token.shadow metadata
     let (new_mtime, new_len) = match fs::metadata(CONF_FILE) {
         Ok(meta) => (meta.modified().ok(), Some(meta.len())),
@@ -79,6 +91,7 @@ fn refresh_cache_if_file_changed() -> Result<(), Error> {
     cache.secrets.clear();
     cache.file_mtime = new_mtime;
     cache.file_len = new_len;
+    cache.last_checked = Some(now);
 
     Ok(())
 }
@@ -169,6 +182,8 @@ struct ApiTokenSecretCache {
     file_mtime: Option<SystemTime>,
     // shadow file length to detect changes
     file_len: Option<u64>,
+    // last time the file metadata was checked
+    last_checked: Option<i64>,
 }
 
 fn token_secret_cache() -> &'static RwLock<ApiTokenSecretCache> {
@@ -177,6 +192,7 @@ fn token_secret_cache() -> &'static RwLock<ApiTokenSecretCache> {
             secrets: HashMap::new(),
             file_mtime: None,
             file_len: None,
+            last_checked: None,
         })
     })
 }
-- 
2.47.3





More information about the pbs-devel mailing list