[pve-devel] [PATCH v2 proxmox-perl-rs 6/7] cache: add bindings for `SharedCache`

Lukas Wagner l.wagner at proxmox.com
Thu Sep 28 13:50:11 CEST 2023


These bindings are contained in the `SharedCacheBase` class, which is
subclassed by `SharedCache` in Perl. The subclass was needed to
implement the `get_or_update` method since that requires to call a
closure as a passed parameter.

Signed-off-by: Lukas Wagner <l.wagner at proxmox.com>
---

Notes:
    Changes v1 -> v2:
      - Added lock/unlock
      - Added get_or_update in perl subclass
      - added *_with_lock methods

 common/pkg/Makefile                  |  1 +
 common/pkg/Proxmox/RS/SharedCache.pm | 46 ++++++++++++++
 common/src/mod.rs                    |  1 +
 common/src/shared_cache.rs           | 89 ++++++++++++++++++++++++++++
 pve-rs/Cargo.toml                    |  1 +
 5 files changed, 138 insertions(+)
 create mode 100644 common/pkg/Proxmox/RS/SharedCache.pm
 create mode 100644 common/src/shared_cache.rs

diff --git a/common/pkg/Makefile b/common/pkg/Makefile
index 7bf669f..a99c30d 100644
--- a/common/pkg/Makefile
+++ b/common/pkg/Makefile
@@ -26,6 +26,7 @@ Proxmox/RS/CalendarEvent.pm:
 	  Proxmox::RS::APT::Repositories \
 	  Proxmox::RS::CalendarEvent \
 	  Proxmox::RS::Notify \
+	  Proxmox::RS::SharedCacheBase \
 	  Proxmox::RS::Subscription
 
 all: Proxmox/RS/CalendarEvent.pm
diff --git a/common/pkg/Proxmox/RS/SharedCache.pm b/common/pkg/Proxmox/RS/SharedCache.pm
new file mode 100644
index 0000000..a35e0c5
--- /dev/null
+++ b/common/pkg/Proxmox/RS/SharedCache.pm
@@ -0,0 +1,46 @@
+package Proxmox::RS::SharedCache;
+
+use base 'Proxmox::RS::SharedCacheBase';
+
+use strict;
+use warnings;
+
+# This part has to be implemented in perl, since we calculate the new value on
+# demand from a passed closure.
+sub get_or_update {
+    my ($self, $key, $value_func, $timeout) = @_;
+
+    #Lookup value
+    my $val = $self->get($key);
+
+    if (!$val) {
+	my $lock = undef;
+	eval {
+	    # If expired, lock cache entry. This makes sure that other processes
+	    # cannot update it at the same time.
+	    $lock = $self->lock($key, 1);
+
+	    # Check again, may somebody else has already updated the value
+	    $val = $self->get_with_lock($key, $lock);
+
+	    # If still expired, update it
+	    if (!$val) {
+	        $val = $value_func->();
+	        $self->set_with_lock($key, $val, $timeout, $lock);
+	    }
+	};
+
+	my $err = $@;
+
+	# If the file has been locked, we *must* unlock it, no matter what
+	if (defined($lock)) {
+	    $self->unlock($lock)
+	}
+
+	die $err if $err;
+    }
+
+    return $val;
+}
+
+1;
diff --git a/common/src/mod.rs b/common/src/mod.rs
index c3574f4..badfc98 100644
--- a/common/src/mod.rs
+++ b/common/src/mod.rs
@@ -2,4 +2,5 @@ pub mod apt;
 mod calendar_event;
 pub mod logger;
 pub mod notify;
+pub mod shared_cache;
 mod subscription;
diff --git a/common/src/shared_cache.rs b/common/src/shared_cache.rs
new file mode 100644
index 0000000..0e2b561
--- /dev/null
+++ b/common/src/shared_cache.rs
@@ -0,0 +1,89 @@
+#[perlmod::package(name = "Proxmox::RS::SharedCacheBase")]
+mod export {
+    use std::os::fd::{FromRawFd, IntoRawFd, RawFd};
+
+    use anyhow::Error;
+    use nix::sys::stat::Mode;
+    use perlmod::Value;
+    use serde_json::Value as JSONValue;
+
+    use proxmox_shared_cache::{SharedCache, CacheLockGuard};
+    use proxmox_sys::fs::CreateOptions;
+
+    pub struct CacheWrapper(SharedCache);
+
+    perlmod::declare_magic!(Box<CacheWrapper> : &CacheWrapper as "Proxmox::RS::SharedCacheBase");
+
+    #[export(raw_return)]
+    fn new(#[raw] class: Value, base_dir: &str) -> Result<Value, Error> {
+        // TODO: Make this configurable once we need to cache values that should be
+        // accessible by other users.
+        let options = CreateOptions::new()
+            .root_only()
+            .perm(Mode::from_bits_truncate(0o700));
+
+        Ok(perlmod::instantiate_magic!(&class, MAGIC => Box::new(
+            CacheWrapper (
+                SharedCache::new(base_dir, options)?
+            )
+        )))
+    }
+
+    #[export]
+    fn set(
+        #[try_from_ref] this: &CacheWrapper,
+        key: &str,
+        value: JSONValue,
+        expires_in: Option<i64>,
+    ) -> Result<(), Error> {
+        this.0.set(key, &value, expires_in)
+    }
+
+    #[export]
+    fn set_with_lock(
+        #[try_from_ref] this: &CacheWrapper,
+        key: &str,
+        value: JSONValue,
+        expires_in: Option<i64>,
+        lock: RawFd,
+    ) -> Result<(), Error> {
+        let lock = unsafe { CacheLockGuard::from_raw_fd(lock) };
+        this.0.set_with_lock(key, &value, expires_in, &lock)
+    }
+
+    #[export]
+    fn get(#[try_from_ref] this: &CacheWrapper, key: &str) -> Result<Option<JSONValue>, Error> {
+        this.0.get(key)
+    }
+
+    #[export]
+    fn get_with_lock(#[try_from_ref] this: &CacheWrapper, key: &str, lock: RawFd) -> Result<Option<JSONValue>, Error> {
+        let lock = unsafe { CacheLockGuard::from_raw_fd(lock) };
+        this.0.get_with_lock(key, &lock)
+    }
+
+    #[export]
+    fn delete(#[try_from_ref] this: &CacheWrapper, key: &str) -> Result<(), Error> {
+        this.0.delete(key)
+    }
+
+    #[export]
+    fn delete_with_lock(#[try_from_ref] this: &CacheWrapper, key: &str, lock: RawFd) -> Result<(), Error> {
+        let lock = unsafe { CacheLockGuard::from_raw_fd(lock) };
+        this.0.delete_with_lock(key, &lock)
+    }
+
+    #[export]
+    fn lock(#[try_from_ref] this: &CacheWrapper, key: &str, exclusive: bool) -> Result<RawFd, Error> {
+        let file = this.0.lock(key, exclusive)?;
+        Ok(file.into_raw_fd())
+    }
+
+    #[export]
+    fn unlock(#[try_from_ref] _this: &CacheWrapper, lock: RawFd) -> Result<(), Error> {
+        // advisory file locks using flock are unlocked once the FD is closed
+        let _ = unsafe { CacheLockGuard::from_raw_fd(lock) };
+
+        Ok(())
+    }
+}
diff --git a/pve-rs/Cargo.toml b/pve-rs/Cargo.toml
index f9e3291..fa78dd6 100644
--- a/pve-rs/Cargo.toml
+++ b/pve-rs/Cargo.toml
@@ -39,6 +39,7 @@ proxmox-http-error = "0.1.0"
 proxmox-notify = "0.2"
 proxmox-openid = "0.10"
 proxmox-resource-scheduling = "0.3.0"
+proxmox-shared-cache = "0.1.0"
 proxmox-subscription = "0.4"
 proxmox-sys = "0.5"
 proxmox-tfa = { version = "4.0.4", features = ["api"] }
-- 
2.39.2






More information about the pve-devel mailing list