all lists on lists.proxmox.com
 help / color / mirror / Atom feed
From: Lukas Wagner <l.wagner@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [pve-devel] [PATCH v2 proxmox-perl-rs 6/7] cache: add bindings for `SharedCache`
Date: Thu, 28 Sep 2023 13:50:11 +0200	[thread overview]
Message-ID: <20230928115012.326777-7-l.wagner@proxmox.com> (raw)
In-Reply-To: <20230928115012.326777-1-l.wagner@proxmox.com>

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@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





  parent reply	other threads:[~2023-09-28 11:50 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-09-28 11:50 [pve-devel] [PATCH v2 storage/proxmox{, -perl-rs} 0/7] cache storage plugin status for pvestatd/API status update calls Lukas Wagner
2023-09-28 11:50 ` [pve-devel] [PATCH v2 proxmox 1/7] sys: fs: remove unnecessary clippy allow directive Lukas Wagner
2023-09-28 11:50 ` [pve-devel] [PATCH v2 proxmox 2/7] sys: fs: let CreateOptions::apply_to take RawFd instead of File Lukas Wagner
2023-09-28 11:50 ` [pve-devel] [PATCH v2 proxmox 3/7] sys: fs: use inline formatting for bail! macro Lukas Wagner
2023-09-28 11:50 ` [pve-devel] [PATCH v2 proxmox 4/7] sys: add make_tmp_dir Lukas Wagner
2023-09-28 11:50 ` [pve-devel] [PATCH v2 proxmox 5/7] cache: add new crate 'proxmox-shared-cache' Lukas Wagner
2023-09-28 11:50 ` Lukas Wagner [this message]
2023-09-28 11:50 ` [pve-devel] [PATCH v2 pve-storage 7/7] stats: api: cache storage plugin status Lukas Wagner

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20230928115012.326777-7-l.wagner@proxmox.com \
    --to=l.wagner@proxmox.com \
    --cc=pve-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal