Skip to main content

harmos_runtime/
config.rs

1/* ***
2 *
3 * Harmos
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * ***
8 */
9
10// Externally Supplied Runtime, Storage, Transport And Tooling Settings.
11
12/* === Modules === */
13
14/* === Re-Exports === */
15
16pub use harmos_domain::config::*;
17
18/* === Imports === */
19
20// Standard Library Imports
21#[cfg(feature = "plugin-guest")]
22use std::num::NonZeroU64;
23use std::num::NonZeroUsize;
24#[cfg(any(feature = "grpc", feature = "plugin"))]
25use std::time::Duration;
26
27// Third Party Imports
28
29// Local Imports
30pub use crate::storage::config::{StorageRequestSettings, StorageResponseSettings, StorageSettings};
31pub use crate::storage::state::StorageStateSettings;
32use crate::{Error, Result};
33
34/* === Constants === */
35
36/* === Macros === */
37
38/* === Type Aliases === */
39
40/* === Structs === */
41
42#[cfg(feature = "grpc")]
43#[derive(Clone, Copy, Debug)]
44pub struct DecodeSettings {
45    pub message_bytes: NonZeroUsize,
46    pub retained_bytes: NonZeroUsize,
47}
48
49#[cfg(feature = "grpc")]
50#[derive(Clone, Copy, Debug)]
51pub struct EncodeSettings {
52    pub message_bytes: NonZeroUsize,
53    pub buffer_bytes: NonZeroUsize,
54}
55
56#[cfg(feature = "grpc")]
57#[derive(Clone, Copy, Debug)]
58pub struct GrpcServerSettings {
59    pub calls: std::num::NonZeroUsize,
60    pub decode: DecodeSettings,
61    pub encode: EncodeSettings,
62    pub input: crate::operations::StreamSettings,
63    pub output: crate::operations::StreamSettings,
64    pub opening_timeout: Duration,
65}
66
67#[cfg(feature = "grpc")]
68#[derive(Clone, Debug)]
69pub struct GrpcSettings {
70    pub address: std::net::SocketAddr,
71    pub operations: GrpcServerSettings,
72    pub reflection: bool,
73    pub web: bool,
74    // Exact browser origins allowed through the gRPC-Web listener.
75    // An empty list emits no CORS headers.
76    pub allowed_origins: Vec<crate::grpc::Origin>,
77    pub tls: Option<tonic::transport::ServerTlsConfig>,
78    pub connections: NonZeroUsize,
79    pub calls_per_connection: NonZeroUsize,
80    pub request_timeout: Duration,
81    pub shutdown_timeout: Duration,
82}
83
84#[derive(Clone, Debug)]
85pub struct ManagedStorageSettings {
86    pub replay: crate::storage::ReadLimits,
87    pub capture: crate::storage::ReadLimits,
88    pub snapshots: Option<crate::state::SnapshotPolicy>,
89    pub journal_tail: usize,
90}
91
92#[derive(Clone, Copy, Debug)]
93pub struct OperationSettings {
94    pub queued: NonZeroUsize,
95    pub lifecycle_queued: NonZeroUsize,
96    pub preparation_bytes: Option<NonZeroUsize>,
97}
98
99#[cfg(feature = "plugin")]
100#[derive(Clone, Debug)]
101pub struct PluginSettings {
102    pub configuration: Vec<u8>,
103    pub requires: Vec<harmos_domain::Requirement>,
104    pub host_operations: Vec<crate::Identifier>,
105    pub initialization_timeout: Duration,
106    pub shutdown_timeout: Duration,
107    pub encoding_memory_bytes: NonZeroUsize,
108    pub restart: PluginRestartSettings,
109}
110
111#[cfg(feature = "plugin")]
112#[derive(Clone, Copy, Debug, PartialEq, Eq)]
113pub struct PluginRestartSettings {
114    // Automatic Restarts Allowed Per Explicit Activation Cycle.
115    pub retries: u32,
116    pub backoff_base: Duration,
117    pub backoff_cap: Duration,
118}
119
120#[cfg(feature = "plugin-sidecar")]
121#[derive(Clone, Debug)]
122pub struct PluginSidecarSettings {
123    pub plugin: PluginSettings,
124    pub calls: NonZeroUsize,
125}
126
127#[cfg(feature = "plugin-guest")]
128#[derive(Clone, Copy, Debug)]
129pub struct PluginGuestSettings {
130    pub component_bytes: NonZeroUsize,
131    pub memory_bytes: NonZeroUsize,
132    pub fuel: NonZeroU64,
133    pub memories: NonZeroUsize,
134    pub table_elements: NonZeroUsize,
135    pub tables: NonZeroUsize,
136    pub instances: NonZeroUsize,
137}
138
139/* === Enums === */
140
141/* === Traits === */
142
143/* === Implementations === */
144
145#[cfg(feature = "plugin")]
146impl Default for PluginRestartSettings {
147    fn default() -> Self {
148        Self {
149            retries: 0,
150            backoff_base: Duration::from_millis(100),
151            backoff_cap: Duration::from_secs(5),
152        }
153    }
154}
155
156#[cfg(feature = "plugin")]
157impl PluginRestartSettings {
158    pub fn validate(&self) -> Result<()> {
159        if self.backoff_base.is_zero()
160            || self.backoff_cap < self.backoff_base
161            || std::time::Instant::now().checked_add(self.backoff_cap).is_none()
162        {
163            return Err(Error::InvalidInput {
164                field: "plugin restart backoff",
165                reason: "requires positive representable bounds with base no greater than cap",
166            });
167        }
168        Ok(())
169    }
170}
171
172#[cfg(feature = "plugin-sidecar")]
173impl Default for PluginSidecarSettings {
174    fn default() -> Self {
175        Self {
176            plugin: PluginSettings::default(),
177            calls: NonZeroUsize::new(4).unwrap(),
178        }
179    }
180}
181
182#[cfg(feature = "plugin")]
183impl Default for PluginSettings {
184    fn default() -> Self {
185        Self {
186            configuration: b"null".to_vec(),
187            requires: Vec::new(),
188            host_operations: Vec::new(),
189            initialization_timeout: Duration::from_secs(5),
190            shutdown_timeout: Duration::from_secs(30),
191            encoding_memory_bytes: NonZeroUsize::new(4096).unwrap(),
192            restart: PluginRestartSettings::default(),
193        }
194    }
195}
196
197#[cfg(feature = "plugin")]
198impl PluginSettings {
199    pub fn validate(&self) -> Result<()> {
200        self.restart.validate()?;
201        for timeout in [self.initialization_timeout, self.shutdown_timeout] {
202            if timeout.is_zero() || std::time::Instant::now().checked_add(timeout).is_none() {
203                return Err(Error::InvalidInput {
204                    field: "plugin lifecycle timeout",
205                    reason: "must be positive and representable",
206                });
207            }
208        }
209        let grants: std::collections::BTreeSet<_> = self.host_operations.iter().collect();
210        if grants.len() != self.host_operations.len() {
211            return Err(Error::InvalidInput {
212                field: "plugin host operations",
213                reason: "grants must be unique",
214            });
215        }
216        Ok(())
217    }
218}
219
220#[cfg(feature = "plugin-guest")]
221impl Default for PluginGuestSettings {
222    fn default() -> Self {
223        Self {
224            component_bytes: NonZeroUsize::new(64 * 1024 * 1024).unwrap(),
225            memory_bytes: NonZeroUsize::new(64 * 1024 * 1024).unwrap(),
226            fuel: NonZeroU64::new(10_000_000).unwrap(),
227            memories: NonZeroUsize::new(1).unwrap(),
228            table_elements: NonZeroUsize::new(65_536).unwrap(),
229            tables: NonZeroUsize::new(8).unwrap(),
230            instances: NonZeroUsize::new(8).unwrap(),
231        }
232    }
233}
234#[cfg(feature = "grpc")]
235impl Default for GrpcServerSettings {
236    fn default() -> Self {
237        let bytes = std::num::NonZeroUsize::new(16 * 1024 * 1024).unwrap();
238        let message = std::num::NonZeroUsize::new(4 * 1024 * 1024).unwrap();
239        let items = std::num::NonZeroUsize::new(16).unwrap();
240        Self {
241            calls: std::num::NonZeroUsize::new(64).unwrap(),
242            decode: DecodeSettings {
243                message_bytes: message,
244                retained_bytes: bytes,
245            },
246            encode: EncodeSettings {
247                message_bytes: message,
248                buffer_bytes: std::num::NonZeroUsize::new(8192).unwrap(),
249            },
250            input: crate::operations::StreamSettings { items, bytes },
251            output: crate::operations::StreamSettings { items, bytes },
252            opening_timeout: Duration::from_secs(5),
253        }
254    }
255}
256
257#[cfg(feature = "grpc")]
258impl Default for GrpcSettings {
259    fn default() -> Self {
260        Self {
261            address: ([127, 0, 0, 1], 0).into(),
262            operations: GrpcServerSettings::default(),
263            reflection: false,
264            web: false,
265            allowed_origins: Vec::new(),
266
267            tls: None,
268            connections: NonZeroUsize::new(64).unwrap(),
269            calls_per_connection: NonZeroUsize::new(32).unwrap(),
270            request_timeout: Duration::from_secs(60),
271            shutdown_timeout: Duration::from_secs(10),
272        }
273    }
274}
275
276impl OperationSettings {
277    pub(crate) fn validate(&self) -> Result<()> {
278        if self.queued.get() > tokio::sync::Semaphore::MAX_PERMITS || self.lifecycle_queued.get() > tokio::sync::Semaphore::MAX_PERMITS {
279            return Err(Error::InvalidInput {
280                field: "operation queue capacity",
281                reason: "capacity exceeds the semaphore claim limit",
282            });
283        }
284        if self.preparation_bytes.is_some_and(|bytes| bytes.get() > tokio::sync::Semaphore::MAX_PERMITS || bytes.get() > u32::MAX as usize)
285        {
286            return Err(Error::InvalidInput {
287                field: "operation preparation bytes",
288                reason: "budget exceeds the semaphore byte-claim limit",
289            });
290        }
291        Ok(())
292    }
293}
294
295impl Default for OperationSettings {
296    fn default() -> Self {
297        Self {
298            queued: NonZeroUsize::new(256).expect("nonzero ordinary admission bound"),
299            lifecycle_queued: NonZeroUsize::new(256).expect("nonzero lifecycle admission bound"),
300            preparation_bytes: None,
301        }
302    }
303}
304
305/* === Public Functions === */
306
307/* === Private Functions === */
308
309/* === Tests === */