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
next prev 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.