1use std::collections::BTreeMap;
9use std::fmt;
10use std::marker::PhantomData;
11use std::path::PathBuf;
12use std::sync::Arc;
13use std::sync::atomic::{AtomicBool, Ordering};
14use std::time::{Duration, Instant};
15
16use serde::Serialize;
17use serde::de::DeserializeOwned;
18use serde_json::Value;
19use serde_json::value::RawValue;
20
21use lean_rs_worker_protocol::protocol::{DataRow, Diagnostic, StreamSummary};
22use lean_rs_worker_protocol::types::{
23 LeanWorkerCapabilityMetadata, LeanWorkerDeclarationFilter, LeanWorkerDeclarationInspectionRequest,
24 LeanWorkerDeclarationInspectionResult, LeanWorkerDeclarationRow, LeanWorkerDeclarationSearch,
25 LeanWorkerDeclarationSearchResult, LeanWorkerDeclarationType, LeanWorkerDeclarationVerificationBatchRequest,
26 LeanWorkerDeclarationVerificationBatchResult, LeanWorkerDeclarationVerificationRequest,
27 LeanWorkerDeclarationVerificationResult, LeanWorkerDoctorReport, LeanWorkerElabOptions, LeanWorkerElabResult,
28 LeanWorkerKernelResult, LeanWorkerMetaResult, LeanWorkerMetaTransparency, LeanWorkerModuleQuery,
29 LeanWorkerModuleQueryBatchOutcome, LeanWorkerModuleQueryOutcome, LeanWorkerModuleQuerySelector,
30 LeanWorkerModuleSnapshotCacheClearResult, LeanWorkerOutputBudgets, LeanWorkerProofAttemptRequest,
31 LeanWorkerProofAttemptResult, LeanWorkerRendered, LeanWorkerResourceExhaustedFacts, LeanWorkerSessionImportProfile,
32};
33
34use crate::supervisor::{LeanWorker, LeanWorkerError};
35
36#[derive(Clone, Debug, Eq, PartialEq)]
38pub struct LeanWorkerSessionConfig {
39 project_root: PathBuf,
40 mode: LeanWorkerSessionMode,
41 imports: Vec<String>,
42 import_profile: LeanWorkerSessionImportProfile,
43}
44
45#[derive(Clone, Debug, Eq, PartialEq)]
46pub(crate) enum LeanWorkerSessionMode {
47 Capability {
48 package: String,
49 lib_name: String,
50 manifest_path: Option<PathBuf>,
51 },
52 ShimsOnly,
53}
54
55impl LeanWorkerSessionConfig {
56 pub fn new(
58 project_root: impl Into<PathBuf>,
59 package: impl Into<String>,
60 lib_name: impl Into<String>,
61 imports: impl IntoIterator<Item = impl Into<String>>,
62 ) -> Self {
63 Self {
64 project_root: project_root.into(),
65 mode: LeanWorkerSessionMode::Capability {
66 package: package.into(),
67 lib_name: lib_name.into(),
68 manifest_path: None,
69 },
70 imports: imports.into_iter().map(Into::into).collect(),
71 import_profile: LeanWorkerSessionImportProfile::default(),
72 }
73 }
74
75 #[must_use]
77 pub fn manifest_backed(
78 project_root: impl Into<PathBuf>,
79 package: impl Into<String>,
80 lib_name: impl Into<String>,
81 manifest_path: impl Into<PathBuf>,
82 imports: impl IntoIterator<Item = impl Into<String>>,
83 ) -> Self {
84 Self {
85 project_root: project_root.into(),
86 mode: LeanWorkerSessionMode::Capability {
87 package: package.into(),
88 lib_name: lib_name.into(),
89 manifest_path: Some(manifest_path.into()),
90 },
91 imports: imports.into_iter().map(Into::into).collect(),
92 import_profile: LeanWorkerSessionImportProfile::default(),
93 }
94 }
95
96 #[must_use]
98 pub fn shims_only(project_root: impl Into<PathBuf>, imports: impl IntoIterator<Item = impl Into<String>>) -> Self {
99 Self {
100 project_root: project_root.into(),
101 mode: LeanWorkerSessionMode::ShimsOnly,
102 imports: imports.into_iter().map(Into::into).collect(),
103 import_profile: LeanWorkerSessionImportProfile::default(),
104 }
105 }
106
107 #[must_use]
109 pub fn with_import_profile(mut self, profile: LeanWorkerSessionImportProfile) -> Self {
110 self.import_profile = profile;
111 self
112 }
113
114 pub(crate) fn project_root_string(&self) -> String {
115 self.project_root.to_string_lossy().into_owned()
116 }
117
118 pub(crate) fn mode(&self) -> &LeanWorkerSessionMode {
119 &self.mode
120 }
121
122 pub(crate) fn imports(&self) -> &[String] {
123 &self.imports
124 }
125
126 #[must_use]
128 pub fn import_profile(&self) -> LeanWorkerSessionImportProfile {
129 self.import_profile
130 }
131
132 #[must_use]
135 pub fn with_imports(&self, imports: impl IntoIterator<Item = impl Into<String>>) -> Self {
136 Self {
137 project_root: self.project_root.clone(),
138 mode: self.mode.clone(),
139 imports: imports.into_iter().map(Into::into).collect(),
140 import_profile: self.import_profile,
141 }
142 }
143}
144
145#[derive(Clone, Debug, Eq, PartialEq)]
151pub struct LeanWorkerRuntimeMetadata {
152 pub worker_version: String,
153 pub protocol_version: u16,
154 pub lean_version: Option<String>,
155}
156
157#[derive(Clone, Debug, Default)]
164pub struct LeanWorkerCancellationToken {
165 cancelled: Arc<AtomicBool>,
166}
167
168impl LeanWorkerCancellationToken {
169 #[must_use]
171 pub fn new() -> Self {
172 Self::default()
173 }
174
175 pub fn cancel(&self) {
177 self.cancelled.store(true, Ordering::Release);
178 }
179
180 #[must_use]
182 pub fn is_cancelled(&self) -> bool {
183 self.cancelled.load(Ordering::Acquire)
184 }
185}
186
187#[derive(Clone, Debug, Eq, PartialEq)]
189pub struct LeanWorkerProgressEvent {
190 pub phase: String,
191 pub current: u64,
192 pub total: Option<u64>,
193 pub elapsed: Duration,
194}
195
196pub trait LeanWorkerProgressSink: Send + Sync {
198 fn report(&self, event: LeanWorkerProgressEvent);
199}
200
201#[derive(Clone, Debug, Eq, PartialEq)]
207pub struct LeanWorkerDataRow {
208 pub stream: String,
209 pub sequence: u64,
210 pub payload: Value,
211}
212
213impl TryFrom<DataRow> for LeanWorkerDataRow {
214 type Error = LeanWorkerError;
215
216 fn try_from(value: DataRow) -> Result<Self, Self::Error> {
217 let payload = serde_json::from_str(value.payload.get()).map_err(|err| LeanWorkerError::Protocol {
218 message: format!("worker data-row payload decode failed: {err}"),
219 })?;
220 Ok(Self {
221 stream: value.stream,
222 sequence: value.sequence,
223 payload,
224 })
225 }
226}
227
228pub trait LeanWorkerDataSink: Send + Sync {
236 fn report(&self, row: LeanWorkerDataRow);
237}
238
239pub(crate) struct LeanWorkerRawDataRow {
240 pub(crate) stream: String,
241 pub(crate) sequence: u64,
242 pub(crate) payload: Box<RawValue>,
243}
244
245impl From<DataRow> for LeanWorkerRawDataRow {
246 fn from(value: DataRow) -> Self {
247 Self {
248 stream: value.stream,
249 sequence: value.sequence,
250 payload: value.payload,
251 }
252 }
253}
254
255pub(crate) trait LeanWorkerRawDataSink: Send + Sync {
256 fn report(&self, row: LeanWorkerRawDataRow);
257}
258
259#[derive(Clone, Copy)]
260pub(crate) enum LeanWorkerDataSinkTarget<'a> {
261 Value(&'a dyn LeanWorkerDataSink),
262 Raw(&'a dyn LeanWorkerRawDataSink),
263}
264
265#[derive(Clone, Debug, Eq, PartialEq)]
271pub struct LeanWorkerDiagnosticEvent {
272 pub code: String,
273 pub message: String,
274}
275
276impl From<Diagnostic> for LeanWorkerDiagnosticEvent {
277 fn from(value: Diagnostic) -> Self {
278 Self {
279 code: value.code,
280 message: value.message,
281 }
282 }
283}
284
285pub trait LeanWorkerDiagnosticSink: Send + Sync {
287 fn report(&self, diagnostic: LeanWorkerDiagnosticEvent);
288}
289
290#[derive(Clone, Debug, Eq, PartialEq)]
296pub struct LeanWorkerStreamSummary {
297 pub total_rows: u64,
299 pub per_stream_counts: BTreeMap<String, u64>,
301 pub elapsed: Duration,
303 pub metadata: Option<Value>,
305}
306
307impl From<StreamSummary> for LeanWorkerStreamSummary {
308 fn from(value: StreamSummary) -> Self {
309 Self {
310 total_rows: value.total_rows,
311 per_stream_counts: value.per_stream_counts,
312 elapsed: Duration::from_micros(value.elapsed_micros),
313 metadata: value.metadata,
314 }
315 }
316}
317
318pub struct LeanWorkerJsonCommand<Req, Resp> {
324 export: String,
325 _types: PhantomData<fn(&Req) -> Resp>,
326}
327
328impl<Req, Resp> LeanWorkerJsonCommand<Req, Resp> {
329 #[must_use]
331 pub fn new(export: impl Into<String>) -> Self {
332 Self {
333 export: export.into(),
334 _types: PhantomData,
335 }
336 }
337
338 #[must_use]
340 pub fn export(&self) -> &str {
341 &self.export
342 }
343}
344
345impl<Req, Resp> Clone for LeanWorkerJsonCommand<Req, Resp> {
346 fn clone(&self) -> Self {
347 Self {
348 export: self.export.clone(),
349 _types: PhantomData,
350 }
351 }
352}
353
354impl<Req, Resp> fmt::Debug for LeanWorkerJsonCommand<Req, Resp> {
355 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
356 f.debug_struct("LeanWorkerJsonCommand")
357 .field("export", &self.export)
358 .finish()
359 }
360}
361
362impl<Req, Resp> PartialEq for LeanWorkerJsonCommand<Req, Resp> {
363 fn eq(&self, other: &Self) -> bool {
364 self.export == other.export
365 }
366}
367
368impl<Req, Resp> Eq for LeanWorkerJsonCommand<Req, Resp> {}
369
370pub struct LeanWorkerStreamingCommand<Req, Row, Summary> {
378 export: String,
379 _types: PhantomData<fn(&Req) -> (Row, Summary)>,
380}
381
382impl<Req, Row, Summary> LeanWorkerStreamingCommand<Req, Row, Summary> {
383 #[must_use]
385 pub fn new(export: impl Into<String>) -> Self {
386 Self {
387 export: export.into(),
388 _types: PhantomData,
389 }
390 }
391
392 #[must_use]
394 pub fn export(&self) -> &str {
395 &self.export
396 }
397}
398
399impl<Req, Row, Summary> Clone for LeanWorkerStreamingCommand<Req, Row, Summary> {
400 fn clone(&self) -> Self {
401 Self {
402 export: self.export.clone(),
403 _types: PhantomData,
404 }
405 }
406}
407
408impl<Req, Row, Summary> fmt::Debug for LeanWorkerStreamingCommand<Req, Row, Summary> {
409 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
410 f.debug_struct("LeanWorkerStreamingCommand")
411 .field("export", &self.export)
412 .finish()
413 }
414}
415
416impl<Req, Row, Summary> PartialEq for LeanWorkerStreamingCommand<Req, Row, Summary> {
417 fn eq(&self, other: &Self) -> bool {
418 self.export == other.export
419 }
420}
421
422impl<Req, Row, Summary> Eq for LeanWorkerStreamingCommand<Req, Row, Summary> {}
423
424#[derive(Clone, Debug, Eq, PartialEq)]
426pub struct LeanWorkerTypedDataRow<Row> {
427 pub stream: String,
428 pub sequence: u64,
429 pub payload: Row,
430}
431
432pub trait LeanWorkerTypedDataSink<Row>: Send + Sync {
438 fn report(&self, row: LeanWorkerTypedDataRow<Row>);
439}
440
441#[derive(Clone, Debug, Eq, PartialEq)]
447pub struct LeanWorkerTypedStreamSummary<Summary> {
448 pub total_rows: u64,
449 pub per_stream_counts: BTreeMap<String, u64>,
450 pub elapsed: Duration,
451 pub metadata: Option<Summary>,
452}
453
454pub struct LeanWorkerSession<'worker> {
460 worker: &'worker mut LeanWorker,
461 open: bool,
462}
463
464impl LeanWorker {
465 pub fn open_session<'worker>(
473 &'worker mut self,
474 config: &LeanWorkerSessionConfig,
475 cancellation: Option<&LeanWorkerCancellationToken>,
476 progress: Option<&dyn LeanWorkerProgressSink>,
477 ) -> Result<LeanWorkerSession<'worker>, LeanWorkerError> {
478 self.open_worker_session(config, cancellation, progress)?;
479 Ok(LeanWorkerSession {
480 worker: self,
481 open: true,
482 })
483 }
484
485 pub(crate) fn attach_open_session(&mut self) -> LeanWorkerSession<'_> {
486 LeanWorkerSession {
487 worker: self,
488 open: true,
489 }
490 }
491}
492
493impl LeanWorkerSession<'_> {
494 #[must_use]
496 pub fn request_timeout(&self) -> Duration {
497 self.worker.request_timeout()
498 }
499
500 pub fn set_request_timeout(&mut self, timeout: Duration) {
505 self.worker.set_request_timeout(timeout);
506 }
507
508 pub fn elaborate(
516 &mut self,
517 source: &str,
518 options: &LeanWorkerElabOptions,
519 cancellation: Option<&LeanWorkerCancellationToken>,
520 progress: Option<&dyn LeanWorkerProgressSink>,
521 ) -> Result<LeanWorkerElabResult, LeanWorkerError> {
522 self.with_session(|worker| worker.worker_elaborate(source, options, cancellation, progress))
523 }
524
525 pub fn kernel_check(
533 &mut self,
534 source: &str,
535 options: &LeanWorkerElabOptions,
536 cancellation: Option<&LeanWorkerCancellationToken>,
537 progress: Option<&dyn LeanWorkerProgressSink>,
538 ) -> Result<LeanWorkerKernelResult, LeanWorkerError> {
539 self.with_session(|worker| worker.worker_kernel_check(source, options, cancellation, progress))
540 }
541
542 pub fn declaration_kinds(
550 &mut self,
551 names: &[&str],
552 cancellation: Option<&LeanWorkerCancellationToken>,
553 progress: Option<&dyn LeanWorkerProgressSink>,
554 ) -> Result<Vec<String>, LeanWorkerError> {
555 self.with_session(|worker| worker.worker_declaration_kinds(names, cancellation, progress))
556 }
557
558 pub fn declaration_names(
566 &mut self,
567 names: &[&str],
568 cancellation: Option<&LeanWorkerCancellationToken>,
569 progress: Option<&dyn LeanWorkerProgressSink>,
570 ) -> Result<Vec<String>, LeanWorkerError> {
571 self.with_session(|worker| worker.worker_declaration_names(names, cancellation, progress))
572 }
573
574 pub fn infer_type(
600 &mut self,
601 source: &str,
602 options: &LeanWorkerElabOptions,
603 cancellation: Option<&LeanWorkerCancellationToken>,
604 progress: Option<&dyn LeanWorkerProgressSink>,
605 ) -> Result<LeanWorkerMetaResult<LeanWorkerRendered>, LeanWorkerError> {
606 self.with_session(|worker| worker.worker_infer_type(source, options, cancellation, progress))
607 }
608
609 pub fn whnf(
624 &mut self,
625 source: &str,
626 options: &LeanWorkerElabOptions,
627 cancellation: Option<&LeanWorkerCancellationToken>,
628 progress: Option<&dyn LeanWorkerProgressSink>,
629 ) -> Result<LeanWorkerMetaResult<LeanWorkerRendered>, LeanWorkerError> {
630 self.with_session(|worker| worker.worker_whnf(source, options, cancellation, progress))
631 }
632
633 pub fn is_def_eq(
642 &mut self,
643 lhs: &str,
644 rhs: &str,
645 transparency: LeanWorkerMetaTransparency,
646 options: &LeanWorkerElabOptions,
647 cancellation: Option<&LeanWorkerCancellationToken>,
648 progress: Option<&dyn LeanWorkerProgressSink>,
649 ) -> Result<LeanWorkerMetaResult<bool>, LeanWorkerError> {
650 self.with_session(|worker| worker.worker_is_def_eq(lhs, rhs, transparency, options, cancellation, progress))
651 }
652
653 pub fn describe(
664 &mut self,
665 name: &str,
666 cancellation: Option<&LeanWorkerCancellationToken>,
667 progress: Option<&dyn LeanWorkerProgressSink>,
668 ) -> Result<Option<LeanWorkerDeclarationRow>, LeanWorkerError> {
669 self.with_session(|worker| worker.worker_describe(name, cancellation, progress))
670 }
671
672 pub fn search_declarations(
685 &mut self,
686 search: &LeanWorkerDeclarationSearch,
687 cancellation: Option<&LeanWorkerCancellationToken>,
688 progress: Option<&dyn LeanWorkerProgressSink>,
689 ) -> Result<LeanWorkerDeclarationSearchResult, LeanWorkerError> {
690 self.with_session(|worker| worker.worker_search_declarations(search, cancellation, progress))
691 }
692
693 pub fn declaration_type(
704 &mut self,
705 name: &str,
706 max_bytes: usize,
707 cancellation: Option<&LeanWorkerCancellationToken>,
708 progress: Option<&dyn LeanWorkerProgressSink>,
709 ) -> Result<Option<LeanWorkerDeclarationType>, LeanWorkerError> {
710 self.with_session(|worker| worker.worker_declaration_type(name, max_bytes, cancellation, progress))
711 }
712
713 pub fn inspect_declaration(
724 &mut self,
725 request: &LeanWorkerDeclarationInspectionRequest,
726 cancellation: Option<&LeanWorkerCancellationToken>,
727 progress: Option<&dyn LeanWorkerProgressSink>,
728 ) -> Result<LeanWorkerDeclarationInspectionResult, LeanWorkerError> {
729 self.with_session(|worker| worker.worker_inspect_declaration(request, cancellation, progress))
730 }
731
732 pub fn attempt_proof(
742 &mut self,
743 request: &LeanWorkerProofAttemptRequest,
744 options: &LeanWorkerElabOptions,
745 cancellation: Option<&LeanWorkerCancellationToken>,
746 progress: Option<&dyn LeanWorkerProgressSink>,
747 ) -> Result<LeanWorkerProofAttemptResult, LeanWorkerError> {
748 self.with_session(|worker| worker.worker_attempt_proof(request, options, cancellation, progress))
749 }
750
751 pub fn verify_declaration(
761 &mut self,
762 request: &LeanWorkerDeclarationVerificationRequest,
763 options: &LeanWorkerElabOptions,
764 cancellation: Option<&LeanWorkerCancellationToken>,
765 progress: Option<&dyn LeanWorkerProgressSink>,
766 ) -> Result<LeanWorkerDeclarationVerificationResult, LeanWorkerError> {
767 self.with_session(|worker| worker.worker_verify_declaration(request, options, cancellation, progress))
768 }
769
770 pub fn verify_declaration_batch(
781 &mut self,
782 request: &LeanWorkerDeclarationVerificationBatchRequest,
783 options: &LeanWorkerElabOptions,
784 cancellation: Option<&LeanWorkerCancellationToken>,
785 progress: Option<&dyn LeanWorkerProgressSink>,
786 ) -> Result<LeanWorkerDeclarationVerificationBatchResult, LeanWorkerError> {
787 self.with_session(|worker| worker.worker_verify_declaration_batch(request, options, cancellation, progress))
788 }
789
790 pub fn list_declarations_strings(
801 &mut self,
802 filter: &LeanWorkerDeclarationFilter,
803 cancellation: Option<&LeanWorkerCancellationToken>,
804 progress: Option<&dyn LeanWorkerProgressSink>,
805 ) -> Result<Vec<String>, LeanWorkerError> {
806 self.with_session(|worker| worker.worker_list_declarations_strings(*filter, cancellation, progress))
807 }
808
809 pub fn describe_bulk(
821 &mut self,
822 names: &[&str],
823 cancellation: Option<&LeanWorkerCancellationToken>,
824 progress: Option<&dyn LeanWorkerProgressSink>,
825 ) -> Result<Vec<LeanWorkerDeclarationRow>, LeanWorkerError> {
826 self.with_session(|worker| worker.worker_describe_bulk(names, cancellation, progress))
827 }
828
829 pub fn process_module_query(
840 &mut self,
841 source: &str,
842 query: LeanWorkerModuleQuery,
843 options: &LeanWorkerElabOptions,
844 cancellation: Option<&LeanWorkerCancellationToken>,
845 progress: Option<&dyn LeanWorkerProgressSink>,
846 ) -> Result<LeanWorkerModuleQueryOutcome, LeanWorkerError> {
847 self.with_session(|worker| worker.worker_process_module_query(source, query, options, cancellation, progress))
848 }
849
850 pub fn process_module_query_batch(
861 &mut self,
862 source: &str,
863 selectors: &[LeanWorkerModuleQuerySelector],
864 budgets: &LeanWorkerOutputBudgets,
865 options: &LeanWorkerElabOptions,
866 cancellation: Option<&LeanWorkerCancellationToken>,
867 progress: Option<&dyn LeanWorkerProgressSink>,
868 ) -> Result<LeanWorkerModuleQueryBatchOutcome, LeanWorkerError> {
869 self.with_session(|worker| {
870 worker.worker_process_module_query_batch(source, selectors, budgets, options, cancellation, progress)
871 })
872 }
873
874 pub fn clear_module_snapshot_cache(
881 &mut self,
882 cancellation: Option<&LeanWorkerCancellationToken>,
883 progress: Option<&dyn LeanWorkerProgressSink>,
884 ) -> Result<LeanWorkerModuleSnapshotCacheClearResult, LeanWorkerError> {
885 self.with_session(|worker| worker.worker_clear_module_snapshot_cache(cancellation, progress))
886 }
887
888 pub fn run_data_stream(
903 &mut self,
904 export: &str,
905 request: &Value,
906 rows: &dyn LeanWorkerDataSink,
907 diagnostics: Option<&dyn LeanWorkerDiagnosticSink>,
908 cancellation: Option<&LeanWorkerCancellationToken>,
909 progress: Option<&dyn LeanWorkerProgressSink>,
910 ) -> Result<LeanWorkerStreamSummary, LeanWorkerError> {
911 self.with_session(|worker| {
912 worker.worker_run_data_stream(export, request, rows, diagnostics, cancellation, progress)
913 })
914 }
915
916 fn run_data_stream_raw(
917 &mut self,
918 export: &str,
919 request: &Value,
920 rows: &dyn LeanWorkerRawDataSink,
921 diagnostics: Option<&dyn LeanWorkerDiagnosticSink>,
922 cancellation: Option<&LeanWorkerCancellationToken>,
923 progress: Option<&dyn LeanWorkerProgressSink>,
924 ) -> Result<LeanWorkerStreamSummary, LeanWorkerError> {
925 self.with_session(|worker| {
926 worker.worker_run_data_stream_raw(export, request, rows, diagnostics, cancellation, progress)
927 })
928 }
929
930 pub fn run_json_command<Req, Resp>(
943 &mut self,
944 command: &LeanWorkerJsonCommand<Req, Resp>,
945 request: &Req,
946 cancellation: Option<&LeanWorkerCancellationToken>,
947 progress: Option<&dyn LeanWorkerProgressSink>,
948 ) -> Result<Resp, LeanWorkerError>
949 where
950 Req: Serialize,
951 Resp: DeserializeOwned,
952 {
953 let request_json =
954 serde_json::to_string(request).map_err(|err| LeanWorkerError::TypedCommandRequestEncode {
955 export: command.export().to_owned(),
956 message: err.to_string(),
957 })?;
958 let response_json = self.with_session(|worker| {
959 worker.worker_json_command(command.export(), request_json, cancellation, progress)
960 })?;
961 serde_json::from_str(&response_json).map_err(|err| LeanWorkerError::TypedCommandResponseDecode {
962 export: command.export().to_owned(),
963 message: err.to_string(),
964 })
965 }
966
967 pub fn run_streaming_command<Req, Row, Summary>(
985 &mut self,
986 command: &LeanWorkerStreamingCommand<Req, Row, Summary>,
987 request: &Req,
988 rows: &dyn LeanWorkerTypedDataSink<Row>,
989 diagnostics: Option<&dyn LeanWorkerDiagnosticSink>,
990 cancellation: Option<&LeanWorkerCancellationToken>,
991 progress: Option<&dyn LeanWorkerProgressSink>,
992 ) -> Result<LeanWorkerTypedStreamSummary<Summary>, LeanWorkerError>
993 where
994 Req: Serialize,
995 Row: DeserializeOwned,
996 Summary: DeserializeOwned,
997 {
998 let request_value =
999 serde_json::to_value(request).map_err(|err| LeanWorkerError::TypedCommandRequestEncode {
1000 export: command.export().to_owned(),
1001 message: err.to_string(),
1002 })?;
1003 let internal_cancellation = LeanWorkerCancellationToken::new();
1004 let cancellation_for_stream = cancellation.unwrap_or(&internal_cancellation);
1005 let typed_sink = TypedRawDataSink {
1006 export: command.export(),
1007 rows,
1008 cancellation: cancellation_for_stream,
1009 decode_error: std::sync::Mutex::new(None),
1010 };
1011
1012 match self.run_data_stream_raw(
1015 command.export(),
1016 &request_value,
1017 &typed_sink,
1018 diagnostics,
1019 Some(cancellation_for_stream),
1020 progress,
1021 ) {
1022 Ok(summary) => {
1023 if let Some(err) = typed_sink.take_decode_error() {
1024 return Err(err);
1025 }
1026 let metadata = summary
1027 .metadata
1028 .map(|metadata| {
1029 serde_json::from_value(metadata).map_err(|err| LeanWorkerError::TypedCommandSummaryDecode {
1030 export: command.export().to_owned(),
1031 message: err.to_string(),
1032 })
1033 })
1034 .transpose()?;
1035 Ok(LeanWorkerTypedStreamSummary {
1036 total_rows: summary.total_rows,
1037 per_stream_counts: summary.per_stream_counts,
1038 elapsed: summary.elapsed,
1039 metadata,
1040 })
1041 }
1042 Err(LeanWorkerError::Cancelled { resource, .. }) => {
1043 if let Some(err) = typed_sink.take_decode_error() {
1044 Err(err)
1045 } else {
1046 Err(LeanWorkerError::Cancelled {
1047 operation: "worker_run_data_stream",
1048 resource,
1049 })
1050 }
1051 }
1052 Err(err) => Err(err),
1053 }
1054 }
1055
1056 pub fn capability_metadata(
1069 &mut self,
1070 export: &str,
1071 request: &Value,
1072 cancellation: Option<&LeanWorkerCancellationToken>,
1073 progress: Option<&dyn LeanWorkerProgressSink>,
1074 ) -> Result<LeanWorkerCapabilityMetadata, LeanWorkerError> {
1075 self.with_session(|worker| worker.worker_capability_metadata(export, request, cancellation, progress))
1076 }
1077
1078 pub fn capability_doctor(
1091 &mut self,
1092 export: &str,
1093 request: &Value,
1094 cancellation: Option<&LeanWorkerCancellationToken>,
1095 progress: Option<&dyn LeanWorkerProgressSink>,
1096 ) -> Result<LeanWorkerDoctorReport, LeanWorkerError> {
1097 self.with_session(|worker| worker.worker_capability_doctor(export, request, cancellation, progress))
1098 }
1099
1100 fn ensure_open(&self) -> Result<(), LeanWorkerError> {
1101 if self.open {
1102 Ok(())
1103 } else {
1104 Err(LeanWorkerError::UnsupportedRequest {
1105 operation: "worker_session_invalidated",
1106 })
1107 }
1108 }
1109
1110 fn with_session<T>(
1118 &mut self,
1119 f: impl FnOnce(&mut LeanWorker) -> Result<T, LeanWorkerError>,
1120 ) -> Result<T, LeanWorkerError> {
1121 self.ensure_open()?;
1122 let result = f(self.worker);
1123 if matches!(
1124 result,
1125 Err(LeanWorkerError::Cancelled { .. } | LeanWorkerError::Timeout { .. })
1126 ) {
1127 self.open = false;
1128 }
1129 result
1130 }
1131}
1132
1133struct TypedRawDataSink<'a, Row> {
1134 export: &'a str,
1135 rows: &'a dyn LeanWorkerTypedDataSink<Row>,
1136 cancellation: &'a LeanWorkerCancellationToken,
1137 decode_error: std::sync::Mutex<Option<LeanWorkerError>>,
1138}
1139
1140impl<Row> TypedRawDataSink<'_, Row> {
1141 fn take_decode_error(&self) -> Option<LeanWorkerError> {
1142 self.decode_error.lock().ok().and_then(|mut guard| guard.take())
1143 }
1144}
1145
1146impl<Row> LeanWorkerRawDataSink for TypedRawDataSink<'_, Row>
1147where
1148 Row: DeserializeOwned,
1149{
1150 fn report(&self, row: LeanWorkerRawDataRow) {
1151 match serde_json::from_str(row.payload.get()) {
1152 Ok(payload) => self.rows.report(LeanWorkerTypedDataRow {
1153 stream: row.stream,
1154 sequence: row.sequence,
1155 payload,
1156 }),
1157 Err(err) => {
1158 if let Ok(mut guard) = self.decode_error.lock() {
1159 *guard = Some(LeanWorkerError::TypedCommandRowDecode {
1160 export: self.export.to_owned(),
1161 stream: row.stream,
1162 sequence: row.sequence,
1163 message: err.to_string(),
1164 });
1165 }
1166 self.cancellation.cancel();
1167 }
1168 }
1169 }
1170}
1171
1172pub(crate) fn check_cancelled(
1173 operation: &'static str,
1174 token: Option<&LeanWorkerCancellationToken>,
1175) -> Result<(), LeanWorkerError> {
1176 if token.is_some_and(LeanWorkerCancellationToken::is_cancelled) {
1177 Err(LeanWorkerError::Cancelled {
1178 operation,
1179 resource: Box::new(pre_dispatch_cancelled_resource(operation)),
1180 })
1181 } else {
1182 Ok(())
1183 }
1184}
1185
1186fn pre_dispatch_cancelled_resource(operation: &'static str) -> LeanWorkerResourceExhaustedFacts {
1187 LeanWorkerResourceExhaustedFacts {
1188 cause: "worker_cancelled".to_owned(),
1189 work_entered_child: false,
1190 operation: Some(operation.to_owned()),
1191 current_rss_kib: None,
1192 limit_kib: None,
1193 import_count: None,
1194 worker_generation: None,
1195 restart_reason: None,
1196 queue_wait_ms: None,
1197 duration_ms: None,
1198 cold_open_attempts: None,
1199 cold_open_admitted: None,
1200 cold_open_refusals: None,
1201 import_like_requests: None,
1202 import_like_admitted: None,
1203 last_import_stats: None,
1204 }
1205}
1206
1207pub(crate) fn report_parent_progress(
1208 sink: Option<&dyn LeanWorkerProgressSink>,
1209 event: LeanWorkerProgressEvent,
1210) -> Result<(), LeanWorkerError> {
1211 let Some(sink) = sink else {
1212 return Ok(());
1213 };
1214 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| sink.report(event))).map_err(|payload| {
1215 let message = if let Some(s) = payload.downcast_ref::<&str>() {
1216 (*s).to_owned()
1217 } else if let Some(s) = payload.downcast_ref::<String>() {
1218 s.clone()
1219 } else {
1220 "worker progress sink panicked".to_owned()
1221 };
1222 LeanWorkerError::ProgressPanic { message }
1223 })
1224}
1225
1226pub(crate) fn report_parent_data_row(
1227 sink: Option<LeanWorkerDataSinkTarget<'_>>,
1228 row: DataRow,
1229) -> Result<(), LeanWorkerError> {
1230 let Some(sink) = sink else {
1231 return Err(LeanWorkerError::Protocol {
1232 message: "worker sent data row for a request without a row sink".to_owned(),
1233 });
1234 };
1235 match sink {
1236 LeanWorkerDataSinkTarget::Value(sink) => {
1237 let row = LeanWorkerDataRow::try_from(row)?;
1238 report_value_data_row(sink, row)
1239 }
1240 LeanWorkerDataSinkTarget::Raw(sink) => report_raw_data_row(sink, row.into()),
1241 }
1242}
1243
1244fn report_value_data_row(sink: &dyn LeanWorkerDataSink, row: LeanWorkerDataRow) -> Result<(), LeanWorkerError> {
1245 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| sink.report(row))).map_err(|payload| {
1246 let message = if let Some(s) = payload.downcast_ref::<&str>() {
1247 (*s).to_owned()
1248 } else if let Some(s) = payload.downcast_ref::<String>() {
1249 s.clone()
1250 } else {
1251 "worker data sink panicked".to_owned()
1252 };
1253 LeanWorkerError::DataSinkPanic { message }
1254 })
1255}
1256
1257fn report_raw_data_row(sink: &dyn LeanWorkerRawDataSink, row: LeanWorkerRawDataRow) -> Result<(), LeanWorkerError> {
1258 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| sink.report(row))).map_err(|payload| {
1259 let message = if let Some(s) = payload.downcast_ref::<&str>() {
1260 (*s).to_owned()
1261 } else if let Some(s) = payload.downcast_ref::<String>() {
1262 s.clone()
1263 } else {
1264 "worker data sink panicked".to_owned()
1265 };
1266 LeanWorkerError::DataSinkPanic { message }
1267 })
1268}
1269
1270pub(crate) fn report_parent_diagnostic(
1271 sink: Option<&dyn LeanWorkerDiagnosticSink>,
1272 diagnostic: LeanWorkerDiagnosticEvent,
1273) -> Result<(), LeanWorkerError> {
1274 let Some(sink) = sink else {
1275 return Ok(());
1276 };
1277 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| sink.report(diagnostic))).map_err(|payload| {
1278 let message = if let Some(s) = payload.downcast_ref::<&str>() {
1279 (*s).to_owned()
1280 } else if let Some(s) = payload.downcast_ref::<String>() {
1281 s.clone()
1282 } else {
1283 "worker diagnostic sink panicked".to_owned()
1284 };
1285 LeanWorkerError::DiagnosticSinkPanic { message }
1286 })
1287}
1288
1289pub(crate) fn elapsed_event(
1290 phase: String,
1291 current: u64,
1292 total: Option<u64>,
1293 started: Instant,
1294) -> LeanWorkerProgressEvent {
1295 LeanWorkerProgressEvent {
1296 phase,
1297 current,
1298 total,
1299 elapsed: started.elapsed(),
1300 }
1301}