Skip to content

Commit c825b1f

Browse files
authored
perf(supervisor-middleware): remove body clones from local dispatch (NVIDIA#2679)
Signed-off-by: Shiju <shiju@nvidia.com> Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
1 parent 1709619 commit c825b1f

12 files changed

Lines changed: 1077 additions & 527 deletions

File tree

‎Cargo.lock‎

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎architecture/sandbox.md‎

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,7 @@ host selectors choose the chain independently of the network rule that admitted
108108
the request. Policy-local map keys identify configs, while built-in names or
109109
operator-owned registration names identify implementations.
110110

111-
Built-ins run in-process; operator services use the same bounded gRPC contract.
112-
`openshell-policy` validates policy-owned structure, and the active middleware
113-
registry validates implementation-owned config. The generic registry and chain
114-
runner live in `openshell-supervisor-middleware`; first-party implementations
115-
live in `openshell-supervisor-middleware-builtins`.
111+
Built-ins run in-process against a borrowed view of the chain's current request state. Operator services retain the bounded protobuf/gRPC contract, and the remote adapter materializes an owned evaluation only when the request crosses that transport boundary. `openshell-policy` validates policy-owned structure, and the active middleware registry validates implementation-owned config. The generic registry and chain runner live in `openshell-supervisor-middleware`; first-party implementations live in `openshell-supervisor-middleware-builtins`.
116112

117113
The supervisor installs policy and middleware registry changes as one runtime
118114
generation and preserves the last-known-good generation if preparation fails.

‎crates/openshell-core/Cargo.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ license.workspace = true
1111
repository.workspace = true
1212

1313
[dependencies]
14+
async-trait = "0.1"
1415
glob = { workspace = true }
1516
prost = { workspace = true }
1617
prost-types = { workspace = true }

‎crates/openshell-core/src/middleware.rs‎

Lines changed: 188 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,197 @@
11
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

4-
//! Platform-wide supervisor middleware limits.
4+
//! Supervisor middleware in-process contracts and platform-wide limits.
55
66
use std::time::Duration;
77

8+
use miette::Result;
9+
10+
use crate::proto::{
11+
HttpHeader, HttpRequestResult, HttpRequestTarget, MiddlewareManifest, RequestContext,
12+
SupervisorMiddlewarePhase,
13+
};
14+
15+
/// Borrowed request state exposed to one in-process middleware invocation.
16+
///
17+
/// The view reflects every transformation applied by earlier stages. It is valid
18+
/// only for the current invocation and cannot be retained by the middleware.
19+
#[derive(Clone, Copy)]
20+
pub struct HttpRequestView<'a> {
21+
phase: SupervisorMiddlewarePhase,
22+
context: &'a RequestContext,
23+
config: &'a prost_types::Struct,
24+
target: &'a HttpRequestTarget,
25+
headers: &'a [HttpHeader],
26+
body: &'a [u8],
27+
middleware_name: &'a str,
28+
}
29+
30+
impl<'a> HttpRequestView<'a> {
31+
/// Create a view over the chain's current request state for one stage.
32+
#[must_use]
33+
pub fn new(
34+
phase: SupervisorMiddlewarePhase,
35+
context: &'a RequestContext,
36+
config: &'a prost_types::Struct,
37+
target: &'a HttpRequestTarget,
38+
headers: &'a [HttpHeader],
39+
body: &'a [u8],
40+
middleware_name: &'a str,
41+
) -> Self {
42+
Self {
43+
phase,
44+
context,
45+
config,
46+
target,
47+
headers,
48+
body,
49+
middleware_name,
50+
}
51+
}
52+
53+
/// Return the typed middleware phase selected for this invocation.
54+
#[must_use]
55+
pub fn phase(self) -> SupervisorMiddlewarePhase {
56+
self.phase
57+
}
58+
59+
/// Return the request and sandbox identity shared by every chain stage.
60+
#[must_use]
61+
pub fn context(self) -> &'a RequestContext {
62+
self.context
63+
}
64+
65+
/// Return the validated configuration for this policy-selected stage.
66+
#[must_use]
67+
pub fn config(self) -> &'a prost_types::Struct {
68+
self.config
69+
}
70+
71+
/// Return the admitted destination and HTTP request target.
72+
#[must_use]
73+
pub fn target(self) -> &'a HttpRequestTarget {
74+
self.target
75+
}
76+
77+
/// Return visible request headers in wire order, including repeated names.
78+
#[must_use]
79+
pub fn headers(self) -> &'a [HttpHeader] {
80+
self.headers
81+
}
82+
83+
/// Return the current body, including replacements made by earlier stages.
84+
#[must_use]
85+
pub fn body(self) -> &'a [u8] {
86+
self.body
87+
}
88+
89+
/// Return the in-process middleware manifest or attachment name selected by
90+
/// policy, including custom implementation names.
91+
#[must_use]
92+
pub fn middleware_name(self) -> &'a str {
93+
self.middleware_name
94+
}
95+
}
96+
97+
/// Asynchronous contract for supervisor middleware that runs in the process.
98+
///
99+
/// Remote services use the protobuf `SupervisorMiddleware` contract instead.
100+
/// The borrowed view remains valid for the evaluation future, so implementations
101+
/// can yield without constructing an owned protobuf request envelope.
102+
///
103+
/// Downstream implementations must apply `#[async_trait::async_trait]` to each
104+
/// `impl InProcessMiddleware` block. The macro's default expansion creates
105+
/// `Send` futures, matching this trait's generated method signatures; do not
106+
/// use the `?Send` form.
107+
///
108+
/// An implementing crate must declare `async-trait` as a direct dependency
109+
/// because `openshell-core`'s dependency does not make the procedural macro
110+
/// available in downstream source. The example also names `miette` and
111+
/// `prost-types`, so standalone crates must declare those dependencies when
112+
/// using those paths.
113+
///
114+
/// # Examples
115+
///
116+
/// ```
117+
/// use std::sync::Arc;
118+
///
119+
/// use miette::Result;
120+
/// use openshell_core::middleware::{HttpRequestView, InProcessMiddleware};
121+
/// use openshell_core::proto::{
122+
/// Decision, HttpRequestResult, MiddlewareBinding, MiddlewareManifest,
123+
/// SupervisorMiddlewareOperation, SupervisorMiddlewarePhase,
124+
/// };
125+
/// use prost_types::Struct;
126+
///
127+
/// struct Service;
128+
///
129+
/// #[async_trait::async_trait]
130+
/// impl InProcessMiddleware for Service {
131+
/// async fn describe(&self) -> MiddlewareManifest {
132+
/// MiddlewareManifest {
133+
/// name: "example/audit".into(),
134+
/// service_version: "1".into(),
135+
/// bindings: vec![MiddlewareBinding {
136+
/// operation: SupervisorMiddlewareOperation::HttpRequest as i32,
137+
/// phase: SupervisorMiddlewarePhase::PreCredentials as i32,
138+
/// max_body_bytes: 1024,
139+
/// timeout: String::new(),
140+
/// }],
141+
/// }
142+
/// }
143+
///
144+
/// async fn validate_config(
145+
/// &self,
146+
/// _middleware_name: &str,
147+
/// _config: &Struct,
148+
/// ) -> Result<()> {
149+
/// Ok(())
150+
/// }
151+
///
152+
/// async fn evaluate_http_request(
153+
/// &self,
154+
/// _request: HttpRequestView<'_>,
155+
/// ) -> Result<HttpRequestResult> {
156+
/// Ok(HttpRequestResult {
157+
/// decision: Decision::Allow as i32,
158+
/// ..Default::default()
159+
/// })
160+
/// }
161+
/// }
162+
///
163+
/// let service: Arc<dyn InProcessMiddleware> = Arc::new(Service);
164+
/// assert_eq!(Arc::strong_count(&service), 1);
165+
/// ```
166+
#[async_trait::async_trait]
167+
pub trait InProcessMiddleware: Send + Sync {
168+
/// Return the immutable manifest describing this implementation.
169+
async fn describe(&self) -> MiddlewareManifest;
170+
171+
/// Validate implementation-owned configuration for one policy attachment.
172+
///
173+
/// # Errors
174+
///
175+
/// Returns an error when the implementation name is unknown or the
176+
/// configuration is malformed or unsupported.
177+
async fn validate_config(
178+
&self,
179+
middleware_name: &str,
180+
config: &prost_types::Struct,
181+
) -> Result<()>;
182+
183+
/// Evaluate one request using borrowed chain state.
184+
///
185+
/// # Errors
186+
///
187+
/// Returns an error when the selected implementation cannot evaluate the
188+
/// request or its validated configuration.
189+
async fn evaluate_http_request(
190+
&self,
191+
request: HttpRequestView<'_>,
192+
) -> Result<HttpRequestResult>;
193+
}
194+
8195
/// Default timeout for one supervisor middleware RPC.
9196
pub const DEFAULT_MIDDLEWARE_TIMEOUT: Duration = Duration::from_millis(500);
10197
/// Smallest operator-configured supervisor middleware RPC timeout.

‎crates/openshell-supervisor-middleware-builtins/Cargo.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ rust-version.workspace = true
1313
[dependencies]
1414
openshell-core = { path = "../openshell-core", default-features = false }
1515

16+
async-trait = "0.1"
1617
miette = { workspace = true }
1718
prost-types = { workspace = true }
1819
regex = { workspace = true }
1920
serde = { workspace = true }
2021
serde_json = { workspace = true }
21-
tonic = { workspace = true, features = ["server"] }
2222

2323
[dev-dependencies]
2424
tokio = { workspace = true }

0 commit comments

Comments
 (0)