1use std::{
4 collections::{BTreeMap, BTreeSet},
5 sync::{
6 Arc, Mutex, MutexGuard,
7 atomic::{AtomicU64, Ordering},
8 },
9};
10
11use sim_kernel::{Cx, Env, Error, Expr, Symbol, Value};
12use sim_lib_expr_tree::TreeHandle;
13use sim_lib_server::{ServerAddress, SystemWallClock, WallClock};
14use sim_lib_view::SurfaceCodec;
15use sim_lib_view_expr_tree::ExpressionTreeSurfaceCodec;
16use sim_value::access;
17
18use crate::error::{ExpressionTreeServerError, ServerResult, internal};
19use crate::model::{ExpressionTreeServerLimits, SessionId, WatchBatch, WatchId};
20use crate::protocol;
21use crate::session::SessionRecord;
22
23mod route;
24
25static NEXT_SERVER_NONCE: AtomicU64 = AtomicU64::new(1);
26
27pub struct ExpressionTreeServer {
29 address: ServerAddress,
30 codecs: Vec<Symbol>,
31 clock: Arc<dyn WallClock>,
32 limits: ExpressionTreeServerLimits,
33 nonce: u64,
34 registry: Mutex<Registry>,
35}
36
37struct Registry {
38 sessions: BTreeMap<SessionId, SessionRecord>,
39 in_flight: BTreeSet<SessionId>,
40 next_session: u64,
41 next_tick: u64,
42}
43
44struct RuntimeTarget {
45 tree: TreeHandle,
46 resource: Symbol,
47}
48
49impl RuntimeTarget {
50 fn new(record: &SessionRecord) -> Self {
51 Self {
52 tree: record.tree.clone(),
53 resource: record.resource(),
54 }
55 }
56}
57
58impl ExpressionTreeServer {
59 pub fn new(
62 address: ServerAddress,
63 codecs: Vec<Symbol>,
64 clock: Arc<dyn WallClock>,
65 limits: ExpressionTreeServerLimits,
66 ) -> ServerResult<Self> {
67 if codecs.is_empty() {
68 return Err(ExpressionTreeServerError::new(
69 "invalid-config",
70 "at least one server codec is required",
71 ));
72 }
73 if !limits.validate() {
74 return Err(ExpressionTreeServerError::new(
75 "invalid-config",
76 "all expression-tree server limits must be nonzero",
77 ));
78 }
79 Ok(Self {
80 address,
81 codecs,
82 clock,
83 limits,
84 nonce: NEXT_SERVER_NONCE.fetch_add(1, Ordering::Relaxed),
85 registry: Mutex::new(Registry {
86 sessions: BTreeMap::new(),
87 in_flight: BTreeSet::new(),
88 next_session: 1,
89 next_tick: 1,
90 }),
91 })
92 }
93
94 pub fn local() -> Self {
97 Self::new(
98 ServerAddress::Local,
99 vec![Symbol::qualified("codec", "binary")],
100 Arc::new(SystemWallClock),
101 ExpressionTreeServerLimits::default(),
102 )
103 .expect("default expression-tree server configuration is valid")
104 }
105
106 pub fn address(&self) -> &ServerAddress {
108 &self.address
109 }
110
111 pub fn codecs(&self) -> &[Symbol] {
113 &self.codecs
114 }
115
116 pub const fn limits(&self) -> ExpressionTreeServerLimits {
118 self.limits
119 }
120
121 pub fn create_session(&self, cx: &mut Cx, storage_name: &str) -> ServerResult<SessionId> {
124 let (id, tick) = {
125 let mut registry = self.lock_registry()?;
126 let tick = begin_request(&mut registry, self.limits);
127 if registry.sessions.len() >= self.limits.max_sessions {
128 return Err(ExpressionTreeServerError::new(
129 "session-limit",
130 format!("server session limit {} reached", self.limits.max_sessions),
131 ));
132 }
133 let id = SessionId(format!(
134 "{:016x}-{:016x}",
135 self.nonce, registry.next_session
136 ));
137 registry.next_session = registry.next_session.saturating_add(1);
138 (id, tick)
139 };
140
141 let value = cx
142 .eval_expr(Expr::Call {
143 operator: Box::new(Expr::Symbol(Symbol::qualified("expr-tree", "open"))),
144 args: vec![Expr::String(storage_name.to_owned())],
145 })
146 .map_err(classify_kernel_error)?;
147 let tree = value
148 .object()
149 .downcast_ref::<TreeHandle>()
150 .cloned()
151 .ok_or_else(|| {
152 ExpressionTreeServerError::new(
153 "runtime-contract",
154 "expr-tree/open did not return a live TreeHandle",
155 )
156 })?;
157 let clock = Arc::clone(&self.clock);
158 tree.set_wall_clock(move || clock.now().ok().map(|time| time.unix_millis()))
159 .map_err(classify_kernel_error)?;
160
161 let mut registry = self.lock_registry()?;
162 expire_idle(&mut registry, self.limits);
163 if registry.sessions.len() >= self.limits.max_sessions {
164 return Err(ExpressionTreeServerError::new(
165 "session-limit",
166 "session capacity changed while opening the tree",
167 ));
168 }
169 registry
170 .sessions
171 .insert(id.clone(), SessionRecord::new(id.clone(), tree, tick));
172 Ok(id)
173 }
174
175 pub fn snapshot(&self, session: &SessionId) -> ServerResult<Expr> {
177 let mut registry = self.lock_registry()?;
178 let tick = begin_request(&mut registry, self.limits);
179 let record = session_mut(&mut registry, session)?;
180 record.last_activity_tick = tick;
181 record.snapshot(self.limits)
182 }
183
184 pub fn revision(&self, session: &SessionId) -> ServerResult<u64> {
186 let mut registry = self.lock_registry()?;
187 let tick = begin_request(&mut registry, self.limits);
188 let record = session_mut(&mut registry, session)?;
189 record.last_activity_tick = tick;
190 Ok(record.revision)
191 }
192
193 pub fn apply_intent(
196 &self,
197 cx: &mut Cx,
198 session: &SessionId,
199 expected_revision: u64,
200 intent: &Expr,
201 ) -> ServerResult<Expr> {
202 let snapshot = self.snapshot(session)?;
203 let current = snapshot_revision(&snapshot)?;
204 if current != expected_revision {
205 return Err(stale(expected_revision, current));
206 }
207 let codec = ExpressionTreeSurfaceCodec::new();
208 let draft = codec
209 .decode(cx, &snapshot, intent)
210 .map_err(classify_kernel_error)?;
211 let operation = codec.commit(cx, &draft).map_err(classify_kernel_error)?;
212 cx.require_all(&operation.required_capabilities)
213 .map_err(classify_kernel_error)?;
214 self.commit_surface_operation(cx, session, Some(expected_revision), &operation.form)
215 }
216
217 pub fn close_session(&self, session: &SessionId) -> ServerResult<bool> {
219 let mut registry = self.lock_registry()?;
220 begin_request(&mut registry, self.limits);
221 if registry.in_flight.contains(session) {
222 return Err(session_busy());
223 }
224 Ok(registry.sessions.remove(session).is_some())
225 }
226
227 pub fn subscribe(&self, session: &SessionId) -> ServerResult<WatchId> {
229 let mut registry = self.lock_registry()?;
230 let tick = begin_request(&mut registry, self.limits);
231 let record = session_mut(&mut registry, session)?;
232 record.last_activity_tick = tick;
233 record.subscribe(self.limits)
234 }
235
236 pub fn poll_watch(
238 &self,
239 session: &SessionId,
240 watch: &WatchId,
241 limit: usize,
242 ) -> ServerResult<WatchBatch> {
243 let mut registry = self.lock_registry()?;
244 let tick = begin_request(&mut registry, self.limits);
245 let record = session_mut(&mut registry, session)?;
246 record.last_activity_tick = tick;
247 record.poll_watch(watch, limit.min(self.limits.watch_capacity))
248 }
249
250 pub fn cancel_watch(&self, session: &SessionId, watch: &WatchId) -> ServerResult<()> {
252 let mut registry = self.lock_registry()?;
253 let tick = begin_request(&mut registry, self.limits);
254 let record = session_mut(&mut registry, session)?;
255 record.last_activity_tick = tick;
256 record.cancel_watch(watch)
257 }
258
259 pub fn maintenance_tick(&self, steps: u64) -> ServerResult<usize> {
262 let mut registry = self.lock_registry()?;
263 registry.next_tick = registry.next_tick.saturating_add(steps);
264 let before = registry.sessions.len();
265 expire_idle(&mut registry, self.limits);
266 Ok(before.saturating_sub(registry.sessions.len()))
267 }
268
269 fn wall_observation(&self) -> Option<u64> {
270 self.clock.now().ok().map(|time| time.unix_millis())
271 }
272
273 fn lock_registry(&self) -> ServerResult<MutexGuard<'_, Registry>> {
274 self.registry.lock().map_err(internal)
275 }
276
277 #[cfg(test)]
278 pub(crate) fn registry_is_unlocked_for_test(&self) -> bool {
279 self.registry.try_lock().is_ok()
280 }
281}
282
283impl Default for ExpressionTreeServer {
284 fn default() -> Self {
285 Self::local()
286 }
287}
288
289fn begin_request(registry: &mut Registry, limits: ExpressionTreeServerLimits) -> u64 {
290 let tick = registry.next_tick;
291 registry.next_tick = registry.next_tick.saturating_add(1);
292 expire_idle(registry, limits);
293 tick
294}
295
296fn expire_idle(registry: &mut Registry, limits: ExpressionTreeServerLimits) {
297 let now = registry.next_tick;
298 let Registry {
299 sessions,
300 in_flight,
301 ..
302 } = registry;
303 sessions.retain(|id, session| {
304 in_flight.contains(id)
305 || now.saturating_sub(session.last_activity_tick) <= limits.max_idle_ticks
306 });
307}
308
309fn session_mut<'a>(
310 registry: &'a mut Registry,
311 session: &SessionId,
312) -> ServerResult<&'a mut SessionRecord> {
313 if registry.in_flight.contains(session) {
314 return Err(session_busy());
315 }
316 reserved_session_mut(registry, session)
317}
318
319fn reserved_session_mut<'a>(
320 registry: &'a mut Registry,
321 session: &SessionId,
322) -> ServerResult<&'a mut SessionRecord> {
323 registry.sessions.get_mut(session).ok_or_else(|| {
324 ExpressionTreeServerError::new(
325 "unknown-session",
326 "session is absent, expired, cancelled, or belongs to another server",
327 )
328 })
329}
330
331fn session_busy() -> ExpressionTreeServerError {
332 ExpressionTreeServerError::new(
333 "session-busy",
334 "another operation is already evaluating for this session",
335 )
336}
337
338fn execute_runtime(cx: &mut Cx, target: &RuntimeTarget, operation: &Expr) -> ServerResult<Value> {
339 validate_runtime_target(target, operation)?;
340 let tree = cx
341 .factory()
342 .opaque(Arc::new(target.tree.clone()))
343 .map_err(classify_kernel_error)?;
344 let mut env = Env::child(Arc::new(cx.env().clone()));
345 env.define(target.resource.clone(), tree);
346 cx.with_env(env, |cx| cx.eval_expr(operation.clone()))
347 .map_err(classify_kernel_error)
348}
349
350fn validate_runtime_target(target: &RuntimeTarget, operation: &Expr) -> ServerResult<()> {
351 let Expr::Call { operator, args } = operation else {
352 return Err(ExpressionTreeServerError::new(
353 "invalid-operation",
354 "surface operation must be a local map or expression-tree call",
355 ));
356 };
357 let Expr::Symbol(operator) = operator.as_ref() else {
358 return Err(ExpressionTreeServerError::new(
359 "invalid-operation",
360 "runtime operation must name an expression-tree function",
361 ));
362 };
363 if operator.namespace.as_deref() != Some("expr-tree") {
364 return Err(ExpressionTreeServerError::new(
365 "invalid-operation",
366 "runtime operation is outside the expression-tree family",
367 ));
368 }
369 if !matches!(args.first(), Some(Expr::Symbol(resource)) if resource == &target.resource) {
370 return Err(ExpressionTreeServerError::new(
371 "session-mismatch",
372 "runtime operation targets another expression-tree session",
373 ));
374 }
375 Ok(())
376}
377
378fn operation_metadata(operation: &Expr) -> ServerResult<(String, Option<String>)> {
379 if let Some(op) = protocol::operation(operation) {
380 let path = access::field_str(operation, "path").map(str::to_owned);
381 return Ok((op.name.to_string(), path));
382 }
383 let Expr::Call { operator, args } = operation else {
384 return Err(ExpressionTreeServerError::new(
385 "invalid-operation",
386 "operation is neither a map nor a call",
387 ));
388 };
389 let Expr::Symbol(operator) = operator.as_ref() else {
390 return Err(ExpressionTreeServerError::new(
391 "invalid-operation",
392 "operation call has a non-symbol operator",
393 ));
394 };
395 let path = args.get(1).and_then(|arg| match arg {
396 Expr::String(path) => Some(path.clone()),
397 _ => None,
398 });
399 Ok((operator.name.to_string(), path))
400}
401
402fn is_surface_local(operation: &Expr) -> bool {
403 protocol::operation(operation)
404 .is_some_and(|op| op.namespace.as_deref() == Some("expr-tree-view"))
405}
406
407fn is_revision_change(kind: &str) -> bool {
408 !matches!(kind, "ref" | "list" | "status" | "explain" | "open-policy")
409}
410
411fn target_session(expr: &Expr) -> Option<SessionId> {
412 let Expr::Call { operator, args } = expr else {
413 return None;
414 };
415 let Expr::Symbol(operator) = operator.as_ref() else {
416 return None;
417 };
418 if operator.namespace.as_deref() != Some("expr-tree") {
419 return None;
420 }
421 match args.first() {
422 Some(Expr::Symbol(resource)) => SessionId::from_resource(resource),
423 _ => None,
424 }
425}
426
427fn snapshot_revision(snapshot: &Expr) -> ServerResult<u64> {
428 protocol::uint(snapshot, "revision").map_err(|_| {
429 ExpressionTreeServerError::new(
430 "invalid-expected-revision",
431 "expected-current is not an expression-tree snapshot",
432 )
433 })
434}
435
436fn stale(expected: u64, current: u64) -> ExpressionTreeServerError {
437 ExpressionTreeServerError::new(
438 "stale-revision",
439 format!("expected revision {expected}, current revision is {current}"),
440 )
441}
442
443fn classify_kernel_error(error: Error) -> ExpressionTreeServerError {
444 match error {
445 Error::CapabilityDenied { capability } => ExpressionTreeServerError::new(
446 "authority-denied",
447 format!("caller lacks capability {capability}"),
448 ),
449 Error::TrustDenied { capability, .. } => ExpressionTreeServerError::new(
450 "trust-denied",
451 format!("caller trust does not permit capability {capability}"),
452 ),
453 other => ExpressionTreeServerError::new("operation-failed", other.to_string()),
454 }
455}