[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