public inbox for pdm-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Robert Obkircher <r.obkircher@proxmox.com>
To: Lukas Wagner <l.wagner@proxmox.com>
Cc: pdm-devel@lists.proxmox.com
Subject: Re: [PATCH proxmox 01/20] router: introduce shared state
Date: Fri, 21 Aug 2026 15:59:15 +0200	[thread overview]
Message-ID: <178732075530.242959.4186982857535541130.b4-review@b4> (raw)
In-Reply-To: <20260817125727.454039-2-l.wagner@proxmox.com>

> API handlers often need access to long-lived application data such as
> configuration, caches or client handles. So far the only way to get
> there is a global static, which hides the actual dependencies of a
> handler and makes testing awkward.
> 
> Add a type-keyed registry that a server fills once during startup,
> together with an accessor on RpcEnvironment so that handlers can reach
> it. The State<T> newtype wraps values that come from the registry,
> which allows telling them apart from regular API parameters.
> 
> Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
>
> diff --git a/proxmox-router/src/cli/environment.rs b/proxmox-router/src/cli/environment.rs
> index c85105a7..c9aefb75 100644
> --- a/proxmox-router/src/cli/environment.rs
> +++ b/proxmox-router/src/cli/environment.rs
> @@ -5,7 +5,7 @@ use serde_json::Value;
>  
>  use proxmox_schema::ApiType;
>  
> -use crate::{RpcEnvironment, RpcEnvironmentType};
> +use crate::{RpcEnvironment, RpcEnvironmentType, SharedStateRegistry};
>  
>  /// [`RpcEnvironment`] implementation for command line tools.
>  ///
> @@ -15,6 +15,7 @@ use crate::{RpcEnvironment, RpcEnvironmentType};
>  pub struct CliEnvironment {
>      result_attributes: Value,
>      auth_id: Option<String>,
> +    shared_state_registry: Option<SharedStateRegistry>,
>      pub(crate) global_options: HashMap<TypeId, Box<dyn Any + Send + Sync + 'static>>,
>  }
>  
> @@ -23,6 +24,11 @@ impl CliEnvironment {
>          Default::default()
>      }
>  
> +    /// Set the shared state registry for this environment.
> +    pub fn set_shared_state_registry(&mut self, registry: SharedStateRegistry) {
> +        self.shared_state_registry = Some(registry);
> +    }
> +
>      /// Borrow a global option by type.
>      ///
>      /// Returns `None` if the option type was not registered or no value was provided on the
> @@ -98,4 +104,8 @@ impl RpcEnvironment for CliEnvironment {
>      fn get_auth_id(&self) -> Option<String> {
>          self.auth_id.clone()
>      }
> +
> +    fn shared_state(&self) -> Option<&SharedStateRegistry> {
> +        self.shared_state_registry.as_ref()
> +    }
>  }
> diff --git a/proxmox-router/src/lib.rs b/proxmox-router/src/lib.rs
> index da2f018f..df225d25 100644
> --- a/proxmox-router/src/lib.rs
> +++ b/proxmox-router/src/lib.rs
> @@ -16,6 +16,7 @@ mod permission;
>  mod router;
>  mod rpc_environment;
>  mod serializable_return;
> +mod shared_state;
>  
>  #[doc(inline)]
>  #[cfg(feature = "server")]
> @@ -25,6 +26,7 @@ pub use permission::*;
>  pub use router::*;
>  pub use rpc_environment::{RpcEnvironment, RpcEnvironmentType};
>  pub use serializable_return::SerializableReturn;
> +pub use shared_state::{SharedStateRegistry, State};
>  
>  // make list_subdirs_api_method! work without an explicit proxmox-schema dependency:
>  #[doc(hidden)]
> diff --git a/proxmox-router/src/rpc_environment.rs b/proxmox-router/src/rpc_environment.rs
> index 8ce2d99d..e065504a 100644
> --- a/proxmox-router/src/rpc_environment.rs
> +++ b/proxmox-router/src/rpc_environment.rs
> @@ -2,6 +2,8 @@ use std::any::Any;
>  
>  use serde_json::Value;
>  
> +use crate::SharedStateRegistry;
> +
>  /// Helper to get around `RpcEnvironment: Sized`
>  pub trait AsAny {
>      fn as_any(&self) -> &(dyn Any + Send);
> @@ -45,6 +47,11 @@ pub trait RpcEnvironment: Any + AsAny + Send {
>      fn get_client_ip(&self) -> Option<std::net::SocketAddr> {
>          None // dummy no-op implementation, as most environments don't need this
>      }
> +
> +    /// Return a reference to the shared state registry.
> +    fn shared_state(&self) -> Option<&SharedStateRegistry> {
> +        None
> +    }
>  }
>  
>  /// Environment Type
> diff --git a/proxmox-router/src/shared_state.rs b/proxmox-router/src/shared_state.rs
> new file mode 100644
> index 00000000..14a539af
> --- /dev/null
> +++ b/proxmox-router/src/shared_state.rs
> @@ -0,0 +1,46 @@
> +//! Type-keyed state that API handlers can request as a parameter.
> +
> +use std::any::{Any, TypeId};
> +use std::collections::HashMap;
> +
> +use anyhow::{Error, bail};
> +
> +/// Registry of state values, keyed by their type.
> +///
> +/// It holds at most one value per type. Values are registered with
> +/// [`register`](SharedStateRegistry::register) before the registry is handed over to the API
> +/// environment, from where handlers can access them through
> +/// [`RpcEnvironment::shared_state`](crate::RpcEnvironment::shared_state). This allows passing
> +/// application context to handlers without resorting to globals.
> +#[derive(Default)]
> +pub struct SharedStateRegistry {
> +    map: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
> +}

We could get rid of some indirection by replacing the map with a
Box<&dyn SharedState>. Probably not worth it, though.

trait SharedState { fn lookup(&self, id: TypeId) -> &dyn Any; }

struct PdmState { a: A, b: B }

impl SharedState for PdmState {
    fn lookup(&self, id: TypeId) -> &dyn Any {
        if id == TypeId::of::<A>() { &self.a }
        else if id == TypeId::of::<B>() { &self.b }
        else { unreachable!() }
    }
}


> +
> +impl SharedStateRegistry {
> +    /// Get a clone of the registered value of type `T`, if there is one.
> +    pub fn lookup<T: 'static + Send + Sync + Clone>(&self) -> Option<T> {
> +        self.map
> +            .get(&TypeId::of::<T>())
> +            .and_then(|s| s.downcast_ref())
> +            .cloned()
This could .expect("value must have correct type for key").

-- 
Robert Obkircher <r.obkircher@proxmox.com>




  parent reply	other threads:[~2026-08-21 13:59 UTC|newest]

Thread overview: 28+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-17 12:57 [PATCH datacenter-manager/proxmox 00/20] inject application context via API macro for easier integration testing Lukas Wagner
2026-08-17 12:57 ` [PATCH proxmox 01/20] router: introduce shared state Lukas Wagner
2026-08-17 13:26   ` Lukas Wagner
2026-08-20 11:23   ` Lukas Wagner
2026-08-21 13:59   ` Robert Obkircher [this message]
2026-08-17 12:57 ` [PATCH proxmox 02/20] rest-server: allow to inject " Lukas Wagner
2026-08-17 12:57 ` [PATCH proxmox 03/20] api-macro: support shared state extraction type Lukas Wagner
2026-08-21 13:59   ` Robert Obkircher
2026-08-17 12:57 ` [PATCH datacenter-manager 04/20] context: promote context to a dir-style module Lukas Wagner
2026-08-17 12:57 ` [PATCH datacenter-manager 05/20] pdm-config: remotes: rename trait methods to read/write/lock Lukas Wagner
2026-08-17 12:57 ` [PATCH datacenter-manager 06/20] pdm-config: subscriptions: " Lukas Wagner
2026-08-21 14:00   ` Robert Obkircher
2026-08-17 12:57 ` [PATCH datacenter-manager 07/20] remote iterator: pass remote config reader explicitly Lukas Wagner
2026-08-17 12:57 ` [PATCH datacenter-manager 08/20] context: introduce a ContextFactory to build application context Lukas Wagner
2026-08-17 12:57 ` [PATCH datacenter-manager 09/20] context: establish PdmApplication object Lukas Wagner
2026-08-17 12:57 ` [PATCH datacenter-manager 10/20] context: register PdmApplication in router Lukas Wagner
2026-08-17 12:57 ` [PATCH datacenter-manager 11/20] parallel fetcher: pass arguments to closure in a single type Lukas Wagner
2026-08-21 14:00   ` Robert Obkircher
2026-08-17 12:57 ` [PATCH datacenter-manager 12/20] parallel fetcher: support a custom client factory Lukas Wagner
2026-08-17 12:57 ` [PATCH datacenter-manager 13/20] api: sdn: use PdmApplication handle for accessing remotes Lukas Wagner
2026-08-17 12:57 ` [PATCH datacenter-manager 14/20] tests: add helpers for building API-handler-level integration tests Lukas Wagner
2026-08-17 12:57 ` [PATCH datacenter-manager 15/20] tests: add example tests for SDN API routes Lukas Wagner
2026-08-17 12:57 ` [PATCH datacenter-manager 16/20] api-cache: add wrapper type Lukas Wagner
2026-08-17 12:57 ` [PATCH datacenter-manager 17/20] context: provide api-cache on the app object Lukas Wagner
2026-08-17 12:57 ` [PATCH datacenter-manager 18/20] api: subscriptions: use PdmApplication instead of globals Lukas Wagner
2026-08-17 12:57 ` [PATCH datacenter-manager 19/20] pdm-config: subscriptions: drop unused accessor functions Lukas Wagner
2026-08-17 12:57 ` [PATCH datacenter-manager 20/20] tests: add example tests for remote subscription management Lukas Wagner
2026-08-20 14:54 ` superseded: [PATCH datacenter-manager/proxmox 00/20] inject application context via API macro for easier integration testing 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=178732075530.242959.4186982857535541130.b4-review@b4 \
    --to=r.obkircher@proxmox.com \
    --cc=l.wagner@proxmox.com \
    --cc=pdm-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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal