1#![cfg(not(target_arch = "wasm32"))]
2
3use std::cell::RefCell;
4use std::path::{Path, PathBuf};
5use std::rc::Rc;
6use std::sync::{mpsc, Arc};
7
8use crate::core::{map_entries, ExceptionInfo, Promise, TraceFrame, Value};
9use crate::invoke_hta::InvokeHtaError;
10use crate::lang::data::Symbol;
11use crate::lang::protocol::INamespaced;
12use crate::{
13 EvaluationId, InProcessSandboxProvider, Runtime, SandboxId, SandboxSpec, SandboxStatus,
14 SessionId, SessionKernel,
15};
16
17mod arguments;
18mod documentation;
19mod kernel;
20use arguments::{
21 keyword, optional_string as optional_string_argument, string as string_argument,
22 strings as strings_argument, strings_value, tap_value,
23};
24pub use documentation::{Documentation, DocumentationValue};
25use kernel::kernel_call;
26
27const RUNTIME_BROKER_STACK_SIZE: usize = if cfg!(debug_assertions) {
31 64 * 1024 * 1024
32} else {
33 8 * 1024 * 1024
34};
35const MAX_DIAGNOSTIC_DATA_BYTES: usize = 16 * 1024;
36
37#[derive(Clone, Copy)]
38enum RuntimeBootstrap {
39 Full,
40 Core,
41 Source,
42}
43
44enum Request {
45 Eval {
46 session: String,
47 source: String,
48 reply: mpsc::Sender<Result<String, String>>,
49 },
50 EvalDiagnostic {
51 session: String,
52 source: String,
53 reply: mpsc::Sender<Result<String, RuntimeDiagnostic>>,
54 },
55 Namespace {
56 session: String,
57 reply: mpsc::Sender<Result<String, String>>,
58 },
59 Complete {
60 session: String,
61 prefix: String,
62 reply: mpsc::Sender<Result<Vec<String>, String>>,
63 },
64 Doc {
65 session: String,
66 symbol: String,
67 reply: mpsc::Sender<Result<Documentation, String>>,
68 },
69 Create {
70 session: String,
71 reply: mpsc::Sender<Result<String, String>>,
72 },
73 Close {
74 session: String,
75 reply: mpsc::Sender<Result<String, String>>,
76 },
77 List {
78 reply: mpsc::Sender<Result<Vec<String>, String>>,
79 },
80 Info {
81 session: String,
82 reply: mpsc::Sender<Result<String, String>>,
83 },
84 RegisterResource {
85 name: String,
86 source: String,
87 reply: mpsc::Sender<Result<(), String>>,
88 },
89 RemoveResource {
90 name: String,
91 reply: mpsc::Sender<Result<(), String>>,
92 },
93 ListResources {
94 reply: mpsc::Sender<Result<Vec<String>, String>>,
95 },
96 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
97 ConfigureNativeSourceCache {
98 root: PathBuf,
99 source_index_fingerprint: [u8; 32],
100 reply: mpsc::Sender<Result<(), String>>,
101 },
102 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
103 InstallSourceFoundationImage {
104 image: Vec<u8>,
105 reply: mpsc::Sender<Result<(), String>>,
106 },
107 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
108 InstallSourceLanguageSpecImage {
109 image: Vec<u8>,
110 reply: mpsc::Sender<Result<(), String>>,
111 },
112 InstallModule {
113 session: String,
114 manifest: String,
115 module: crate::wasmtime_provider::CompiledWasmModule,
116 reply: mpsc::Sender<Result<String, String>>,
117 },
118 InvokeModule {
119 session: String,
120 namespace: String,
121 export: String,
122 arguments: Vec<u8>,
123 reply: mpsc::Sender<Result<Vec<u8>, String>>,
124 },
125 InvokeHta {
126 session: String,
127 qualified_var: String,
128 arguments: Vec<u8>,
129 reply: mpsc::Sender<Result<Vec<u8>, InvokeHtaError>>,
130 },
131 SandboxOpen {
132 spec: SandboxSpec,
133 reply: mpsc::Sender<Result<SandboxId, String>>,
134 },
135 SandboxEval {
136 sandbox: SandboxId,
137 source: String,
138 started: mpsc::Sender<Result<EvaluationId, String>>,
139 reply: mpsc::Sender<Result<String, String>>,
140 },
141 SandboxCall {
142 sandbox: SandboxId,
143 callable: String,
144 arguments: Vec<u8>,
145 started: mpsc::Sender<Result<EvaluationId, String>>,
146 reply: mpsc::Sender<Result<Vec<u8>, String>>,
147 },
148 SandboxCancel {
149 sandbox: SandboxId,
150 evaluation: Option<EvaluationId>,
151 reply: mpsc::Sender<Result<bool, String>>,
152 },
153 SandboxStatus {
154 sandbox: SandboxId,
155 reply: mpsc::Sender<Result<SandboxStatus, String>>,
156 },
157 SandboxClose {
158 sandbox: SandboxId,
159 reply: mpsc::Sender<Result<(), String>>,
160 },
161 Shutdown,
162}
163
164struct BrokerHandle {
165 sender: mpsc::Sender<Request>,
166}
167
168impl Drop for BrokerHandle {
169 fn drop(&mut self) {
170 let _ = self.sender.send(Request::Shutdown);
171 }
172}
173
174#[derive(Clone)]
175pub struct RuntimeBroker {
176 handle: Arc<BrokerHandle>,
177 root: Option<PathBuf>,
178}
179
180#[derive(Clone, Debug)]
184pub(crate) struct RuntimeDiagnostic {
185 pub message: String,
186 pub exception: Option<RuntimeException>,
187 pub frames: Vec<TraceFrame>,
188}
189
190#[derive(Clone, Debug)]
194pub(crate) struct RuntimeException {
195 pub message: String,
196 pub class: Option<String>,
197 pub code: Option<String>,
198 pub data: String,
199 pub cause: Option<Box<RuntimeException>>,
200 pub throws: Vec<crate::core::ExceptionSite>,
201}
202
203impl RuntimeDiagnostic {
204 fn message(message: String) -> Self {
205 Self {
206 message,
207 exception: None,
208 frames: Vec::new(),
209 }
210 }
211}
212
213fn exception_attribute(exception: &ExceptionInfo, name: &str) -> Option<Value> {
214 let key = Value::Keyword(name.into());
215 map_entries(exception.data.as_ref())?
216 .into_iter()
217 .find_map(|(candidate, value)| (candidate == key).then_some(value))
218}
219
220fn bounded_diagnostic_data(value: String) -> String {
221 if value.len() <= MAX_DIAGNOSTIC_DATA_BYTES {
222 return value;
223 }
224 let mut end = MAX_DIAGNOSTIC_DATA_BYTES.saturating_sub(3);
225 while end > 0 && !value.is_char_boundary(end) {
226 end -= 1;
227 }
228 format!("{}...", &value[..end])
229}
230
231fn exception_snapshot(exception: &ExceptionInfo) -> RuntimeException {
232 let provenance = exception.provenance.borrow();
233 RuntimeException {
234 message: exception.message.clone(),
235 class: exception_attribute(exception, "ex/class").map(|value| value.display()),
236 code: exception_attribute(exception, "ex/code").map(|value| value.display()),
237 data: bounded_diagnostic_data(exception.data.display()),
238 cause: exception.cause.as_deref().and_then(|value| match value {
239 Value::ExceptionInfo(cause) => Some(Box::new(exception_snapshot(cause))),
240 _ => None,
241 }),
242 throws: provenance.throws.clone(),
243 }
244}
245
246fn captured_exception(value: Option<Value>) -> Option<RuntimeException> {
247 match value {
248 Some(Value::ExceptionInfo(exception)) => Some(exception_snapshot(&exception)),
249 _ => None,
250 }
251}
252
253impl RuntimeBroker {
254 pub fn start() -> Result<Self, String> {
255 Self::start_with_bootstrap(None, false, false, false, RuntimeBootstrap::Full)
256 }
257
258 pub fn start_core() -> Result<Self, String> {
263 Self::start_with_bootstrap(None, false, false, false, RuntimeBootstrap::Core)
264 }
265
266 pub fn start_with(
267 root: Option<PathBuf>,
268 native_sockets: bool,
269 allow_process: bool,
270 allow_postgres: bool,
271 ) -> Result<Self, String> {
272 Self::start_with_bootstrap(
273 root,
274 native_sockets,
275 allow_process,
276 allow_postgres,
277 RuntimeBootstrap::Full,
278 )
279 }
280
281 pub fn start_with_backend(
286 root: Option<PathBuf>,
287 native_sockets: bool,
288 allow_process: bool,
289 allow_postgres: bool,
290 execution_backend: &str,
291 ) -> Result<Self, String> {
292 Self::start_with_bootstrap_and_backend(
293 root,
294 native_sockets,
295 allow_process,
296 allow_postgres,
297 RuntimeBootstrap::Full,
298 execution_backend,
299 )
300 }
301
302 pub fn start_with_backend_and_source_catalog(
305 root: Option<PathBuf>,
306 native_sockets: bool,
307 allow_process: bool,
308 allow_postgres: bool,
309 execution_backend: &str,
310 source_catalog: crate::project::SourceCatalog,
311 ) -> Result<Self, String> {
312 Self::start_with_bootstrap_and_backend_and_catalog(
313 root,
314 native_sockets,
315 allow_process,
316 allow_postgres,
317 RuntimeBootstrap::Full,
318 execution_backend,
319 Some(source_catalog),
320 )
321 }
322
323 pub fn start_with_source_catalog(
327 root: Option<PathBuf>,
328 native_sockets: bool,
329 allow_process: bool,
330 allow_postgres: bool,
331 execution_backend: &str,
332 source_catalog: crate::project::SourceCatalog,
333 ) -> Result<Self, String> {
334 Self::start_with_bootstrap_and_backend_and_catalog(
335 root,
336 native_sockets,
337 allow_process,
338 allow_postgres,
339 RuntimeBootstrap::Source,
340 execution_backend,
341 Some(source_catalog),
342 )
343 }
344
345 pub fn start_core_with_backend_and_source_catalog(
350 root: Option<PathBuf>,
351 native_sockets: bool,
352 allow_process: bool,
353 allow_postgres: bool,
354 execution_backend: &str,
355 source_catalog: crate::project::SourceCatalog,
356 ) -> Result<Self, String> {
357 Self::start_with_bootstrap_and_backend_and_catalog(
358 root,
359 native_sockets,
360 allow_process,
361 allow_postgres,
362 RuntimeBootstrap::Core,
363 execution_backend,
364 Some(source_catalog),
365 )
366 }
367
368 fn start_with_bootstrap(
369 root: Option<PathBuf>,
370 native_sockets: bool,
371 allow_process: bool,
372 allow_postgres: bool,
373 bootstrap: RuntimeBootstrap,
374 ) -> Result<Self, String> {
375 Self::start_with_bootstrap_and_backend(
376 root,
377 native_sockets,
378 allow_process,
379 allow_postgres,
380 bootstrap,
381 "interpreter",
382 )
383 }
384
385 fn start_with_bootstrap_and_backend(
386 root: Option<PathBuf>,
387 native_sockets: bool,
388 allow_process: bool,
389 allow_postgres: bool,
390 bootstrap: RuntimeBootstrap,
391 execution_backend: &str,
392 ) -> Result<Self, String> {
393 Self::start_with_bootstrap_and_backend_and_catalog(
394 root,
395 native_sockets,
396 allow_process,
397 allow_postgres,
398 bootstrap,
399 execution_backend,
400 None,
401 )
402 }
403
404 fn start_with_bootstrap_and_backend_and_catalog(
405 root: Option<PathBuf>,
406 native_sockets: bool,
407 allow_process: bool,
408 allow_postgres: bool,
409 bootstrap: RuntimeBootstrap,
410 execution_backend: &str,
411 source_catalog: Option<crate::project::SourceCatalog>,
412 ) -> Result<Self, String> {
413 crate::validate_execution_backend(execution_backend)?;
414 if allow_postgres {
415 return Err(
416 "PostgreSQL support is not included in the core hara-native crate".to_owned(),
417 );
418 }
419 let execution_backend = execution_backend.to_owned();
420 let (sender, receiver) = mpsc::channel();
421 let runtime_root = root.clone();
422 std::thread::Builder::new()
423 .name("hara-runtime-broker".into())
424 .stack_size(RUNTIME_BROKER_STACK_SIZE)
425 .spawn(move || {
426 run(
427 receiver,
428 runtime_root,
429 native_sockets,
430 allow_process,
431 allow_postgres,
432 bootstrap,
433 execution_backend,
434 source_catalog,
435 )
436 })
437 .map_err(|error| format!("runtime broker failed: {error}"))?;
438 Ok(Self {
439 handle: Arc::new(BrokerHandle { sender }),
440 root,
441 })
442 }
443
444 pub(super) fn root(&self) -> Option<&Path> {
445 self.root.as_deref()
446 }
447
448 pub fn eval(&self, session: &str, source: &str) -> Result<String, String> {
449 self.call(|reply| Request::Eval {
450 session: session.into(),
451 source: source.into(),
452 reply,
453 })
454 }
455
456 pub(crate) fn eval_diagnostic(
457 &self,
458 session: &str,
459 source: &str,
460 ) -> Result<String, RuntimeDiagnostic> {
461 let (reply, response) = mpsc::channel();
462 self.handle
463 .sender
464 .send(Request::EvalDiagnostic {
465 session: session.into(),
466 source: source.into(),
467 reply,
468 })
469 .map_err(|_| RuntimeDiagnostic::message("runtime broker is closed".into()))?;
470 response.recv().map_err(|_| {
471 RuntimeDiagnostic::message("runtime broker stopped without a response".into())
472 })?
473 }
474
475 pub fn namespace(&self, session: &str) -> Result<String, String> {
476 self.call(|reply| Request::Namespace {
477 session: session.into(),
478 reply,
479 })
480 }
481
482 pub fn complete(&self, session: &str, prefix: &str) -> Result<Vec<String>, String> {
483 self.call(|reply| Request::Complete {
484 session: session.into(),
485 prefix: prefix.into(),
486 reply,
487 })
488 }
489
490 pub fn documentation(&self, session: &str, symbol: &str) -> Result<Documentation, String> {
491 self.call(|reply| Request::Doc {
492 session: session.into(),
493 symbol: symbol.into(),
494 reply,
495 })
496 }
497
498 pub fn create(&self, session: &str) -> Result<String, String> {
499 self.call(|reply| Request::Create {
500 session: session.into(),
501 reply,
502 })
503 }
504
505 pub fn close(&self, session: &str) -> Result<String, String> {
506 self.call(|reply| Request::Close {
507 session: session.into(),
508 reply,
509 })
510 }
511
512 pub fn list(&self) -> Result<Vec<String>, String> {
513 self.call(|reply| Request::List { reply })
514 }
515
516 pub fn info(&self, session: &str) -> Result<String, String> {
517 self.call(|reply| Request::Info {
518 session: session.into(),
519 reply,
520 })
521 }
522
523 pub fn register_resource(&self, name: &str, source: &str) -> Result<(), String> {
524 self.call(|reply| Request::RegisterResource {
525 name: name.into(),
526 source: source.into(),
527 reply,
528 })
529 }
530
531 pub fn remove_resource(&self, name: &str) -> Result<(), String> {
532 self.call(|reply| Request::RemoveResource {
533 name: name.into(),
534 reply,
535 })
536 }
537
538 pub fn resources(&self) -> Result<Vec<String>, String> {
539 self.call(|reply| Request::ListResources { reply })
540 }
541
542 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
547 pub fn configure_native_source_cache(
548 &self,
549 root: &Path,
550 source_index_fingerprint: [u8; 32],
551 ) -> Result<(), String> {
552 self.call(|reply| Request::ConfigureNativeSourceCache {
553 root: root.to_path_buf(),
554 source_index_fingerprint,
555 reply,
556 })
557 }
558
559 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
560 pub fn install_source_foundation_image(&self, image: &[u8]) -> Result<(), String> {
561 self.call(|reply| Request::InstallSourceFoundationImage {
562 image: image.to_vec(),
563 reply,
564 })
565 }
566
567 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
568 pub fn install_source_language_spec_image(&self, image: &[u8]) -> Result<(), String> {
569 self.call(|reply| Request::InstallSourceLanguageSpecImage {
570 image: image.to_vec(),
571 reply,
572 })
573 }
574
575 pub fn install_module(
576 &self,
577 session: &str,
578 manifest: &str,
579 module: &crate::wasmtime_provider::CompiledWasmModule,
580 ) -> Result<String, String> {
581 self.call(|reply| Request::InstallModule {
582 session: session.into(),
583 manifest: manifest.into(),
584 module: module.clone(),
585 reply,
586 })
587 }
588
589 pub fn invoke_hta(
590 &self,
591 session: &str,
592 qualified_var: &str,
593 arguments: &[u8],
594 ) -> Result<Vec<u8>, InvokeHtaError> {
595 let (reply, response) = mpsc::channel();
596 self.handle
597 .sender
598 .send(Request::InvokeHta {
599 session: session.into(),
600 qualified_var: qualified_var.into(),
601 arguments: arguments.into(),
602 reply,
603 })
604 .map_err(|_| InvokeHtaError::BrokerClosed)?;
605 response.recv().map_err(|_| InvokeHtaError::BrokerStopped)?
606 }
607
608 pub fn invoke_module(
609 &self,
610 session: &str,
611 namespace: &str,
612 export: &str,
613 arguments: &[u8],
614 ) -> Result<Vec<u8>, String> {
615 self.call(|reply| Request::InvokeModule {
616 session: session.into(),
617 namespace: namespace.into(),
618 export: export.into(),
619 arguments: arguments.into(),
620 reply,
621 })
622 }
623
624 fn sandbox_open(&self, spec: SandboxSpec) -> Result<SandboxId, String> {
625 self.call(|reply| Request::SandboxOpen { spec, reply })
626 }
627
628 fn sandbox_eval_receiver(
629 &self,
630 sandbox: SandboxId,
631 source: &str,
632 ) -> Result<(EvaluationId, mpsc::Receiver<Result<String, String>>), String> {
633 let (reply, response) = mpsc::channel();
634 let (started_reply, started_response) = mpsc::channel();
635 self.handle
636 .sender
637 .send(Request::SandboxEval {
638 sandbox,
639 source: source.into(),
640 started: started_reply,
641 reply,
642 })
643 .map_err(|_| "runtime broker is closed".to_owned())?;
644 let evaluation = started_response.recv().map_err(|_| {
645 "runtime broker stopped before starting sandbox evaluation".to_owned()
646 })??;
647 Ok((evaluation, response))
648 }
649
650 fn sandbox_call_receiver(
651 &self,
652 sandbox: SandboxId,
653 callable: &str,
654 arguments: &[u8],
655 ) -> Result<(EvaluationId, mpsc::Receiver<Result<Vec<u8>, String>>), String> {
656 let (reply, response) = mpsc::channel();
657 let (started_reply, started_response) = mpsc::channel();
658 self.handle
659 .sender
660 .send(Request::SandboxCall {
661 sandbox,
662 callable: callable.into(),
663 arguments: arguments.into(),
664 started: started_reply,
665 reply,
666 })
667 .map_err(|_| "runtime broker is closed".to_owned())?;
668 let evaluation = started_response
669 .recv()
670 .map_err(|_| "runtime broker stopped before starting sandbox call".to_owned())??;
671 Ok((evaluation, response))
672 }
673
674 fn sandbox_cancel(&self, sandbox: SandboxId) -> Result<bool, String> {
675 self.call(|reply| Request::SandboxCancel {
676 sandbox,
677 evaluation: None,
678 reply,
679 })
680 }
681
682 fn sandbox_cancel_evaluation(
683 &self,
684 sandbox: SandboxId,
685 evaluation: EvaluationId,
686 ) -> Result<bool, String> {
687 self.call(|reply| Request::SandboxCancel {
688 sandbox,
689 evaluation: Some(evaluation),
690 reply,
691 })
692 }
693
694 fn sandbox_status(&self, sandbox: SandboxId) -> Result<SandboxStatus, String> {
695 self.call(|reply| Request::SandboxStatus { sandbox, reply })
696 }
697
698 fn sandbox_close(&self, sandbox: SandboxId) -> Result<(), String> {
699 self.call(|reply| Request::SandboxClose { sandbox, reply })
700 }
701
702 fn call<T>(
703 &self,
704 request: impl FnOnce(mpsc::Sender<Result<T, String>>) -> Request,
705 ) -> Result<T, String> {
706 let (reply, response) = mpsc::channel();
707 self.handle
708 .sender
709 .send(request(reply))
710 .map_err(|_| "runtime broker is closed".to_owned())?;
711 response
712 .recv()
713 .map_err(|_| "runtime broker stopped without a response".to_owned())?
714 }
715}
716
717fn runtime(
718 root: Option<&PathBuf>,
719 native_sockets: bool,
720 allow_process: bool,
721 allow_postgres: bool,
722 bootstrap: RuntimeBootstrap,
723 execution_backend: &str,
724 source_catalog: Option<&crate::project::SourceCatalog>,
725) -> Runtime {
726 let mut runtime = match bootstrap {
727 RuntimeBootstrap::Full => Runtime::new(),
728 RuntimeBootstrap::Core | RuntimeBootstrap::Source => Runtime::core(),
729 };
730 if let Some(source_catalog) = source_catalog {
731 runtime.register_source_catalog(source_catalog);
732 }
733 if matches!(bootstrap, RuntimeBootstrap::Source) {
734 runtime
735 .bootstrap_source_foundation()
736 .expect("source Foundation bootstrap must be valid");
737 }
738 if let Some(root) = root {
739 runtime.install_native_file_provider(root.to_string_lossy().as_ref());
740 }
741 if native_sockets {
742 runtime.install_native_socket_provider();
743 }
744 if allow_process {
745 runtime.install_native_process_provider();
746 }
747 runtime
751 .configure_execution_backend(execution_backend)
752 .expect("validated execution backend must configure");
753 let _ = allow_postgres;
754 runtime
755}
756
757fn run(
758 receiver: mpsc::Receiver<Request>,
759 root: Option<PathBuf>,
760 native_sockets: bool,
761 allow_process: bool,
762 allow_postgres: bool,
763 bootstrap: RuntimeBootstrap,
764 execution_backend: String,
765 source_catalog: Option<crate::project::SourceCatalog>,
766) {
767 let runtime_root = root.clone();
768 let runtime_backend = execution_backend.clone();
769 let runtime_catalog = source_catalog;
770 let runtime_factory: Rc<dyn Fn() -> Runtime> = Rc::new(move || {
771 runtime(
772 runtime_root.as_ref(),
773 native_sockets,
774 allow_process,
775 allow_postgres,
776 bootstrap,
777 &runtime_backend,
778 runtime_catalog.as_ref(),
779 )
780 });
781 let root_runtime = runtime_factory();
782 let mut kernel = SessionKernel::with_runtime_factory(root_runtime, runtime_factory);
783 kernel.register_sandbox_provider(Rc::new(InProcessSandboxProvider));
784 while let Ok(request) = receiver.recv() {
785 match request {
786 Request::Eval {
787 session,
788 source,
789 reply,
790 } => {
791 let result = broker_session_id(&session).and_then(|id| {
792 let runtime = kernel.session_mut(&id)?.runtime_mut()?;
793 if execution_backend == "direct-native" {
794 runtime.eval_native(&source)
795 } else {
796 runtime.eval_native_traced(&source)
797 }
798 });
799 let _ = reply.send(result);
800 }
801 Request::EvalDiagnostic {
802 session,
803 source,
804 reply,
805 } => {
806 let result = broker_session_id(&session)
807 .map_err(RuntimeDiagnostic::message)
808 .and_then(|id| {
809 let runtime = kernel
810 .session_mut(&id)
811 .and_then(|session| session.runtime_mut())
812 .map_err(RuntimeDiagnostic::message)?;
813 let ((result, frames), exception) = runtime.eval_native_diagnostic(&source);
814 result.map_err(|message| RuntimeDiagnostic {
815 message,
816 exception: captured_exception(exception),
817 frames,
818 })
819 });
820 let _ = reply.send(result);
821 }
822 Request::Namespace { session, reply } => {
823 let result =
824 broker_session_id(&session).and_then(|id| kernel.session_namespace(&id));
825 let _ = reply.send(result);
826 }
827 Request::Complete {
828 session,
829 prefix,
830 reply,
831 } => {
832 let result = broker_session_id(&session).and_then(|id| {
833 kernel.session(&id)?.runtime().map(|runtime| {
834 let mut symbols = runtime
835 .visible_symbols()
836 .into_iter()
837 .filter(|symbol| symbol.starts_with(&prefix))
838 .collect::<Vec<_>>();
839 symbols.dedup();
840 symbols
841 })
842 });
843 let _ = reply.send(result);
844 }
845 Request::Doc {
846 session,
847 symbol,
848 reply,
849 } => {
850 let result = broker_session_id(&session)
851 .and_then(|id| documentation(kernel.session(&id)?.runtime()?, &symbol));
852 let _ = reply.send(result);
853 }
854 Request::Create { session, reply } => {
855 let result = SessionId::parse(&session)
856 .map_err(|_| format!("Session already exists or is invalid: {session}"))
857 .and_then(|id| {
858 kernel
859 .create_session(id)
860 .map_err(|_| format!("Session already exists or is invalid: {session}"))
861 })
862 .map(|_| session);
863 let _ = reply.send(result);
864 }
865 Request::Close { session, reply } => {
866 let result = broker_session_id(&session)
867 .and_then(|id| kernel.close_session(&id))
868 .map(|_| session)
869 .map_err(|error| match error.as_str() {
870 "ROOT_CANNOT_CLOSE" => "ROOT cannot be closed".into(),
871 _ if error.starts_with("NO_SESSION ") => {
872 format!("No session: {}", error.trim_start_matches("NO_SESSION "))
873 }
874 _ => error,
875 });
876 let _ = reply.send(result);
877 }
878 Request::List { reply } => {
879 let names = kernel
880 .session_names()
881 .into_iter()
882 .map(|id| id.to_string())
883 .collect();
884 let _ = reply.send(Ok(names));
885 }
886 Request::Info { session, reply } => {
887 let result = broker_session_id(&session)
888 .and_then(|id| kernel.session_namespace(&id))
889 .map(|namespace| format!("{session} {namespace}"));
890 let _ = reply.send(result);
891 }
892 Request::RegisterResource {
893 name,
894 source,
895 reply,
896 } => {
897 kernel.register_resource(&name, &source);
898 let _ = reply.send(Ok(()));
899 }
900 Request::RemoveResource { name, reply } => {
901 kernel.remove_resource(&name);
902 let _ = reply.send(Ok(()));
903 }
904 Request::ListResources { reply } => {
905 let _ = reply.send(Ok(kernel.resource_names()));
906 }
907 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
908 Request::ConfigureNativeSourceCache {
909 root,
910 source_index_fingerprint,
911 reply,
912 } => {
913 kernel.configure_native_source_cache(&root, source_index_fingerprint);
914 let _ = reply.send(Ok(()));
915 }
916 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
917 Request::InstallSourceFoundationImage { image, reply } => {
918 let _ = reply.send(kernel.install_source_foundation_image(&image));
919 }
920 #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
921 Request::InstallSourceLanguageSpecImage { image, reply } => {
922 let _ = reply.send(kernel.install_source_language_spec_image(&image));
923 }
924 Request::InstallModule {
925 session,
926 manifest,
927 module,
928 reply,
929 } => {
930 let result = broker_session_id(&session).and_then(|id| {
931 let runtime = kernel.session_mut(&id)?.runtime_mut()?;
932 let provider = module.provider();
933 let parsed =
934 crate::extension::ExtensionManifest::parse(&manifest, "MODULE PUT")?;
935 let namespace = parsed.namespace.clone();
936 runtime.install_wasm_extension(&manifest, "MODULE PUT", provider)?;
937 Ok(namespace)
938 });
939 let _ = reply.send(result);
940 }
941 Request::InvokeModule {
942 session,
943 namespace,
944 export,
945 arguments,
946 reply,
947 } => {
948 let result = broker_session_id(&session).and_then(|id| {
949 let runtime = kernel.session_mut(&id)?.runtime_mut()?;
950 let arguments = crate::hta::decode(&arguments)?;
951 let arguments: Vec<crate::extension::Value> = match arguments {
952 crate::extension::Value::Vector(values) => values.iter().cloned().collect(),
953 crate::extension::Value::Tuple(values) => values.iter().cloned().collect(),
954 other => {
955 return Err(format!(
956 "hta/arguments: expected vector, got {}",
957 other.display()
958 ))
959 }
960 };
961 let result = runtime.invoke_wasm_extension(&namespace, &export, &arguments)?;
962 crate::hta::encode(&result)
963 });
964 let _ = reply.send(result);
965 }
966 Request::InvokeHta {
967 session,
968 qualified_var,
969 arguments,
970 reply,
971 } => {
972 let result = SessionId::parse(&session)
973 .map_err(|_| InvokeHtaError::SessionMissing(session.clone()))
974 .and_then(|id| {
975 kernel
976 .session_mut(&id)
977 .map_err(|_| InvokeHtaError::SessionMissing(session.clone()))?
978 .runtime_mut()
979 .map_err(InvokeHtaError::Execution)?
980 .invoke_hta(&qualified_var, &arguments)
981 });
982 let _ = reply.send(result);
983 }
984 Request::SandboxOpen { spec, reply } => {
985 let _ = reply.send(kernel.open_sandbox(spec).map_err(|error| error.to_string()));
986 }
987 Request::SandboxEval {
988 sandbox,
989 source,
990 started,
991 reply,
992 } => match kernel.sandbox_eval(sandbox, &source) {
993 Ok(pending) => {
994 let _ = started.send(Ok(pending.evaluation()));
995 std::thread::spawn(move || {
996 let _ = reply.send(pending.wait().map_err(|error| error.to_string()));
997 });
998 }
999 Err(error) => {
1000 let error = error.to_string();
1001 let _ = started.send(Err(error.clone()));
1002 let _ = reply.send(Err(error));
1003 }
1004 },
1005 Request::SandboxCall {
1006 sandbox,
1007 callable,
1008 arguments,
1009 started,
1010 reply,
1011 } => match kernel.sandbox_call(sandbox, &callable, &arguments) {
1012 Ok(pending) => {
1013 let _ = started.send(Ok(pending.evaluation()));
1014 std::thread::spawn(move || {
1015 let _ = reply.send(pending.wait().map_err(|error| error.to_string()));
1016 });
1017 }
1018 Err(error) => {
1019 let error = error.to_string();
1020 let _ = started.send(Err(error.clone()));
1021 let _ = reply.send(Err(error));
1022 }
1023 },
1024 Request::SandboxCancel {
1025 sandbox,
1026 evaluation,
1027 reply,
1028 } => {
1029 let result = match evaluation {
1030 Some(evaluation) => kernel.cancel_sandbox_evaluation(sandbox, evaluation),
1031 None => kernel.cancel_sandbox(sandbox),
1032 };
1033 let _ = reply.send(result.map_err(|error| error.to_string()));
1034 }
1035 Request::SandboxStatus { sandbox, reply } => {
1036 let _ = reply.send(
1037 kernel
1038 .sandbox_status(sandbox)
1039 .map_err(|error| error.to_string()),
1040 );
1041 }
1042 Request::SandboxClose { sandbox, reply } => {
1043 let _ = reply.send(
1044 kernel
1045 .close_sandbox(sandbox)
1046 .map_err(|error| error.to_string()),
1047 );
1048 }
1049 Request::Shutdown => break,
1050 }
1051 }
1052}
1053
1054fn broker_session_id(session: &str) -> Result<SessionId, String> {
1055 SessionId::parse(session).map_err(|_| format!("No session: {session}"))
1056}
1057
1058fn documentation(runtime: &Runtime, symbol: &str) -> Result<Documentation, String> {
1059 documentation::lookup(runtime, symbol)
1060}
1061
1062pub fn install_native_kernel(runtime: &mut Runtime, broker: RuntimeBroker) {
1066 runtime.install_native_kernel_provider(Rc::new(move |operation, arguments| {
1067 kernel_call(&broker, &operation, &arguments)
1068 }));
1069}
1070
1071#[cfg(test)]
1072mod tests;