Skip to main content

reifydb_sub_server/
state.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use reifydb_auth::service::AuthService;
5use reifydb_core::actors::server::ServerMessage;
6use reifydb_engine::engine::StandardEngine;
7use reifydb_runtime::{
8	actor::{
9		mailbox::ActorRef,
10		system::{ActorHandle, ActorSpawner},
11	},
12	context::{clock::Clock, rng::Rng},
13};
14use reifydb_value::value::duration::Duration;
15use tracing::instrument;
16
17use crate::{actor::ServerActor, interceptor::RequestInterceptorChain};
18
19#[derive(Debug, Clone)]
20pub struct StateConfig {
21	pub query_timeout: Duration,
22
23	pub request_timeout: Duration,
24
25	pub max_connections: usize,
26
27	pub admin_enabled: bool,
28
29	pub subscribe_max_hydration_rows: u64,
30
31	pub subscribe_min_throttle: Duration,
32
33	pub subscribe_min_linger: Duration,
34
35	pub claim_wait_max: Duration,
36
37	pub claim_lease_ttl: Duration,
38
39	pub claim_max_parked: usize,
40}
41
42impl Default for StateConfig {
43	fn default() -> Self {
44		Self {
45			query_timeout: Duration::from_seconds(30).unwrap(),
46			request_timeout: Duration::from_seconds(60).unwrap(),
47			max_connections: 10_000,
48			admin_enabled: false,
49			subscribe_max_hydration_rows: 10_000,
50			subscribe_min_throttle: Duration::from_milliseconds(50).unwrap(),
51			subscribe_min_linger: Duration::zero(),
52			claim_wait_max: Duration::from_seconds(60).unwrap(),
53			claim_lease_ttl: Duration::from_seconds(30).unwrap(),
54			claim_max_parked: 10_000,
55		}
56	}
57}
58
59impl StateConfig {
60	pub fn new() -> Self {
61		Self::default()
62	}
63
64	pub fn query_timeout(mut self, timeout: Duration) -> Self {
65		self.query_timeout = timeout;
66		self
67	}
68
69	pub fn request_timeout(mut self, timeout: Duration) -> Self {
70		self.request_timeout = timeout;
71		self
72	}
73
74	pub fn max_connections(mut self, max: usize) -> Self {
75		self.max_connections = max;
76		self
77	}
78
79	pub fn admin_enabled(mut self, enabled: bool) -> Self {
80		self.admin_enabled = enabled;
81		self
82	}
83
84	pub fn subscribe_max_hydration_rows(mut self, n: u64) -> Self {
85		self.subscribe_max_hydration_rows = n;
86		self
87	}
88
89	pub fn subscribe_min_throttle(mut self, interval: Duration) -> Self {
90		self.subscribe_min_throttle = interval;
91		self
92	}
93
94	pub fn subscribe_min_linger(mut self, interval: Duration) -> Self {
95		self.subscribe_min_linger = interval;
96		self
97	}
98
99	pub fn claim_wait_max(mut self, budget: Duration) -> Self {
100		self.claim_wait_max = budget;
101		self
102	}
103
104	pub fn claim_lease_ttl(mut self, ttl: Duration) -> Self {
105		self.claim_lease_ttl = ttl;
106		self
107	}
108
109	pub fn claim_max_parked(mut self, max: usize) -> Self {
110		self.claim_max_parked = max;
111		self
112	}
113}
114
115#[derive(Clone)]
116pub struct AppState {
117	spawner: ActorSpawner,
118	engine: StandardEngine,
119	auth_service: AuthService,
120	config: StateConfig,
121	request_interceptors: RequestInterceptorChain,
122	clock: Clock,
123	rng: Rng,
124}
125
126impl AppState {
127	pub fn new(
128		spawner: ActorSpawner,
129		engine: StandardEngine,
130		auth_service: AuthService,
131		config: StateConfig,
132		request_interceptors: RequestInterceptorChain,
133		clock: Clock,
134		rng: Rng,
135	) -> Self {
136		Self {
137			spawner,
138			engine,
139			auth_service,
140			config,
141			request_interceptors,
142			clock,
143			rng,
144		}
145	}
146
147	pub fn clone_with_config(&self, config: StateConfig) -> Self {
148		Self {
149			spawner: self.spawner.clone(),
150			engine: self.engine.clone(),
151			auth_service: self.auth_service.clone(),
152			config,
153			request_interceptors: self.request_interceptors.clone(),
154			clock: self.clock.clone(),
155			rng: self.rng.clone(),
156		}
157	}
158
159	#[inline]
160	pub fn spawner(&self) -> ActorSpawner {
161		self.spawner.clone()
162	}
163
164	#[inline]
165	pub fn engine(&self) -> &StandardEngine {
166		&self.engine
167	}
168
169	#[inline]
170	pub fn engine_clone(&self) -> StandardEngine {
171		self.engine.clone()
172	}
173
174	#[inline]
175	pub fn config(&self) -> &StateConfig {
176		&self.config
177	}
178
179	#[inline]
180	pub fn query_timeout(&self) -> Duration {
181		self.config.query_timeout
182	}
183
184	#[inline]
185	pub fn request_timeout(&self) -> Duration {
186		self.config.request_timeout
187	}
188
189	#[inline]
190	pub fn max_connections(&self) -> usize {
191		self.config.max_connections
192	}
193
194	#[inline]
195	pub fn admin_enabled(&self) -> bool {
196		self.config.admin_enabled
197	}
198
199	#[inline]
200	pub fn subscribe_max_hydration_rows(&self) -> u64 {
201		self.config.subscribe_max_hydration_rows
202	}
203
204	#[inline]
205	pub fn subscribe_min_throttle(&self) -> Duration {
206		self.config.subscribe_min_throttle
207	}
208
209	#[inline]
210	pub fn claim_wait_max(&self) -> Duration {
211		self.config.claim_wait_max
212	}
213
214	#[inline]
215	pub fn claim_lease_ttl(&self) -> Duration {
216		self.config.claim_lease_ttl
217	}
218
219	#[inline]
220	pub fn claim_max_parked(&self) -> usize {
221		self.config.claim_max_parked
222	}
223
224	pub fn clamp_throttle(&self, requested: Option<Duration>) -> Duration {
225		match requested {
226			Some(d) if !d.is_zero() => d.max(self.config.subscribe_min_throttle),
227			_ => Duration::zero(),
228		}
229	}
230
231	#[inline]
232	pub fn subscribe_min_linger(&self) -> Duration {
233		self.config.subscribe_min_linger
234	}
235
236	pub fn clamp_linger(&self, requested: Option<Duration>) -> Duration {
237		match requested {
238			Some(d) if !d.is_zero() => d.max(self.config.subscribe_min_linger),
239			_ => Duration::zero(),
240		}
241	}
242
243	#[inline]
244	pub fn request_interceptors(&self) -> &RequestInterceptorChain {
245		&self.request_interceptors
246	}
247
248	#[inline]
249	pub fn clock(&self) -> &Clock {
250		&self.clock
251	}
252
253	#[inline]
254	pub fn rng(&self) -> &Rng {
255		&self.rng
256	}
257
258	#[inline]
259	pub fn auth_service(&self) -> &AuthService {
260		&self.auth_service
261	}
262
263	#[instrument(name = "actor::spawn_server", level = "info", skip_all)]
264	pub fn spawn_server_actor(&self) -> (ActorRef<ServerMessage>, ActorHandle<ServerMessage>) {
265		let actor = ServerActor::new(self.engine.clone(), self.auth_service.clone(), self.clock.clone());
266		let handle = self.spawner.spawn_ephemeral("server-req", actor);
267		let actor_ref = handle.actor_ref().clone();
268		(actor_ref, handle)
269	}
270}
271
272#[cfg(test)]
273pub mod tests {
274	use super::*;
275
276	#[test]
277	fn test_query_defaults() {
278		let config = StateConfig::default();
279		assert_eq!(config.query_timeout, Duration::from_seconds(30).unwrap());
280		assert_eq!(config.request_timeout, Duration::from_seconds(60).unwrap());
281		assert_eq!(config.max_connections, 10_000);
282		assert_eq!(config.subscribe_max_hydration_rows, 10_000);
283		assert_eq!(config.subscribe_min_throttle, Duration::from_milliseconds(50).unwrap());
284		assert_eq!(config.subscribe_min_linger, Duration::zero());
285	}
286
287	#[test]
288	fn test_query_config_builder() {
289		let config = StateConfig::new()
290			.query_timeout(Duration::from_seconds(60).unwrap())
291			.request_timeout(Duration::from_seconds(120).unwrap())
292			.max_connections(5_000)
293			.subscribe_max_hydration_rows(50_000)
294			.subscribe_min_throttle(Duration::from_milliseconds(200).unwrap())
295			.subscribe_min_linger(Duration::from_milliseconds(20).unwrap());
296
297		assert_eq!(config.query_timeout, Duration::from_seconds(60).unwrap());
298		assert_eq!(config.request_timeout, Duration::from_seconds(120).unwrap());
299		assert_eq!(config.max_connections, 5_000);
300		assert_eq!(config.subscribe_max_hydration_rows, 50_000);
301		assert_eq!(config.subscribe_min_throttle, Duration::from_milliseconds(200).unwrap());
302		assert_eq!(config.subscribe_min_linger, Duration::from_milliseconds(20).unwrap());
303	}
304}