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