1use super::{
2 Arc, BTreeMap, Budget, Checkpoint, CheckpointError, CheckpointErrorKind, CheckpointId,
3 ClaimedWorkflow, Duration, LeaseDuration, Mutex, MutexGuard, Reverse, SystemWorkflowClock,
4 Usage, WorkerId, WorkflowBudgetAuditCursor, WorkflowBudgetAuditEvent, WorkflowBudgetAuditKind,
5 WorkflowBudgetAuditLimit, WorkflowBudgetAuditProjectionId, WorkflowBudgetAuditProjectionLease,
6 WorkflowBudgetForfeitReason, WorkflowBudgetReservationOutcome, WorkflowCancelOutcome,
7 WorkflowCheckpointHistoryLimit, WorkflowCheckpointPhase, WorkflowCheckpointRevision,
8 WorkflowClock, WorkflowDisposition, WorkflowForkCommand, WorkflowForkOutcome,
9 WorkflowInterruptRequest, WorkflowLease, WorkflowLineage, WorkflowSignal, WorkflowSignalId,
10 WorkflowSignalOutcome, WorkflowSignalRetention, WorkflowSignalSnapshot, WorkflowSignalState,
11 WorkflowStore, WorkflowStoreError, WorkflowStoreErrorKind, WorkflowStoreFuture, WorkflowTask,
12 WorkflowTaskSnapshot, WorkflowTaskStatus, WorkflowTenantBudgetPolicy,
13 WorkflowTenantBudgetSnapshot, WorkflowTenantId, WorkflowTenantListLimit, WorkflowTenantPolicy,
14 WorkflowWait, WorkflowWake, decode_revision, fork_checkpoint,
15};
16
17mod budget;
18mod checkpoint;
19mod signal;
20mod task;
21
22use signal::take_buffered_signal;
23use task::{is_non_terminal, require_current_lease, require_tenant, workflow_not_found};
24
25#[derive(Clone)]
27pub struct InMemoryWorkflowStore {
28 tasks: Arc<Mutex<BTreeMap<CheckpointId, StoredTask>>>,
29 checkpoints: Arc<Mutex<StoredCheckpoints>>,
30 signals: Arc<Mutex<BTreeMap<WorkflowSignalId, StoredSignal>>>,
31 admission: Arc<Mutex<AdmissionState>>,
32 clock: Arc<dyn WorkflowClock>,
33}
34
35impl Default for InMemoryWorkflowStore {
36 fn default() -> Self {
37 Self::with_clock(Arc::new(SystemWorkflowClock))
38 }
39}
40
41impl InMemoryWorkflowStore {
42 pub fn new() -> Self {
44 Self::default()
45 }
46
47 pub fn with_clock(clock: Arc<dyn WorkflowClock>) -> Self {
49 Self {
50 tasks: Arc::new(Mutex::new(BTreeMap::new())),
51 checkpoints: Arc::new(Mutex::new(StoredCheckpoints::default())),
52 signals: Arc::new(Mutex::new(BTreeMap::new())),
53 admission: Arc::new(Mutex::new(AdmissionState::default())),
54 clock,
55 }
56 }
57
58 fn tasks(&self) -> MutexGuard<'_, BTreeMap<CheckpointId, StoredTask>> {
59 self.tasks
60 .lock()
61 .unwrap_or_else(std::sync::PoisonError::into_inner)
62 }
63}
64
65impl std::fmt::Debug for InMemoryWorkflowStore {
66 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67 formatter
68 .debug_struct("InMemoryWorkflowStore")
69 .finish_non_exhaustive()
70 }
71}
72
73#[derive(Debug, Default)]
74struct AdmissionState {
75 tenants: BTreeMap<WorkflowTenantId, StoredTenant>,
76 budgets: BTreeMap<WorkflowTenantId, StoredTenantBudget>,
77 budget_audit_projections:
78 BTreeMap<(WorkflowTenantId, WorkflowBudgetAuditProjectionId), StoredBudgetAuditProjection>,
79 next_claim_sequence: u64,
80}
81
82#[derive(Clone, Copy, Debug)]
83struct StoredTenant {
84 policy: WorkflowTenantPolicy,
85 last_claim_sequence: u64,
86}
87
88#[derive(Clone, Debug)]
89struct StoredTenantBudget {
90 policy: WorkflowTenantBudgetPolicy,
91 window_started_at_ms: u64,
92 committed: Usage,
93 reserved: Usage,
94 reservations: BTreeMap<CheckpointId, StoredBudgetReservation>,
95 next_audit_sequence: u64,
96 audit_events: Vec<StoredBudgetAuditEvent>,
97}
98
99#[derive(Clone, Copy, Debug)]
100struct StoredBudgetReservation {
101 baseline: Usage,
102 amount: Usage,
103 reserved_at_ms: u64,
104 expires_at_ms: u64,
105}
106
107#[derive(Clone, Copy, Debug)]
108struct StoredBudgetAuditEvent {
109 cursor: WorkflowBudgetAuditCursor,
110 checkpoint_id: Option<CheckpointId>,
111 occurred_at_ms: u64,
112 kind: WorkflowBudgetAuditKind,
113 usage: Usage,
114 reservation_age_ms: Option<u64>,
115 limit: Budget,
116 committed: Usage,
117 reserved: Usage,
118}
119
120#[derive(Clone, Debug, Default)]
121struct StoredBudgetAuditProjection {
122 cursor: WorkflowBudgetAuditCursor,
123 owner: Option<WorkerId>,
124 fencing_token: u64,
125 expires_at_ms: Option<u64>,
126}
127
128#[derive(Clone, Debug)]
129struct StoredTask {
130 task: WorkflowTask,
131 state: StoredState,
132 attempts: u64,
133 fencing_token: u64,
134 wake: Option<WorkflowWake>,
135 lineage: Option<WorkflowLineage>,
136 created_at_ms: u64,
137 updated_at_ms: u64,
138}
139
140#[derive(Debug, Default)]
141struct StoredCheckpoints {
142 latest: BTreeMap<CheckpointId, Checkpoint>,
143 history: BTreeMap<(CheckpointId, u64), Checkpoint>,
144}
145
146#[derive(Clone, Debug)]
147enum StoredState {
148 Queued {
149 available_at_ms: u64,
150 },
151 Leased(WorkflowLease),
152 WaitingTimer {
153 wake_at_ms: u64,
154 },
155 WaitingSignal {
156 name: crate::WorkflowSignalName,
157 },
158 WaitingSignalOrTimeout {
159 name: crate::WorkflowSignalName,
160 wake_at_ms: u64,
161 },
162 WaitingInterrupt {
163 request: WorkflowInterruptRequest,
164 },
165 Completed,
166 Failed(String),
167 Cancelled,
168}
169
170#[derive(Clone, Debug)]
171struct StoredSignal {
172 tenant_id: WorkflowTenantId,
173 signal: WorkflowSignal,
174 consumed: bool,
175 dead_lettered: bool,
176 accepted_at_ms: u64,
177}
178
179impl InMemoryWorkflowStore {
180 fn suspend(
181 &self,
182 stored: &mut StoredTask,
183 checkpoint_id: CheckpointId,
184 wait: WorkflowWait,
185 now: u64,
186 ) -> StoredState {
187 match wait {
188 WorkflowWait::Timer { delay_ms } => {
189 stored.wake = None;
190 StoredState::WaitingTimer {
191 wake_at_ms: now.saturating_add(delay_ms),
192 }
193 }
194 WorkflowWait::Signal { name } => {
195 self.suspend_signal(stored, checkpoint_id, name, None, now)
196 }
197 WorkflowWait::SignalOrTimeout { name, timeout_ms } => {
198 self.suspend_signal(stored, checkpoint_id, name, Some(timeout_ms), now)
199 }
200 WorkflowWait::Interrupt { request } => {
201 let name = request.signal_name();
202 let mut signals = self
203 .signals
204 .lock()
205 .unwrap_or_else(std::sync::PoisonError::into_inner);
206 if let Some(wake) = take_buffered_signal(&mut signals, checkpoint_id, &name) {
207 stored.wake = Some(wake);
208 StoredState::Queued {
209 available_at_ms: now,
210 }
211 } else {
212 stored.wake = None;
213 StoredState::WaitingInterrupt { request }
214 }
215 }
216 }
217 }
218
219 fn suspend_signal(
220 &self,
221 stored: &mut StoredTask,
222 checkpoint_id: CheckpointId,
223 name: crate::WorkflowSignalName,
224 timeout_ms: Option<u64>,
225 now: u64,
226 ) -> StoredState {
227 let mut signals = self
228 .signals
229 .lock()
230 .unwrap_or_else(std::sync::PoisonError::into_inner);
231 if let Some(wake) = take_buffered_signal(&mut signals, checkpoint_id, &name) {
232 stored.wake = Some(wake);
233 return StoredState::Queued {
234 available_at_ms: now,
235 };
236 }
237 stored.wake = None;
238 match timeout_ms {
239 Some(timeout_ms) => StoredState::WaitingSignalOrTimeout {
240 name,
241 wake_at_ms: now.saturating_add(timeout_ms),
242 },
243 None => StoredState::WaitingSignal { name },
244 }
245 }
246}
247
248impl WorkflowStore for InMemoryWorkflowStore {
249 fn set_tenant_policy(
250 &self,
251 tenant_id: WorkflowTenantId,
252 policy: WorkflowTenantPolicy,
253 ) -> WorkflowStoreFuture<'_, Result<(), WorkflowStoreError>> {
254 self.set_tenant_policy_impl(tenant_id, policy)
255 }
256
257 fn set_tenant_budget_policy(
258 &self,
259 tenant_id: WorkflowTenantId,
260 policy: WorkflowTenantBudgetPolicy,
261 ) -> WorkflowStoreFuture<'_, Result<(), WorkflowStoreError>> {
262 self.set_tenant_budget_policy_impl(tenant_id, policy)
263 }
264
265 fn list_tenant_budgets(
266 &self,
267 after: Option<WorkflowTenantId>,
268 limit: WorkflowTenantListLimit,
269 ) -> WorkflowStoreFuture<'_, Result<Vec<WorkflowTenantId>, WorkflowStoreError>> {
270 self.list_tenant_budgets_impl(after, limit)
271 }
272
273 fn inspect_tenant_budget(
274 &self,
275 tenant_id: WorkflowTenantId,
276 ) -> WorkflowStoreFuture<'_, Result<WorkflowTenantBudgetSnapshot, WorkflowStoreError>> {
277 self.inspect_tenant_budget_impl(tenant_id)
278 }
279
280 fn list_tenant_budget_audit(
281 &self,
282 tenant_id: WorkflowTenantId,
283 after: Option<WorkflowBudgetAuditCursor>,
284 limit: WorkflowBudgetAuditLimit,
285 ) -> WorkflowStoreFuture<'_, Result<Vec<WorkflowBudgetAuditEvent>, WorkflowStoreError>> {
286 self.list_tenant_budget_audit_impl(tenant_id, after, limit)
287 }
288
289 fn compact_tenant_budget_audit(
290 &self,
291 tenant_id: WorkflowTenantId,
292 through: WorkflowBudgetAuditCursor,
293 ) -> WorkflowStoreFuture<'_, Result<u64, WorkflowStoreError>> {
294 self.compact_tenant_budget_audit_impl(tenant_id, through)
295 }
296
297 fn load_or_create_tenant_budget_audit_projection(
298 &self,
299 tenant_id: WorkflowTenantId,
300 projection_id: WorkflowBudgetAuditProjectionId,
301 ) -> WorkflowStoreFuture<'_, Result<WorkflowBudgetAuditCursor, WorkflowStoreError>> {
302 self.load_or_create_tenant_budget_audit_projection_impl(tenant_id, projection_id)
303 }
304
305 fn advance_tenant_budget_audit_projection(
306 &self,
307 tenant_id: WorkflowTenantId,
308 projection_id: WorkflowBudgetAuditProjectionId,
309 expected: WorkflowBudgetAuditCursor,
310 next: WorkflowBudgetAuditCursor,
311 ) -> WorkflowStoreFuture<'_, Result<bool, WorkflowStoreError>> {
312 self.advance_tenant_budget_audit_projection_impl(tenant_id, projection_id, expected, next)
313 }
314
315 fn claim_tenant_budget_audit_projection(
316 &self,
317 tenant_id: WorkflowTenantId,
318 projection_id: WorkflowBudgetAuditProjectionId,
319 owner: WorkerId,
320 lease: LeaseDuration,
321 ) -> WorkflowStoreFuture<
322 '_,
323 Result<Option<WorkflowBudgetAuditProjectionLease>, WorkflowStoreError>,
324 > {
325 self.claim_tenant_budget_audit_projection_impl(tenant_id, projection_id, owner, lease)
326 }
327
328 fn heartbeat_tenant_budget_audit_projection(
329 &self,
330 lease: WorkflowBudgetAuditProjectionLease,
331 extension: LeaseDuration,
332 ) -> WorkflowStoreFuture<'_, Result<WorkflowBudgetAuditProjectionLease, WorkflowStoreError>>
333 {
334 self.heartbeat_tenant_budget_audit_projection_impl(lease, extension)
335 }
336
337 fn advance_tenant_budget_audit_projection_lease(
338 &self,
339 lease: WorkflowBudgetAuditProjectionLease,
340 next: WorkflowBudgetAuditCursor,
341 ) -> WorkflowStoreFuture<'_, Result<WorkflowBudgetAuditProjectionLease, WorkflowStoreError>>
342 {
343 self.advance_tenant_budget_audit_projection_lease_impl(lease, next)
344 }
345
346 fn release_tenant_budget_audit_projection(
347 &self,
348 lease: WorkflowBudgetAuditProjectionLease,
349 ) -> WorkflowStoreFuture<'_, Result<(), WorkflowStoreError>> {
350 self.release_tenant_budget_audit_projection_impl(lease)
351 }
352
353 fn reserve_budget(
354 &self,
355 lease: WorkflowLease,
356 workflow_limit: Budget,
357 baseline: Usage,
358 ) -> WorkflowStoreFuture<'_, Result<WorkflowBudgetReservationOutcome, WorkflowStoreError>> {
359 self.reserve_budget_impl(lease, workflow_limit, baseline)
360 }
361
362 fn settle_budget(
363 &self,
364 lease: WorkflowLease,
365 cumulative: Usage,
366 ) -> WorkflowStoreFuture<'_, Result<(), WorkflowStoreError>> {
367 self.settle_budget_impl(lease, cumulative)
368 }
369
370 fn enqueue(
371 &self,
372 task: WorkflowTask,
373 ) -> WorkflowStoreFuture<'_, Result<(), WorkflowStoreError>> {
374 self.enqueue_impl(task)
375 }
376
377 fn claim(
378 &self,
379 worker: WorkerId,
380 lease: LeaseDuration,
381 ) -> WorkflowStoreFuture<'_, Result<Option<ClaimedWorkflow>, WorkflowStoreError>> {
382 self.claim_impl(worker, lease)
383 }
384
385 fn heartbeat(
386 &self,
387 lease: WorkflowLease,
388 extension: LeaseDuration,
389 ) -> WorkflowStoreFuture<'_, Result<WorkflowLease, WorkflowStoreError>> {
390 self.heartbeat_impl(lease, extension)
391 }
392
393 fn finish(
394 &self,
395 lease: WorkflowLease,
396 disposition: WorkflowDisposition,
397 ) -> WorkflowStoreFuture<'_, Result<(), WorkflowStoreError>> {
398 self.finish_impl(lease, disposition)
399 }
400
401 fn publish_signal(
402 &self,
403 tenant_id: WorkflowTenantId,
404 signal: WorkflowSignal,
405 ) -> WorkflowStoreFuture<'_, Result<WorkflowSignalOutcome, WorkflowStoreError>> {
406 self.publish_signal_impl(tenant_id, signal)
407 }
408
409 fn cancel(
410 &self,
411 tenant_id: WorkflowTenantId,
412 checkpoint_id: CheckpointId,
413 ) -> WorkflowStoreFuture<'_, Result<WorkflowCancelOutcome, WorkflowStoreError>> {
414 self.cancel_impl(tenant_id, checkpoint_id)
415 }
416
417 fn inspect_signal(
418 &self,
419 tenant_id: WorkflowTenantId,
420 signal_id: WorkflowSignalId,
421 ) -> WorkflowStoreFuture<'_, Result<WorkflowSignalSnapshot, WorkflowStoreError>> {
422 self.inspect_signal_impl(tenant_id, signal_id)
423 }
424
425 fn compact_signals(
426 &self,
427 tenant_id: WorkflowTenantId,
428 retention: WorkflowSignalRetention,
429 ) -> WorkflowStoreFuture<'_, Result<u64, WorkflowStoreError>> {
430 self.compact_signals_impl(tenant_id, retention)
431 }
432
433 fn inspect(
434 &self,
435 tenant_id: WorkflowTenantId,
436 checkpoint_id: CheckpointId,
437 ) -> WorkflowStoreFuture<'_, Result<WorkflowTaskSnapshot, WorkflowStoreError>> {
438 self.inspect_impl(tenant_id, checkpoint_id)
439 }
440
441 fn list_checkpoint_history(
442 &self,
443 tenant_id: WorkflowTenantId,
444 checkpoint_id: CheckpointId,
445 after_revision: Option<u64>,
446 limit: WorkflowCheckpointHistoryLimit,
447 ) -> WorkflowStoreFuture<'_, Result<Vec<WorkflowCheckpointRevision>, WorkflowStoreError>> {
448 self.list_checkpoint_history_impl(tenant_id, checkpoint_id, after_revision, limit)
449 }
450
451 fn load_checkpoint_revision(
452 &self,
453 tenant_id: WorkflowTenantId,
454 checkpoint_id: CheckpointId,
455 revision: u64,
456 ) -> WorkflowStoreFuture<'_, Result<WorkflowCheckpointRevision, WorkflowStoreError>> {
457 self.load_checkpoint_revision_impl(tenant_id, checkpoint_id, revision)
458 }
459
460 fn fork_workflow(
461 &self,
462 tenant_id: WorkflowTenantId,
463 command: WorkflowForkCommand,
464 ) -> WorkflowStoreFuture<'_, Result<WorkflowForkOutcome, WorkflowStoreError>> {
465 self.fork_workflow_impl(tenant_id, command)
466 }
467
468 fn load_checkpoint(
469 &self,
470 lease: WorkflowLease,
471 ) -> WorkflowStoreFuture<'_, Result<Checkpoint, CheckpointError>> {
472 self.load_checkpoint_impl(lease)
473 }
474
475 fn compare_and_swap_checkpoint(
476 &self,
477 lease: WorkflowLease,
478 checkpoint: Checkpoint,
479 expected_revision: Option<u64>,
480 ) -> WorkflowStoreFuture<'_, Result<(), CheckpointError>> {
481 self.compare_and_swap_checkpoint_impl(lease, checkpoint, expected_revision)
482 }
483}