1use crate::core::{Promise, PromiseRejection, PromiseState, Value};
2use crate::lang::protocol::IComponent;
3use std::cell::{Cell, RefCell};
4use std::collections::{HashMap, VecDeque};
5use std::fmt;
6use std::rc::{Rc, Weak};
7use std::time::Duration;
8
9pub(crate) mod guest;
10pub mod plan;
11mod scope;
12mod types;
13
14pub use scope::{
15 current_work_context, monotonic_nanos, process_work_host, WorkCancellationToken, WorkContext,
16};
17pub use types::{WorkDeadline, WorkHostStatus, WorkId, WorkOptions, WorkRunState, WorkRunStatus};
18
19use scope::{
20 cancellation_rejection, deadline_remaining_millis, install_progress_hooks, next_work_id,
21 now_millis, resolve_deadline, work_failure,
22};
23
24pub(crate) use scope::with_current_work_context;
25
26type WorkTask = Box<dyn FnOnce(WorkContext) -> Result<Value, PromiseRejection>>;
27type WorkFinalizer = Box<dyn FnOnce(WorkContext) -> Result<(), PromiseRejection>>;
28
29struct PendingWork {
30 id: WorkId,
31 task: WorkTask,
32}
33
34struct WorkHostInner {
35 started: bool,
36 next_id: u64,
37 runs: HashMap<WorkId, WorkRun>,
38 queue: VecDeque<PendingWork>,
39}
40
41#[derive(Clone)]
48pub struct WorkHost {
49 inner: Rc<RefCell<WorkHostInner>>,
50}
51
52impl fmt::Debug for WorkHost {
53 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
54 formatter
55 .debug_struct("WorkHost")
56 .field("status", &self.status())
57 .finish()
58 }
59}
60
61impl Default for WorkHost {
62 fn default() -> Self {
63 Self::new()
64 }
65}
66
67impl WorkHost {
68 pub fn new() -> Self {
69 Self {
70 inner: Rc::new(RefCell::new(WorkHostInner {
71 started: true,
72 next_id: 1,
73 runs: HashMap::new(),
74 queue: VecDeque::new(),
75 })),
76 }
77 }
78
79 pub fn submit<F>(&self, id: Option<&str>, task: F) -> Result<WorkRun, String>
81 where
82 F: FnOnce() -> Result<Value, String> + 'static,
83 {
84 let options = WorkOptions {
85 id: id.map(WorkId::new).transpose()?,
86 ..WorkOptions::default()
87 };
88 self.submit_scoped(options, move |_| task())
89 }
90
91 pub fn submit_rejection<F>(&self, id: Option<&str>, task: F) -> Result<WorkRun, String>
93 where
94 F: FnOnce() -> Result<Value, PromiseRejection> + 'static,
95 {
96 let options = WorkOptions {
97 id: id.map(WorkId::new).transpose()?,
98 ..WorkOptions::default()
99 };
100 self.submit_scoped_rejection(options, move |_| task())
101 }
102
103 pub fn submit_scoped<F>(&self, options: WorkOptions, task: F) -> Result<WorkRun, String>
104 where
105 F: FnOnce(WorkContext) -> Result<Value, String> + 'static,
106 {
107 self.submit_scoped_rejection(options, move |context| task(context).map_err(work_failure))
108 }
109
110 pub fn submit_scoped_rejection<F>(
111 &self,
112 options: WorkOptions,
113 task: F,
114 ) -> Result<WorkRun, String>
115 where
116 F: FnOnce(WorkContext) -> Result<Value, PromiseRejection> + 'static,
117 {
118 let parent = if options.detached {
119 None
120 } else {
121 current_work_context()
122 .filter(|context| context.host.same_identity(self))
123 .map(|context| context.run)
124 };
125 self.submit_with_parent(parent, options, Box::new(task))
126 }
127
128 fn submit_with_parent(
129 &self,
130 parent: Option<WorkRun>,
131 options: WorkOptions,
132 task: WorkTask,
133 ) -> Result<WorkRun, String> {
134 let mut host = self.inner.borrow_mut();
135 if !host.started {
136 return Err("native work host is stopped".into());
137 }
138 let deadline = resolve_deadline(&options, parent.as_ref());
139 let id = match options.id.clone() {
140 Some(id) => id,
141 None => next_work_id(&mut host)?,
142 };
143 if host.runs.contains_key(&id) {
144 return Err(format!("work run ID already exists: {id}"));
145 }
146 if parent
147 .as_ref()
148 .is_some_and(|parent| !parent.accepts_children())
149 {
150 return Err("parent work scope is closed".into());
151 }
152
153 let result = Promise::new();
154 let run = WorkRun {
155 inner: Rc::new(WorkRunInner {
156 id: id.clone(),
157 result,
158 status: RefCell::new(WorkRunStatus {
159 id: id.clone(),
160 state: WorkRunState::Queued,
161 started_at_millis: now_millis(),
162 finished_at_millis: None,
163 error: None,
164 cancel_reason: None,
165 parent_id: parent.as_ref().map(WorkRun::work_id),
166 child_count: 0,
167 deadline_remaining_millis: deadline.map(deadline_remaining_millis),
168 detached: options.detached,
169 }),
170 host: Rc::downgrade(&self.inner),
171 parent: parent.as_ref().map(|parent| Rc::downgrade(&parent.inner)),
172 children: RefCell::new(HashMap::new()),
173 deadline,
174 cancellation: RefCell::new(None),
175 body_done: Cell::new(false),
176 body_outcome: RefCell::new(None),
177 finalizers: RefCell::new(Vec::new()),
178 finalizers_started: Cell::new(false),
179 active_promise: RefCell::new(None),
180 parent_notified: Cell::new(false),
181 events: Rc::new(RefCell::new(WorkEventLog {
182 values: vec![work_event(
183 &id,
184 1,
185 "work/run-queued",
186 WorkRunState::Queued,
187 None,
188 )],
189 ..WorkEventLog::default()
190 })),
191 }),
192 };
193 if let Some(parent) = &parent {
194 if !parent.attach_child(run.clone()) {
195 return Err("parent work scope is closed".into());
196 }
197 }
198 install_progress_hooks(self, &run);
199 host.runs.insert(id.clone(), run.clone());
200 host.queue.push_back(PendingWork { id, task });
201 drop(host);
202 run.check_deadline();
203 Ok(run)
204 }
205
206 pub fn resolve(&self, reference: &str) -> Result<WorkRun, String> {
208 let id = WorkId::new(reference)?;
209 self.resolve_id(&id)
210 }
211
212 pub fn resolve_id(&self, id: &WorkId) -> Result<WorkRun, String> {
213 let run = self
214 .inner
215 .borrow()
216 .runs
217 .get(id)
218 .cloned()
219 .ok_or_else(|| format!("unknown work run: {id}"))?;
220 run.check_deadline();
221 Ok(run)
222 }
223
224 pub fn run(&self, id: &WorkId) -> bool {
226 let (run, task) = {
227 let mut host = self.inner.borrow_mut();
228 let Some(index) = host.queue.iter().position(|pending| &pending.id == id) else {
229 return false;
230 };
231 let pending = host.queue.remove(index).expect("queued work disappeared");
232 let run = host
233 .runs
234 .get(id)
235 .cloned()
236 .expect("queued work has no live run");
237 (run, pending.task)
238 };
239 run.check_deadline();
240 if !run.mark_running() {
241 return false;
242 }
243 let context = WorkContext {
244 host: self.clone(),
245 run: run.clone(),
246 };
247 let result = with_current_work_context(context.clone(), || task(context));
248 match result {
249 Ok(value) => run.settle_body(value),
250 Err(error) => run.fail_body(error),
251 }
252 true
253 }
254
255 pub fn run_next(&self) -> bool {
256 let id = self
257 .inner
258 .borrow()
259 .queue
260 .front()
261 .map(|pending| pending.id.clone());
262 id.is_some_and(|id| self.run(&id))
263 }
264
265 pub fn drain(&self) {
266 while self.run_next() {}
267 }
268
269 fn progress(&self, id: &WorkId) {
270 let _ = self.run(id);
271 let run = self.resolve_id(id).ok();
272 if let Some(run) = &run {
273 run.progress_active_promise();
274 }
275 while run.as_ref().is_some_and(|run| !run.closed()) && self.run_next() {}
276 if let Some(run) = run {
277 run.progress_active_promise();
278 run.check_deadline();
279 }
280 }
281
282 fn wait_for(&self, id: &WorkId) {
283 self.progress(id);
284 if let Ok(run) = self.resolve_id(id) {
285 let active = run.inner.active_promise.borrow().clone();
286 if let Some(active) = active {
287 active.wait_state();
288 }
289 self.progress(id);
290 }
291 }
292
293 pub fn status(&self) -> WorkHostStatus {
294 let host = self.inner.borrow();
295 WorkHostStatus {
296 state: if host.started { "started" } else { "stopped" },
297 run_count: host.runs.len(),
298 queued_count: host.queue.len(),
299 }
300 }
301
302 pub fn started(&self) -> bool {
303 self.inner.borrow().started
304 }
305
306 pub fn start(&self) {
307 self.inner.borrow_mut().started = true;
308 }
309
310 pub fn stop(&self) {
311 self.inner.borrow_mut().started = false;
312 }
313
314 pub fn kill(&self) {
315 let runs = {
316 let mut host = self.inner.borrow_mut();
317 host.started = false;
318 host.runs.values().cloned().collect::<Vec<_>>()
319 };
320 for run in runs {
321 run.cancel(Value::Keyword("host-stopped".into()));
322 }
323 }
324
325 pub fn same_identity(&self, other: &Self) -> bool {
326 Rc::ptr_eq(&self.inner, &other.inner)
327 }
328}
329
330impl IComponent for WorkHost {
331 type Metadata = WorkHostStatus;
332
333 fn props(&self) -> Self::Metadata {
334 self.status()
335 }
336
337 fn status(&self) -> Self::Metadata {
338 WorkHost::status(self)
339 }
340
341 fn started(&self) -> bool {
342 WorkHost::started(self)
343 }
344
345 fn stopped(&self) -> bool {
346 !WorkHost::started(self)
347 }
348
349 fn start(&mut self) {
350 WorkHost::start(self);
351 }
352
353 fn stop(&mut self) {
354 WorkHost::stop(self);
355 }
356
357 fn kill(&mut self) {
358 WorkHost::kill(self);
359 }
360}
361
362#[derive(Clone)]
363struct CancellationRequest {
364 reason: Value,
365 rejection: PromiseRejection,
366}
367
368struct WorkRunInner {
369 id: WorkId,
370 result: Promise,
371 status: RefCell<WorkRunStatus>,
372 host: Weak<RefCell<WorkHostInner>>,
373 parent: Option<Weak<WorkRunInner>>,
374 children: RefCell<HashMap<WorkId, WorkRun>>,
375 deadline: Option<WorkDeadline>,
376 cancellation: RefCell<Option<CancellationRequest>>,
377 body_done: Cell<bool>,
378 body_outcome: RefCell<Option<Result<Value, PromiseRejection>>>,
379 finalizers: RefCell<Vec<WorkFinalizer>>,
380 finalizers_started: Cell<bool>,
381 active_promise: RefCell<Option<Promise>>,
382 parent_notified: Cell<bool>,
383 events: Rc<RefCell<WorkEventLog>>,
384}
385
386#[derive(Default)]
387struct WorkEventLog {
388 values: Vec<Value>,
389 closed: bool,
390 cursors: Vec<Weak<RefCell<WorkEventCursor>>>,
391}
392
393struct WorkEventCursor {
394 next: usize,
395 pending: Option<Promise>,
396 closed: bool,
397}
398
399#[derive(Clone)]
401pub struct WorkRun {
402 inner: Rc<WorkRunInner>,
403}
404
405impl fmt::Debug for WorkRun {
406 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
407 formatter
408 .debug_struct("WorkRun")
409 .field("status", &self.work_status())
410 .finish()
411 }
412}
413
414impl WorkRun {
415 pub fn work_id(&self) -> WorkId {
416 self.inner.id.clone()
417 }
418
419 pub fn work_status(&self) -> WorkRunStatus {
420 self.check_deadline();
421 let mut status = self.inner.status.borrow().clone();
422 status.child_count = self.inner.children.borrow().len();
423 status.deadline_remaining_millis = self.inner.deadline.map(deadline_remaining_millis);
424 status
425 }
426
427 pub fn deadline(&self) -> Option<WorkDeadline> {
428 self.inner.deadline
429 }
430
431 pub fn cancellation_token(&self) -> WorkCancellationToken {
432 WorkCancellationToken {
433 run: Rc::downgrade(&self.inner),
434 }
435 }
436
437 pub fn work_result(&self) -> Promise {
439 self.inner.result.clone()
440 }
441
442 pub fn work_cancel(&self, reason: Value) -> Promise {
443 let result = Promise::new();
444 result.resolve(Value::Bool(self.cancel(reason)));
445 result
446 }
447
448 pub fn work_events(&self, after: usize) -> Value {
449 let cursor = Rc::new(RefCell::new(WorkEventCursor {
450 next: after,
451 pending: None,
452 closed: false,
453 }));
454 self.inner
455 .events
456 .borrow_mut()
457 .cursors
458 .push(Rc::downgrade(&cursor));
459 let events = self.inner.events.clone();
460 let next_cursor = cursor.clone();
461 let next = Rc::new(move || event_next(&events, &next_cursor));
462 let events = self.inner.events.clone();
463 let close_cursor = cursor;
464 let close = Rc::new(move || {
465 let pending = {
466 let mut cursor = close_cursor.borrow_mut();
467 cursor.closed = true;
468 cursor.pending.take()
469 };
470 if let Some(pending) = pending {
471 pending.resolve(Value::Nil);
472 }
473 events.borrow_mut().cursors.retain(|candidate| {
474 candidate
475 .upgrade()
476 .is_some_and(|candidate| !Rc::ptr_eq(&candidate, &close_cursor))
477 });
478 Ok(())
479 });
480 crate::core::host_stream(next, close)
481 }
482
483 pub fn emit(&self, kind: Value, data: Value) -> bool {
485 if self.closed() {
486 return false;
487 }
488 let kind = match kind {
489 Value::Keyword(value) => value.to_string(),
490 Value::Symbol(value) => value.to_string(),
491 Value::String(value) => value,
492 _ => return false,
493 };
494 self.publish_domain_event(&kind, data);
495 true
496 }
497
498 pub fn cancel(&self, reason: Value) -> bool {
499 let rejection = cancellation_rejection(reason.clone());
500 {
501 let mut cancellation = self.inner.cancellation.borrow_mut();
502 if cancellation.is_some() || self.closed() {
503 return false;
504 }
505 *cancellation = Some(CancellationRequest {
506 reason: reason.clone(),
507 rejection: rejection.clone(),
508 });
509 }
510
511 let previous = {
512 let mut status = self.inner.status.borrow_mut();
513 let previous = status.state;
514 if !previous.terminal() {
515 status.state = WorkRunState::Cancelling;
516 status.cancel_reason = Some(reason.clone());
517 }
518 previous
519 };
520 self.publish_event(
521 "work/run-cancelling",
522 WorkRunState::Cancelling,
523 Some(reason.clone()),
524 false,
525 );
526 if previous == WorkRunState::Queued {
527 if let Some(host) = self.inner.host.upgrade() {
528 host.borrow_mut()
529 .queue
530 .retain(|pending| pending.id != self.inner.id);
531 }
532 self.inner.body_done.set(true);
533 }
534 let active = self.inner.active_promise.borrow().clone();
535 if let Some(active) = active {
536 active.cancel();
537 }
538 let children = self
539 .inner
540 .children
541 .borrow()
542 .values()
543 .cloned()
544 .collect::<Vec<_>>();
545 for child in children {
546 child.cancel(reason.clone());
547 }
548 self.finish_if_ready();
549 true
550 }
551
552 pub fn closed(&self) -> bool {
553 self.inner.status.borrow().state.terminal()
554 }
555
556 pub fn same_identity(&self, other: &Self) -> bool {
557 Rc::ptr_eq(&self.inner, &other.inner)
558 }
559
560 fn accepts_children(&self) -> bool {
561 !self.inner.body_done.get()
562 && !self.inner.finalizers_started.get()
563 && self.inner.cancellation.borrow().is_none()
564 && !self.closed()
565 }
566
567 fn attach_child(&self, child: WorkRun) -> bool {
568 if !self.accepts_children() {
569 return false;
570 }
571 self.inner
572 .children
573 .borrow_mut()
574 .insert(child.work_id(), child);
575 true
576 }
577
578 fn child_closed(&self, child: &WorkRun) {
579 self.inner.children.borrow_mut().remove(&child.work_id());
580 self.finish_if_ready();
581 }
582
583 fn mark_running(&self) -> bool {
584 self.check_deadline();
585 let mut status = self.inner.status.borrow_mut();
586 if status.state != WorkRunState::Queued {
587 return false;
588 }
589 status.state = WorkRunState::Running;
590 drop(status);
591 self.publish_event("work/run-running", WorkRunState::Running, None, false);
592 true
593 }
594
595 fn settle_body(&self, value: Value) {
596 if let Value::Promise(source) = value {
597 if source.same_identity(&self.inner.result) {
598 self.fail_body(work_failure("work result promise adoption cycle".into()));
599 return;
600 }
601 *self.inner.active_promise.borrow_mut() = Some(source.clone());
602 if self.inner.cancellation.borrow().is_some() {
603 source.cancel();
604 }
605 let run = Rc::downgrade(&self.inner);
606 source.on_settle(Rc::new(move |state| {
607 let Some(inner) = run.upgrade() else {
608 return;
609 };
610 let run = WorkRun { inner };
611 run.inner.active_promise.borrow_mut().take();
612 match state {
613 PromiseState::Pending => return,
614 PromiseState::Fulfilled(value) => {
615 *run.inner.body_outcome.borrow_mut() = Some(Ok(value));
616 }
617 PromiseState::Rejected(error) => {
618 *run.inner.body_outcome.borrow_mut() = Some(Err(error));
619 }
620 }
621 run.inner.body_done.set(true);
622 run.finish_if_ready();
623 }));
624 self.set_nonterminal_state(if self.inner.cancellation.borrow().is_some() {
625 WorkRunState::Cancelling
626 } else {
627 WorkRunState::Waiting
628 });
629 source.state();
630 return;
631 }
632 *self.inner.body_outcome.borrow_mut() = Some(Ok(value));
633 self.inner.body_done.set(true);
634 self.finish_if_ready();
635 }
636
637 fn fail_body(&self, error: PromiseRejection) {
638 *self.inner.body_outcome.borrow_mut() = Some(Err(error));
639 self.inner.body_done.set(true);
640 self.finish_if_ready();
641 }
642
643 fn progress_active_promise(&self) {
644 let active = self.inner.active_promise.borrow().clone();
645 if let Some(active) = active {
646 active.state();
647 }
648 }
649
650 fn check_deadline(&self) {
651 if self.inner.deadline.is_some_and(WorkDeadline::expired) {
652 self.cancel(Value::Keyword("deadline-exceeded".into()));
653 }
654 }
655
656 fn register_finalizer(&self, finalizer: WorkFinalizer) -> bool {
657 if self.inner.finalizers_started.get() || self.closed() {
658 return false;
659 }
660 self.inner.finalizers.borrow_mut().push(finalizer);
661 true
662 }
663
664 fn finish_if_ready(&self) {
665 if !self.inner.body_done.get() {
666 return;
667 }
668 if !self.inner.children.borrow().is_empty() {
669 self.set_nonterminal_state(if self.inner.cancellation.borrow().is_some() {
670 WorkRunState::Cancelling
671 } else {
672 WorkRunState::Waiting
673 });
674 return;
675 }
676 if self.inner.finalizers_started.replace(true) {
677 return;
678 }
679
680 let context = self.context();
681 let mut finalizer_error = None;
682 let mut finalizers = std::mem::take(&mut *self.inner.finalizers.borrow_mut());
683 while let Some(finalizer) = finalizers.pop() {
684 let result = with_current_work_context(context.clone(), || finalizer(context.clone()));
685 if finalizer_error.is_none() {
686 if let Err(error) = result {
687 finalizer_error = Some(error);
688 }
689 }
690 }
691
692 if let Some(cancellation) = self.inner.cancellation.borrow().clone() {
693 self.settle_terminal(
694 WorkRunState::Cancelled,
695 Some(cancellation.rejection.clone()),
696 Some(cancellation.reason),
697 None,
698 );
699 return;
700 }
701 if let Some(error) = finalizer_error {
702 self.settle_terminal(WorkRunState::Failed, Some(error), None, None);
703 return;
704 }
705 match self.inner.body_outcome.borrow_mut().take() {
706 Some(Ok(value)) => {
707 self.settle_terminal(WorkRunState::Completed, None, None, Some(value));
708 }
709 Some(Err(error)) => {
710 self.settle_terminal(WorkRunState::Failed, Some(error), None, None);
711 }
712 None => {
713 self.settle_terminal(
714 WorkRunState::Failed,
715 Some(work_failure("work body produced no outcome".into())),
716 None,
717 None,
718 );
719 }
720 }
721 }
722
723 fn settle_terminal(
724 &self,
725 state: WorkRunState,
726 error: Option<PromiseRejection>,
727 cancel_reason: Option<Value>,
728 value: Option<Value>,
729 ) -> bool {
730 {
731 let mut status = self.inner.status.borrow_mut();
732 if status.state.terminal() {
733 return false;
734 }
735 status.state = state;
736 status.finished_at_millis = Some(now_millis());
737 status.error = error.clone();
738 status.cancel_reason = cancel_reason.clone();
739 }
740 let event_detail = if state == WorkRunState::Failed {
741 error.as_ref().map(PromiseRejection::value)
742 } else {
743 cancel_reason
744 };
745 match state {
746 WorkRunState::Completed => {
747 self.inner.result.resolve(value.unwrap_or(Value::Nil));
748 }
749 WorkRunState::Failed | WorkRunState::Cancelled => {
750 self.inner
751 .result
752 .reject_rejection(error.expect("terminal failure requires rejection"));
753 }
754 _ => unreachable!("non-terminal state passed to settle_terminal"),
755 }
756 self.publish_event(work_event_type(state), state, event_detail, true);
757 self.notify_parent();
758 true
759 }
760
761 fn set_nonterminal_state(&self, state: WorkRunState) {
762 let mut status = self.inner.status.borrow_mut();
763 let changed = !status.state.terminal() && status.state != state;
764 if changed {
765 status.state = state;
766 }
767 drop(status);
768 if changed && state == WorkRunState::Waiting {
769 self.publish_event("work/run-waiting", state, None, false);
770 }
771 }
772
773 fn notify_parent(&self) {
774 if self.inner.parent_notified.replace(true) {
775 return;
776 }
777 let Some(parent) = self.inner.parent.as_ref().and_then(Weak::upgrade) else {
778 return;
779 };
780 WorkRun { inner: parent }.child_closed(self);
781 }
782
783 fn context(&self) -> WorkContext {
784 let host = self
785 .inner
786 .host
787 .upgrade()
788 .map(|inner| WorkHost { inner })
789 .expect("work host was dropped while run remained live");
790 WorkContext {
791 host,
792 run: self.clone(),
793 }
794 }
795
796 fn publish_event(
797 &self,
798 kind: &str,
799 state: WorkRunState,
800 detail: Option<Value>,
801 terminal: bool,
802 ) {
803 let pending = {
804 let mut events = self.inner.events.borrow_mut();
805 let sequence = events.values.len() + 1;
806 events
807 .values
808 .push(work_event(&self.inner.id, sequence, kind, state, detail));
809 if terminal {
810 events.closed = true;
811 }
812 let mut pending = Vec::new();
813 let mut retained = Vec::new();
814 for weak in std::mem::take(&mut events.cursors) {
815 let Some(cursor) = weak.upgrade() else {
816 continue;
817 };
818 if let Some(settlement) = event_take(&events, &mut cursor.borrow_mut()) {
819 pending.push(settlement);
820 }
821 retained.push(Rc::downgrade(&cursor));
822 }
823 events.cursors = retained;
824 pending
825 };
826 for (promise, value) in pending {
827 promise.resolve(value);
828 }
829 }
830
831 fn publish_domain_event(&self, kind: &str, data: Value) {
832 let pending = {
833 let mut events = self.inner.events.borrow_mut();
834 let sequence = events.values.len() + 1;
835 events.values.push(Value::Map(
836 [
837 (
838 Value::Keyword("event/type".into()),
839 Value::Keyword(kind.into()),
840 ),
841 (
842 Value::Keyword("event/run".into()),
843 Value::String(self.inner.id.to_string()),
844 ),
845 (
846 Value::Keyword("event/sequence".into()),
847 Value::Number(sequence as i64),
848 ),
849 (Value::Keyword("event/data".into()), data),
850 ]
851 .into_iter()
852 .collect(),
853 ));
854 let mut pending = Vec::new();
855 let mut retained = Vec::new();
856 for weak in std::mem::take(&mut events.cursors) {
857 let Some(cursor) = weak.upgrade() else {
858 continue;
859 };
860 if let Some(settlement) = event_take(&events, &mut cursor.borrow_mut()) {
861 pending.push(settlement);
862 }
863 retained.push(Rc::downgrade(&cursor));
864 }
865 events.cursors = retained;
866 pending
867 };
868 for (promise, value) in pending {
869 promise.resolve(value);
870 }
871 }
872}
873
874fn work_event(
875 id: &WorkId,
876 sequence: usize,
877 kind: &str,
878 state: WorkRunState,
879 detail: Option<Value>,
880) -> Value {
881 let data = Value::Map(
882 [
883 (
884 Value::Keyword("state".into()),
885 Value::Keyword(work_state_name(state).into()),
886 ),
887 (
888 Value::Keyword("detail".into()),
889 detail.unwrap_or(Value::Nil),
890 ),
891 ]
892 .into_iter()
893 .collect(),
894 );
895 Value::Map(
896 [
897 (
898 Value::Keyword("event/type".into()),
899 Value::Keyword(kind.into()),
900 ),
901 (
902 Value::Keyword("event/run".into()),
903 Value::String(id.to_string()),
904 ),
905 (
906 Value::Keyword("event/sequence".into()),
907 Value::Number(sequence as i64),
908 ),
909 (Value::Keyword("event/data".into()), data),
910 ]
911 .into_iter()
912 .collect(),
913 )
914}
915
916fn work_state_name(state: WorkRunState) -> &'static str {
917 match state {
918 WorkRunState::Queued => "queued",
919 WorkRunState::Running => "running",
920 WorkRunState::Waiting => "waiting",
921 WorkRunState::Cancelling => "cancelling",
922 WorkRunState::Completed => "completed",
923 WorkRunState::Failed => "failed",
924 WorkRunState::Cancelled => "cancelled",
925 }
926}
927
928fn work_event_type(state: WorkRunState) -> &'static str {
929 match state {
930 WorkRunState::Completed => "work/run-completed",
931 WorkRunState::Failed => "work/run-failed",
932 WorkRunState::Cancelled => "work/run-cancelled",
933 _ => unreachable!("terminal work event requires a terminal state"),
934 }
935}
936
937fn event_take(events: &WorkEventLog, cursor: &mut WorkEventCursor) -> Option<(Promise, Value)> {
938 let promise = cursor.pending.take()?;
939 if cursor.next < events.values.len() {
940 let value = events.values[cursor.next].clone();
941 cursor.next += 1;
942 return Some((promise, value));
943 }
944 if events.closed || cursor.closed {
945 cursor.closed = true;
946 return Some((promise, Value::Nil));
947 }
948 cursor.pending = Some(promise);
949 None
950}
951
952fn event_next(
953 events: &Rc<RefCell<WorkEventLog>>,
954 cursor: &Rc<RefCell<WorkEventCursor>>,
955) -> Result<Promise, String> {
956 let promise = Promise::new();
957 let settlement = {
958 let events = events.borrow();
959 let mut cursor = cursor.borrow_mut();
960 if cursor.closed {
961 Some((promise.clone(), Value::Nil))
962 } else if cursor.pending.is_some() {
963 return Err("stream/pending-pull: only one Stream/next may be pending".into());
964 } else {
965 cursor.pending = Some(promise.clone());
966 event_take(&events, &mut cursor)
967 }
968 };
969 if let Some((target, value)) = settlement {
970 target.resolve(value);
971 }
972 Ok(promise)
973}
974
975#[cfg(test)]
976mod tests;