1use std::{
4 collections::BTreeMap,
5 sync::{Arc, Condvar, Mutex, MutexGuard},
6 thread::ThreadId,
7};
8
9use sim_kernel::{CapabilityName, Cx, Dir, Error, Event, Expr, Result, Symbol};
10use sim_lib_binding::BindingCell;
11use sim_lib_core::{
12 ReadEvalBroker, ReadEvalDecision, ReadEvalRequest, ReadEvalSource, RequestOrigin,
13 SourceAuthority,
14};
15use sim_shape::AnyShape;
16
17use crate::{IdentitySpecifierPolicy, ModuleSpecifierPolicy, SpecifierPolicyRequest};
18
19pub fn module_load_capability() -> CapabilityName {
21 CapabilityName::new("namespace.module.load")
22}
23
24#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
26pub struct ModuleIdentity {
27 root: Symbol,
28 path: String,
29}
30
31impl ModuleIdentity {
32 pub fn root(&self) -> &Symbol {
34 &self.root
35 }
36 pub fn path(&self) -> &str {
38 &self.path
39 }
40}
41
42pub struct ModuleRequest {
44 pub root_id: Symbol,
46 pub root: Arc<dyn Dir>,
48 pub importer: Option<ModuleIdentity>,
50 pub specifier: String,
52 pub codec: Symbol,
54 pub authority: SourceAuthority,
56}
57
58impl ModuleRequest {
59 pub fn new(
61 root_id: Symbol,
62 root: Arc<dyn Dir>,
63 importer: Option<ModuleIdentity>,
64 specifier: String,
65 codec: Symbol,
66 authority: SourceAuthority,
67 ) -> Self {
68 Self {
69 root_id,
70 root,
71 importer,
72 specifier,
73 codec,
74 authority,
75 }
76 }
77}
78
79#[derive(Clone, Debug)]
81pub struct ModuleInstance {
82 identity: ModuleIdentity,
83 generation: u64,
84 default_export: BindingCell,
85}
86
87impl ModuleInstance {
88 pub fn identity(&self) -> &ModuleIdentity {
90 &self.identity
91 }
92 pub fn generation(&self) -> u64 {
94 self.generation
95 }
96 pub fn default_export(&self) -> &BindingCell {
98 &self.default_export
99 }
100}
101
102#[derive(Clone, Copy, Debug, PartialEq, Eq)]
104pub enum ModuleResolutionOutcome {
105 Linked,
107 CacheHit,
109 ReadRefused,
111 DecodeFailed,
113 EvalFailed,
115 Cycle,
117}
118
119#[derive(Clone, Debug, PartialEq, Eq)]
121pub struct ModuleResolutionReceipt {
122 pub identity: ModuleIdentity,
124 pub generation: u64,
126 pub outcome: ModuleResolutionOutcome,
128 pub detail: Option<String>,
130 pub read_eval_event: Option<Event>,
132}
133
134enum CacheState {
135 Initializing {
136 owner: ThreadId,
137 generation: u64,
138 },
139 Linked(ModuleInstance),
140 Failed {
141 generation: u64,
142 message: String,
143 binding: BindingCell,
144 outcome: ModuleResolutionOutcome,
145 },
146}
147
148#[derive(Default)]
149struct LoaderState {
150 cache: BTreeMap<ModuleIdentity, CacheState>,
151 receipts: Vec<ModuleResolutionReceipt>,
152}
153
154pub struct ModuleLoader {
156 state: Mutex<LoaderState>,
157 changed: Condvar,
158 broker: ReadEvalBroker,
159 specifier_policy: Arc<dyn ModuleSpecifierPolicy>,
160}
161
162impl Default for ModuleLoader {
163 fn default() -> Self {
164 Self {
165 state: Mutex::new(LoaderState::default()),
166 changed: Condvar::new(),
167 broker: ReadEvalBroker::default(),
168 specifier_policy: Arc::new(IdentitySpecifierPolicy),
169 }
170 }
171}
172
173impl ModuleLoader {
174 pub fn new() -> Self {
176 Self::default()
177 }
178
179 pub fn with_specifier_policy(specifier_policy: Arc<dyn ModuleSpecifierPolicy>) -> Self {
181 Self {
182 specifier_policy,
183 ..Self::default()
184 }
185 }
186
187 pub fn load(&self, cx: &mut Cx, request: ModuleRequest) -> Result<ModuleInstance> {
189 cx.require(&module_load_capability())?;
190 let identity = canonical_identity(&request, self.specifier_policy.as_ref())?;
191 let owner = std::thread::current().id();
192 let (generation, binding) = loop {
193 let mut state = self.lock_state()?;
194 match state.cache.get(&identity) {
195 Some(CacheState::Linked(instance)) => {
196 let instance = instance.clone();
197 push_receipt(
198 &mut state,
199 &identity,
200 instance.generation,
201 ModuleResolutionOutcome::CacheHit,
202 None,
203 None,
204 );
205 return Ok(instance);
206 }
207 Some(CacheState::Failed {
208 generation,
209 message,
210 outcome,
211 ..
212 }) => {
213 let generation = *generation;
214 let message = message.clone();
215 let outcome = *outcome;
216 push_receipt(
217 &mut state,
218 &identity,
219 generation,
220 outcome,
221 Some(message.clone()),
222 None,
223 );
224 return Err(Error::Eval(message));
225 }
226 Some(CacheState::Initializing {
227 owner: active,
228 generation,
229 ..
230 }) if *active == owner => {
231 let generation = *generation;
232 let message = format!("module cycle at {}:{}", identity.root, identity.path);
233 push_receipt(
234 &mut state,
235 &identity,
236 generation,
237 ModuleResolutionOutcome::Cycle,
238 Some(message.clone()),
239 None,
240 );
241 return Err(Error::Eval(message));
242 }
243 Some(CacheState::Initializing { .. }) => {
244 drop(
245 self.changed
246 .wait(state)
247 .map_err(|_| Error::PoisonedLock("module loader"))?,
248 );
249 continue;
250 }
251 None => {
252 let binding = BindingCell::uninitialized(Symbol::new(identity.path.clone()));
253 state.cache.insert(
254 identity.clone(),
255 CacheState::Initializing {
256 owner,
257 generation: 1,
258 },
259 );
260 break (1, binding);
261 }
262 }
263 };
264 self.finish_load(cx, request, identity, generation, binding)
265 }
266
267 pub fn reload(&self, cx: &mut Cx, request: ModuleRequest) -> Result<ModuleInstance> {
269 cx.require(&module_load_capability())?;
270 let identity = canonical_identity(&request, self.specifier_policy.as_ref())?;
271 let owner = std::thread::current().id();
272 let (generation, binding) = {
273 let mut state = self.lock_state()?;
274 let (generation, binding) = match state.cache.remove(&identity) {
275 Some(CacheState::Linked(instance)) => {
276 (instance.generation + 1, instance.default_export)
277 }
278 Some(CacheState::Failed {
279 generation,
280 binding,
281 ..
282 }) => (generation + 1, binding),
283 Some(initializing @ CacheState::Initializing { .. }) => {
284 state.cache.insert(identity.clone(), initializing);
285 return Err(Error::Eval(format!(
286 "cannot replace initializing module {}:{}",
287 identity.root, identity.path
288 )));
289 }
290 None => (
291 1,
292 BindingCell::uninitialized(Symbol::new(identity.path.clone())),
293 ),
294 };
295 state.cache.insert(
296 identity.clone(),
297 CacheState::Initializing { owner, generation },
298 );
299 (generation, binding)
300 };
301 self.finish_load(cx, request, identity, generation, binding)
302 }
303
304 pub fn receipts(&self) -> Result<Vec<ModuleResolutionReceipt>> {
306 Ok(self.lock_state()?.receipts.clone())
307 }
308
309 pub fn decisions(&self, cx: &Cx) -> Result<Vec<ReadEvalDecision>> {
311 self.broker.decisions(cx)
312 }
313
314 fn finish_load(
315 &self,
316 cx: &mut Cx,
317 request: ModuleRequest,
318 identity: ModuleIdentity,
319 generation: u64,
320 binding: BindingCell,
321 ) -> Result<ModuleInstance> {
322 let admission = match read_source(cx, request.root.as_ref(), &identity.path) {
323 Ok(source) => Some(self.broker.admit_with_event(
324 cx,
325 ReadEvalRequest::new(
326 RequestOrigin::with_detail(
327 Symbol::qualified("namespace", "module"),
328 Expr::String(format!("{}:{}", identity.root, identity.path)),
329 ),
330 request.codec,
331 source,
332 request.authority,
333 Arc::new(AnyShape),
334 ),
335 )?),
336 Err(error) => {
337 return self.finish_refused(identity, generation, binding, error);
338 }
339 };
340 let admission = admission.expect("successful read creates an admission");
341 let outcome = match admission.decision.outcome {
342 sim_lib_core::ReadEvalOutcome::DecodeFailed => ModuleResolutionOutcome::DecodeFailed,
343 sim_lib_core::ReadEvalOutcome::Admitted => ModuleResolutionOutcome::Linked,
344 _ => ModuleResolutionOutcome::EvalFailed,
345 };
346 let event = admission.event;
347 let result = admission.result;
348 let mut state = self.lock_state()?;
349 match result {
350 Ok(value) => {
351 binding.set(value)?;
352 let instance = ModuleInstance {
353 identity: identity.clone(),
354 generation,
355 default_export: binding,
356 };
357 state
358 .cache
359 .insert(identity.clone(), CacheState::Linked(instance.clone()));
360 push_receipt(
361 &mut state,
362 &identity,
363 generation,
364 ModuleResolutionOutcome::Linked,
365 None,
366 Some(event),
367 );
368 self.changed.notify_all();
369 Ok(instance)
370 }
371 Err(error) => {
372 let message = error.to_string();
373 state.cache.insert(
374 identity.clone(),
375 CacheState::Failed {
376 generation,
377 message: message.clone(),
378 binding,
379 outcome,
380 },
381 );
382 push_receipt(
383 &mut state,
384 &identity,
385 generation,
386 outcome,
387 Some(message.clone()),
388 Some(event),
389 );
390 self.changed.notify_all();
391 Err(Error::Eval(message))
392 }
393 }
394 }
395
396 fn finish_refused(
397 &self,
398 identity: ModuleIdentity,
399 generation: u64,
400 binding: BindingCell,
401 error: Error,
402 ) -> Result<ModuleInstance> {
403 let message = error.to_string();
404 let mut state = self.lock_state()?;
405 state.cache.insert(
406 identity.clone(),
407 CacheState::Failed {
408 generation,
409 message: message.clone(),
410 binding,
411 outcome: ModuleResolutionOutcome::ReadRefused,
412 },
413 );
414 push_receipt(
415 &mut state,
416 &identity,
417 generation,
418 ModuleResolutionOutcome::ReadRefused,
419 Some(message.clone()),
420 None,
421 );
422 self.changed.notify_all();
423 Err(Error::Eval(message))
424 }
425
426 fn lock_state(&self) -> Result<MutexGuard<'_, LoaderState>> {
427 self.state
428 .lock()
429 .map_err(|_| Error::PoisonedLock("module loader"))
430 }
431}
432
433pub struct SourceModulePolicy {
439 loader: ModuleLoader,
440 codec: Symbol,
441}
442
443impl SourceModulePolicy {
444 pub fn new(codec: Symbol, specifier_policy: Arc<dyn ModuleSpecifierPolicy>) -> Self {
446 Self {
447 loader: ModuleLoader::with_specifier_policy(specifier_policy),
448 codec,
449 }
450 }
451
452 pub fn load(
454 &self,
455 cx: &mut Cx,
456 root_id: Symbol,
457 root: Arc<dyn Dir>,
458 specifier: impl Into<String>,
459 authority: SourceAuthority,
460 ) -> Result<ModuleInstance> {
461 self.load_from(cx, root_id, root, None, specifier, authority)
462 }
463
464 pub fn load_from(
466 &self,
467 cx: &mut Cx,
468 root_id: Symbol,
469 root: Arc<dyn Dir>,
470 importer: Option<ModuleIdentity>,
471 specifier: impl Into<String>,
472 authority: SourceAuthority,
473 ) -> Result<ModuleInstance> {
474 self.loader.load(
475 cx,
476 ModuleRequest::new(
477 root_id,
478 root,
479 importer,
480 specifier.into(),
481 self.codec.clone(),
482 authority,
483 ),
484 )
485 }
486
487 pub fn dynamic_import(
489 &self,
490 cx: &mut Cx,
491 root_id: Symbol,
492 root: Arc<dyn Dir>,
493 importer: Option<ModuleIdentity>,
494 specifier: impl Into<String>,
495 authority: SourceAuthority,
496 ) -> Result<ModuleInstance> {
497 self.load_from(cx, root_id, root, importer, specifier, authority)
498 }
499
500 pub fn reload(
502 &self,
503 cx: &mut Cx,
504 root_id: Symbol,
505 root: Arc<dyn Dir>,
506 importer: Option<ModuleIdentity>,
507 specifier: impl Into<String>,
508 authority: SourceAuthority,
509 ) -> Result<ModuleInstance> {
510 self.loader.reload(
511 cx,
512 ModuleRequest::new(
513 root_id,
514 root,
515 importer,
516 specifier.into(),
517 self.codec.clone(),
518 authority,
519 ),
520 )
521 }
522
523 pub fn receipts(&self) -> Result<Vec<ModuleResolutionReceipt>> {
525 self.loader.receipts()
526 }
527
528 pub fn decisions(&self, cx: &Cx) -> Result<Vec<ReadEvalDecision>> {
530 self.loader.decisions(cx)
531 }
532}
533
534fn canonical_identity(
535 request: &ModuleRequest,
536 policy: &dyn ModuleSpecifierPolicy,
537) -> Result<ModuleIdentity> {
538 let policy_request =
539 SpecifierPolicyRequest::new(request.importer.clone(), vec![request.specifier.clone()])
540 .map_err(|refusal| Error::Eval(refusal.to_string()))?;
541 let specifier = policy
542 .resolve(&policy_request)
543 .map_err(|refusal| Error::Eval(refusal.to_string()))?;
544 let specifier = specifier.as_str();
545 let absolute = specifier.starts_with('/');
546 if absolute {
547 return Err(Error::Eval(
548 "module specifier must be root-relative, not absolute".to_owned(),
549 ));
550 }
551 let mut parts = if specifier.starts_with('.') {
552 let importer = request
553 .importer
554 .as_ref()
555 .ok_or_else(|| Error::Eval("relative module request has no importer".to_owned()))?;
556 if importer.root != request.root_id {
557 return Err(Error::Eval(
558 "relative module request crosses supplied roots".to_owned(),
559 ));
560 }
561 let mut base: Vec<&str> = importer.path.split('/').collect();
562 base.pop();
563 base
564 } else {
565 Vec::new()
566 };
567 for part in specifier.split('/') {
568 match part {
569 "" | "." => {}
570 ".." => {
571 if parts.pop().is_none() {
572 return Err(Error::Eval(
573 "module request escapes supplied root".to_owned(),
574 ));
575 }
576 }
577 component if component.contains('\\') => {
578 return Err(Error::Eval(
579 "module path contains a non-canonical separator".to_owned(),
580 ));
581 }
582 component => parts.push(component),
583 }
584 }
585 if parts.is_empty() {
586 return Err(Error::Eval("module path is empty".to_owned()));
587 }
588 Ok(ModuleIdentity {
589 root: request.root_id.clone(),
590 path: parts.join("/"),
591 })
592}
593
594fn read_source(cx: &mut Cx, root: &dyn Dir, path: &str) -> Result<ReadEvalSource> {
595 let components = path.split('/').collect::<Vec<_>>();
596 read_source_at(cx, root, &components, path)
597}
598
599fn read_source_at(
600 cx: &mut Cx,
601 dir: &dyn Dir,
602 components: &[&str],
603 path: &str,
604) -> Result<ReadEvalSource> {
605 let (component, rest) = components
606 .split_first()
607 .ok_or_else(|| Error::Eval("module path is empty".to_owned()))?;
608 let key = Symbol::new(*component);
609 if rest.is_empty() {
610 if !dir.has(cx, key.clone())? {
611 return Err(Error::Eval(format!("module source not found: {path}")));
612 }
613 let value = dir.get(cx, key)?;
614 return match value.object().as_expr(cx)? {
615 Expr::String(text) => Ok(ReadEvalSource::Text(text)),
616 Expr::Bytes(bytes) => Ok(ReadEvalSource::Bytes(bytes)),
617 _ => Err(Error::Eval(format!(
618 "module source is not text or bytes: {path}"
619 ))),
620 };
621 }
622 let value = dir
623 .opendir(cx, key)?
624 .ok_or_else(|| Error::Eval(format!("module directory not found: {path}")))?;
625 let child = value
626 .object()
627 .as_dir()
628 .ok_or_else(|| Error::Eval(format!("module path component is not a Dir: {component}")))?;
629 read_source_at(cx, child, rest, path)
630}
631
632fn push_receipt(
633 state: &mut LoaderState,
634 identity: &ModuleIdentity,
635 generation: u64,
636 outcome: ModuleResolutionOutcome,
637 detail: Option<String>,
638 read_eval_event: Option<Event>,
639) {
640 state.receipts.push(ModuleResolutionReceipt {
641 identity: identity.clone(),
642 generation,
643 outcome,
644 detail,
645 read_eval_event,
646 });
647}
648
649#[cfg(test)]
650mod tests;