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