Skip to content
File

Blob: src/workerd/util/state-machine.h

cpp2156 lines
1// Copyright (c) 2017-2025 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4 
5#pragma once
6 
7// MSVC uses a different attribute name for no_unique_address
8#if _MSC_VER
9#define WD_NO_UNIQUE_ADDRESS [[msvc::no_unique_address]]
10#else
11#define WD_NO_UNIQUE_ADDRESS [[no_unique_address]]
12#endif
13 
14// State Machine Abstraction built on kj::OneOf.
15// TODO(later): If this proves useful, consider moving it into kj itself as there
16// are no workerd-specific dependencies.
17//
18// Entire implementation was Claude-generated initially.
19//
20// Most of the detailed doc comments here are largely intended to be used by agents
21// and tooling. Human readers may prefer to just skip to the actual code.
22//
23// This header provides utilities for building type-safe state machines using kj::OneOf.
24// It addresses common patterns found throughout the workerd codebase with improvements
25// that provide tangible benefits over raw kj::OneOf usage.
26//
27// =============================================================================
28// WHY USE THIS INSTEAD OF RAW kj::OneOf?
29// =============================================================================
30//
31// Throughout workerd, we use kj::OneOf as a state machine to track the lifecycle
32// of streams, readers, writers, and other resources. A typical pattern looks like:
33//
34// kj::OneOf<Readable, Closed, kj::Exception> state;
35//
36// void read() {
37// KJ_SWITCH_ONEOF(state) {
38// KJ_CASE_ONEOF(readable, Readable) {
39// auto data = readable.source->read(); // Get reference to state
40// processData(data); // Call some function...
41// readable.source->advance(); // Use reference again - UAF!
42// }
43// KJ_CASE_ONEOF(closed, Closed) { ... }
44// KJ_CASE_ONEOF(err, kj::Exception) { ... }
45// }
46// }
47//
48// THE PROBLEM: Use-After-Free (UAF) from unsound state-transitions
49//
50// The `readable` reference points into the kj::OneOf's internal storage. If ANY
51// code path between obtaining that reference and using it triggers a state
52// transition (even indirectly through callbacks, promise continuations, or
53// nested calls), the reference becomes dangling:
54//
55// KJ_CASE_ONEOF(readable, Readable) {
56// readable.source->read(); // This might call back into our code...
57// // ...which might call close()...
58// // ...which does state.init<Closed>()
59// readable.buffer.size(); // UAF! readable is now destroyed
60// }
61//
62// This is particularly insidious because:
63// 1. The bug may not manifest in simple tests
64// 2. It depends on complex callback chains that are hard to reason about
65// 3. It causes memory corruption that may crash much later
66// 4. ASAN/valgrind may not catch it if the memory is quickly reused
67//
68// HOW StateMachine HELPS:
69//
70// 1. TRANSITION LOCKING via whenState()/whenActive():
71//
72// state.whenState<Readable>([](Readable& r) {
73// r.source->read(); // If this tries to transition...
74// r.buffer.size(); // ...it throws instead of UAF
75// });
76//
77// The callback holds a "transition lock" - any attempt to transition the
78// state machine while the lock is held will throw an exception instead of
79// silently corrupting memory. This turns silent UAF into a loud, debuggable
80// failure.
81//
82// 2. DEFERRED TRANSITIONS for async operations:
83//
84// When code legitimately needs to transition during an operation (e.g.,
85// a read discovers EOF and needs to close), use deferred transitions:
86//
87// {
88// auto op = state.scopedOperation();
89// state.whenActive([&](Readable& r) {
90// if (r.source->atEof()) {
91// state.deferTransitionTo<Closed>(); // Queued, not immediate
92// }
93// });
94// } // Transition happens here, after callback completes safely
95//
96// 3. TERMINAL STATE ENFORCEMENT:
97//
98// Once a stream is Closed or Errored, it should never transition back to
99// Readable. Raw kj::OneOf allows this silently:
100//
101// state.init<Closed>();
102// state.init<Readable>(...); // Oops - zombie stream!
103//
104// StateMachine with TerminalStates<> will throw if you attempt this,
105// catching the bug immediately.
106//
107// 4. SEMANTIC HELPERS:
108//
109// Instead of: state.is<kj::Exception>() || state.is<Closed>()
110// Write: state.isTerminal() or state.isInactive()
111//
112// Instead of: KJ_IF_SOME(e, state.tryGetUnsafe<kj::Exception>()) { ... }
113// Write: KJ_IF_SOME(e, state.tryGetErrorUnsafe()) { ... }
114//
115// WHEN TO USE:
116//
117// - Simple state tracking: StateMachine<States...> is fine
118// - Resource lifecycle (streams, handles): Use TerminalStates + PendingStates
119// - Migrating existing code: See MIGRATION GUIDE section below
120//
121// =============================================================================
122// STATE MACHINE
123// =============================================================================
124//
125// StateMachine supports composable features via spec types:
126//
127// // Simple (no specs)
128// StateMachine<Idle, Running, Done> basic;
129//
130// // With terminal state enforcement
131// StateMachine<TerminalStates<Done>, Idle, Running, Done> withTerminal;
132//
133// // With error extraction helpers
134// StateMachine<ErrorState<Errored>, Active, Closed, Errored> withError;
135//
136// // With deferred transitions
137// StateMachine<PendingStates<Closed, Errored>, Active, Closed, Errored> withDefer;
138//
139// // Full-featured (combine any specs)
140// StateMachine<
141// TerminalStates<Closed, Errored>,
142// ErrorState<Errored>,
143// ActiveState<Active>,
144// PendingStates<Closed, Errored>,
145// Active, Closed, Errored
146// > fullyFeatured;
147//
148// Available spec types:
149// - TerminalStates<Ts...> - States that cannot be transitioned FROM
150// Enables: isTerminal()
151// - ErrorState<T> - Designates the error state type
152// Enables: isErrored(), tryGetErrorUnsafe(), getErrorUnsafe()
153// - ActiveState<T> - Designates the active/working state type
154// Enables: isActive(), isInactive(), whenActive(), whenActiveOr(),
155// tryGetActiveUnsafe(), requireActiveUnsafe()
156// - PendingStates<Ts...> - States that can be deferred during operations
157// Enables: beginOperation(), endOperation(), deferTransitionTo(), etc.
158//
159// NAMING CONVENTIONS:
160// - isTerminal() = current state is in TerminalStates (enforces no outgoing transitions)
161// - isInactive() = current state is NOT the ActiveState (semantic "done" state)
162//
163// =============================================================================
164// MEMORY SAFETY
165// =============================================================================
166//
167// THREAD SAFETY: State machines are NOT thread-safe. All operations on a
168// single state machine instance must be performed from the same thread.
169// If you need concurrent access, use external synchronization.
170//
171// This utility provides protections against common memory safety issues:
172//
173// 1. TRANSITION LOCKING: The state machine can be locked during callbacks to
174// prevent transitions that would invalidate references:
175//
176// machine.whenState<Active>([](Active& a) {
177// // machine.transitionTo<Closed>(); // Would fail - locked!
178// a.resource->read(); // Safe - Active cannot be destroyed
179// });
180//
181// 2. TRANSITION LOCK ENFORCEMENT: The machine tracks active transition locks
182// and throws if a transition is attempted while locks are held.
183//
184// 3. SAFE ACCESS PATTERNS: Prefer whenState() and whenActive() over get()
185// to ensure references don't outlive their validity.
186//
187// UNSAFE PATTERNS TO AVOID:
188//
189// // DON'T: Store references from getUnsafe() across transitions
190// Active& active = machine.getUnsafe<Active>();
191// machine.transitionTo<Closed>(); // active is now dangling!
192//
193// // DO: Use whenState() for safe scoped access
194// machine.whenState<Active>([](Active& a) {
195// // a is guaranteed valid for the duration of the callback
196// });
197//
198// // DON'T: Transition inside a callback (will fail if locked)
199// machine.whenState<Active>([&](Active& a) {
200// machine.transitionTo<Closed>(); // Fails!
201// });
202//
203// // DO: Return a value and transition after
204// auto result = machine.whenState<Active>([](Active& a) {
205// return a.computeSomething();
206// });
207// machine.transitionTo<Closed>();
208//
209// =============================================================================
210// QUICK START
211// =============================================================================
212//
213// Define your state types (add NAME for introspection):
214//
215// struct Readable {
216// static constexpr kj::StringPtr NAME = "readable"_kj;
217// kj::Own<Source> source;
218// };
219// struct Closed { static constexpr kj::StringPtr NAME = "closed"_kj; };
220// struct Errored {
221// static constexpr kj::StringPtr NAME = "errored"_kj;
222// jsg::Value error;
223// };
224//
225// Basic state machine with safe access:
226//
227// StateMachine<Readable, Closed, Errored> state;
228// state.transitionTo<Readable>(...);
229//
230// // RECOMMENDED: Use whenState() for safe scoped access
231// state.whenState<Readable>([](Readable& r) {
232// r.source->read(); // Safe - transitions blocked during callback
233// });
234//
235// // Or with a return value
236// auto size = state.whenState<Readable>([](Readable& r) {
237// return r.source->size();
238// }); // Returns kj::Maybe<size_t>
239//
240// Stream-like state machine (common pattern in workerd):
241//
242// StateMachine<
243// TerminalStates<Closed, Errored>,
244// ErrorState<Errored>,
245// ActiveState<Readable>,
246// PendingStates<Closed, Errored>,
247// Readable, Closed, Errored
248// > state;
249//
250// state.transitionTo<Readable>(...);
251//
252// // Safe access with whenActive()
253// state.whenActive([](Readable& r) {
254// r.source->doSomething(); // Transitions blocked
255// });
256//
257// // Error checking
258// if (state.isErrored()) { ... }
259// KJ_IF_SOME(err, state.tryGetErrorUnsafe()) { ... }
260//
261// // Deferred transitions during operations
262// state.beginOperation();
263// state.deferTransitionTo<Closed>(); // Deferred until operation ends
264// state.endOperation(); // Now transitions to Closed
265//
266// // Terminal enforcement
267// state.transitionTo<Closed>();
268// state.transitionTo<Readable>(...); // FAILS - can't leave terminal state
269//
270// =============================================================================
271// MIGRATION GUIDE: From kj::OneOf to StateMachine
272// =============================================================================
273//
274// This section describes how to migrate existing kj::OneOf state management
275// to use these StateMachine utilities.
276//
277// STEP 1: Add NAME constants to state types
278// -----------------------------------------
279// StateMachine provides currentStateName() for debugging. Add NAME to states:
280//
281// // Before:
282// struct Closed {};
283//
284// // After:
285// struct Closed {
286// static constexpr kj::StringPtr NAME = "Closed"_kj;
287// };
288//
289// STEP 2: Replace kj::OneOf with appropriate StateMachine
290// --------------------------------------------------------
291//
292// // Before:
293// kj::OneOf<Closed, Errored, Readable> state;
294//
295// // After (basic):
296// StateMachine<Closed, Errored, Readable> state;
297//
298// // After (with features):
299// StateMachine<
300// TerminalStates<Closed, Errored>,
301// ErrorState<Errored>,
302// ActiveState<Readable>,
303// Closed, Errored, Readable
304// > state;
305//
306// STEP 3: Update state assignments to use transitionTo()
307// ------------------------------------------------------
308//
309// // Before:
310// state = Closed{};
311// state = Errored{kj::mv(error)};
312//
313// // After:
314// state.transitionTo<Closed>();
315// state.transitionTo<Errored>(kj::mv(error));
316//
317// STEP 4: Update state checks
318// ---------------------------
319//
320// // Before:
321// if (state.is<Closed>() || state.is<Errored>()) { ... }
322// if (state.is<Errored>()) { ... }
323//
324// // After (with ActiveState<Readable>):
325// if (state.isInactive()) { ... } // Not in active state
326//
327// // After (with ErrorState<Errored>):
328// if (state.isErrored()) { ... }
329//
330// STEP 5: Replace unsafe get() with safe access patterns
331// ------------------------------------------------------
332//
333// // Before (unsafe - reference may dangle if callback transitions):
334// KJ_SWITCH_ONEOF(state) {
335// KJ_CASE_ONEOF(readable, Readable) {
336// readable.source->read(); // May be unsafe
337// }
338// }
339//
340// // After (safe - transitions blocked during callback):
341// state.whenActive([](Readable& r) {
342// r.source->read(); // Safe
343// });
344//
345// // Or for specific state:
346// state.whenState<Readable>([](Readable& r) {
347// r.source->read();
348// });
349//
350// STEP 6: Replace manual deferred-transition bookkeeping
351// ------------------------------------------------------
352// If you have code that tracks pending operations and defers close/error:
353//
354// // Before:
355// bool closing = false;
356// int pendingOps = 0;
357//
358// void startOp() { pendingOps++; }
359// void endOp() {
360// if (--pendingOps == 0 && closing) doClose();
361// }
362// void close() {
363// if (pendingOps > 0) { closing = true; return; }
364// doClose();
365// }
366//
367// // After (with PendingStates<Closed>):
368// void startOp() { state.beginOperation(); }
369// void endOp() { state.endOperation(); } // Auto-applies pending
370// void close() { state.deferTransitionTo<Closed>(); }
371//
372// // Or with RAII:
373// void doWork() {
374// auto op = state.scopedOperation();
375// // ... work ...
376// } // endOperation() called automatically
377//
378// STEP 7: Update visitForGc
379// -------------------------
380//
381// // Before:
382// void visitForGc(jsg::GcVisitor& visitor) {
383// KJ_SWITCH_ONEOF(state) {
384// KJ_CASE_ONEOF(e, Errored) { visitor.visit(e.reason); }
385// // ...
386// }
387// }
388//
389// // After:
390// void visitForGc(jsg::GcVisitor& visitor) {
391// state.visitForGc(visitor); // Visits all GC-able states automatically
392// }
393//
394// STEP 8: KJ_SWITCH_ONEOF still works
395// -----------------------------------
396// If you need to keep KJ_SWITCH_ONEOF for complex logic:
397//
398// KJ_SWITCH_ONEOF(state.underlying()) {
399// KJ_CASE_ONEOF(r, Readable) { ... }
400// KJ_CASE_ONEOF(c, Closed) { ... }
401// KJ_CASE_ONEOF(e, Errored) { ... }
402// }
403//
404// Or use the visitor pattern:
405//
406// state.visit([](auto& s) {
407// using S = kj::Decay<decltype(s)>;
408// if constexpr (kj::isSameType<S, Readable>()) { ... }
409// else if constexpr (kj::isSameType<S, Closed>()) { ... }
410// else { ... }
411// });
412//
413// =============================================================================
414 
415#include <kj/common.h>
416#include <kj/debug.h>
417#include <kj/one-of.h>
418#include <kj/string.h>
419 
420#include <concepts>
421#include <tuple>
422#include <type_traits>
423#include <utility>
424 
425namespace workerd {
426 
427// =============================================================================
428// Type Traits and Helpers
429// =============================================================================
430 
431namespace _ { // private
432 
433// Helper to check if a type is in a parameter pack
434template <typename T, typename... Ts>
435inline constexpr bool isOneOf = false;
436 
437template <typename T, typename First, typename... Rest>
438inline constexpr bool isOneOf<T, First, Rest...> =
439 kj::isSameType<T, First>() || isOneOf<T, Rest...>;
440 
441// Concept: type has a static NAME member of type kj::StringPtr
442template <typename T>
443concept HasStateName = requires {
444 { T::NAME } -> std::convertible_to<kj::StringPtr>;
445};
446 
447// Get state name, using NAME if available, otherwise a placeholder
448template <typename T>
449constexpr kj::StringPtr getStateName() {
450 if constexpr (HasStateName<T>) {
451 return T::NAME;
452 } else {
453 return "(unnamed)"_kj;
454 }
455}
456 
457} // namespace _
458 
459// =============================================================================
460// Spec Types for Composable Features
461// =============================================================================
462 
463// Marker type to specify terminal states (cannot transition FROM these)
464template <typename... Ts>
465struct TerminalStates {
466 template <typename T>
467 static constexpr bool contains = _::isOneOf<T, Ts...>;
468 
469 template <typename Machine>
470 static bool isTerminal(const Machine& machine) {
471 return (machine.template is<Ts>() || ...);
472 }
473};
474 
475// Marker type to specify the error state (enables isErrored(), tryGetErrorUnsafe(), etc.)
476// Note: Error states are implicitly terminal - you cannot transition out of an error state
477// using normal transitions. Use forceTransitionTo() if you need to reset from an error.
478template <typename T>
479struct ErrorState {
480 using Type = T;
481};
482 
483// Marker type to specify the active state (enables isActive(), whenActive(), etc.)
484template <typename T>
485struct ActiveState {
486 using Type = T;
487};
488 
489// Marker type to specify which states can be pending/deferred
490template <typename... Ts>
491struct PendingStates {
492 template <typename T>
493 static constexpr bool contains = _::isOneOf<T, Ts...>;
494};
495 
496// =============================================================================
497// Spec Detection Traits
498// =============================================================================
499 
500namespace _ { // private
501 
502// Helper to detect template instantiations
503template <typename T, template <typename...> class Template>
504inline constexpr bool isInstanceOf = false;
505 
506template <template <typename...> class Template, typename... Args>
507inline constexpr bool isInstanceOf<Template<Args...>, Template> = true;
508 
509// Spec detection using template matching
510template <typename T>
511inline constexpr bool isTerminalStatesSpec = isInstanceOf<T, TerminalStates>;
512 
513template <typename T>
514inline constexpr bool isErrorStateSpec = isInstanceOf<T, ErrorState>;
515 
516template <typename T>
517inline constexpr bool isActiveStateSpec = isInstanceOf<T, ActiveState>;
518 
519template <typename T>
520inline constexpr bool isPendingStatesSpec = isInstanceOf<T, PendingStates>;
521 
522// Check if a type is any spec type
523template <typename T>
524inline constexpr bool isSpec = isTerminalStatesSpec<T> || isErrorStateSpec<T> ||
525 isActiveStateSpec<T> || isPendingStatesSpec<T>;
526 
527// Filter out specs from a type list, keeping only actual states
528template <typename... Ts>
529struct FilterStates_;
530 
531template <>
532struct FilterStates_<> {
533 using Type = std::tuple<>;
534};
535 
536template <typename First, typename... Rest>
537struct FilterStates_<First, Rest...> {
538 using RestFiltered = FilterStates_<Rest...>::Type;
539 using Type = std::conditional_t<isSpec<First>,
540 RestFiltered,
541 decltype(std::tuple_cat(kj::instance<std::tuple<First>>(), kj::instance<RestFiltered>()))>;
542};
543 
544template <typename... Ts>
545using FilterStates = FilterStates_<Ts...>::Type;
546 
547// Convert tuple to kj::OneOf
548template <typename Tuple>
549struct TupleToOneOf_;
550 
551template <typename... Ts>
552struct TupleToOneOf_<std::tuple<Ts...>> {
553 using Type = kj::OneOf<Ts...>;
554};
555 
556template <typename Tuple>
557using TupleToOneOf = TupleToOneOf_<Tuple>::Type;
558 
559// Generic spec finder - finds the first type matching a predicate
560template <template <typename> class Pred, typename... Ts>
561struct FindSpecWhere {
562 using Type = void; // Not found
563};
564 
565template <template <typename> class Pred, typename First, typename... Rest>
566struct FindSpecWhere<Pred, First, Rest...> {
567 using Type =
568 std::conditional_t<Pred<First>::value, First, typename FindSpecWhere<Pred, Rest...>::Type>;
569};
570 
571// Predicate wrappers for each spec type
572template <typename T>
573struct IsErrorStateSpec {
574 static constexpr bool value = isErrorStateSpec<T>;
575};
576template <typename T>
577struct IsActiveStateSpec {
578 static constexpr bool value = isActiveStateSpec<T>;
579};
580template <typename T>
581struct IsTerminalStatesSpec {
582 static constexpr bool value = isTerminalStatesSpec<T>;
583};
584template <typename T>
585struct IsPendingStatesSpec {
586 static constexpr bool value = isPendingStatesSpec<T>;
587};
588 
589// Convenient aliases for finding each spec type
590template <typename... Ts>
591using FindErrorStateSpec = FindSpecWhere<IsErrorStateSpec, Ts...>;
592template <typename... Ts>
593using FindActiveStateSpec = FindSpecWhere<IsActiveStateSpec, Ts...>;
594template <typename... Ts>
595using FindTerminalStatesSpec = FindSpecWhere<IsTerminalStatesSpec, Ts...>;
596template <typename... Ts>
597using FindPendingStatesSpec = FindSpecWhere<IsPendingStatesSpec, Ts...>;
598 
599// Check if a type is in a tuple (type list)
600template <typename T, typename Tuple>
601inline constexpr bool isInTuple = false;
602 
603template <typename T, typename... Ts>
604inline constexpr bool isInTuple<T, std::tuple<Ts...>> = (kj::isSameType<T, Ts>() || ...);
605 
606// Placeholder type used when a feature is disabled
607// This is needed because C++ doesn't allow references to void
608struct PlaceholderType {};
609 
610// Empty struct for [[no_unique_address]] optimization
611// Unlike char, this can actually be zero-sized when used with [[no_unique_address]]
612struct Empty {};
613 
614// Helper to extract ::Type from a spec, or PlaceholderType if spec is void
615template <typename Spec>
616struct ExtractSpecType_ {
617 using Type = Spec::Type;
618};
619 
620template <>
621struct ExtractSpecType_<void> {
622 using Type = PlaceholderType;
623};
624 
625template <typename Spec>
626using ExtractSpecType = ExtractSpecType_<Spec>::Type;
627 
628// Generic spec counter using fold expression
629template <template <typename> class Pred, typename... Ts>
630inline constexpr size_t countSpecsWhere = ((Pred<Ts>::value ? 1 : 0) + ... + 0);
631 
632// Convenient aliases for counting each spec type
633template <typename... Ts>
634inline constexpr size_t countErrorStateSpecs = countSpecsWhere<IsErrorStateSpec, Ts...>;
635template <typename... Ts>
636inline constexpr size_t countActiveStateSpecs = countSpecsWhere<IsActiveStateSpec, Ts...>;
637template <typename... Ts>
638inline constexpr size_t countTerminalStatesSpecs = countSpecsWhere<IsTerminalStatesSpec, Ts...>;
639template <typename... Ts>
640inline constexpr size_t countPendingStatesSpecs = countSpecsWhere<IsPendingStatesSpec, Ts...>;
641 
642// Validate that all types in a TerminalStates spec are actual state types
643template <typename StatesTuple, typename... TerminalTs>
644struct ValidateTerminalStates {
645 static constexpr bool allValid = (isInTuple<TerminalTs, StatesTuple> && ...);
646 static_assert(allValid || sizeof...(TerminalTs) == 0,
647 "All types in TerminalStates<...> must be actual state types in the state machine");
648};
649 
650// Validate that all types in a PendingStates spec are actual state types
651template <typename StatesTuple, typename... PendingTs>
652struct ValidatePendingStates {
653 static constexpr bool allValid = (isInTuple<PendingTs, StatesTuple> && ...);
654 static_assert(allValid || sizeof...(PendingTs) == 0,
655 "All types in PendingStates<...> must be actual state types in the state machine");
656};
657 
658// Helper to extract types from TerminalStates for validation
659template <typename StatesTuple, typename TerminalSpec>
660struct ValidateTerminalSpec {
661 static constexpr bool valid = true; // Default: no terminal spec, nothing to validate
662};
663 
664template <typename StatesTuple, typename... Ts>
665struct ValidateTerminalSpec<StatesTuple, TerminalStates<Ts...>> {
666 static constexpr bool valid = ValidateTerminalStates<StatesTuple, Ts...>::allValid;
667};
668 
669// Helper to extract types from PendingStates for validation
670template <typename StatesTuple, typename PendingSpec>
671struct ValidatePendingSpec {
672 static constexpr bool valid = true; // Default: no pending spec, nothing to validate
673};
674 
675template <typename StatesTuple, typename... Ts>
676struct ValidatePendingSpec<StatesTuple, PendingStates<Ts...>> {
677 static constexpr bool valid = ValidatePendingStates<StatesTuple, Ts...>::allValid;
678};
679 
680} // namespace _
681 
682// =============================================================================
683// Transition Lock
684// =============================================================================
685 
686// RAII guard that prevents state transitions while in scope.
687// This is used to ensure references to state data remain valid.
688//
689// LIFETIME REQUIREMENTS:
690// The TransitionLock holds a reference to the state machine. The state machine
691// MUST outlive the TransitionLock. Destroying the state machine while a
692// TransitionLock exists will result in undefined behavior (use-after-free).
693//
694// CORRECT USAGE:
695// {
696// auto lock = machine.acquireTransitionLock();
697// // ... use state data safely ...
698// } // lock destroyed, then machine can be safely destroyed
699//
700// INCORRECT USAGE:
701// auto lock = machine.acquireTransitionLock();
702// machine = StateMachine{}; // BUG: lock still holds reference to old machine!
703//
704// TODO(someday): Consider adding tryGet<S>() and get<S>() accessor methods to provide
705// safe state access while locked. This would enable patterns like:
706//
707// auto lock = state.acquireTransitionLock();
708// KJ_IF_SOME(open, lock.tryGet<Open>()) { ... }
709//
710// Could also explore a WD_IF_STATE macro for KJ_IF_SOME-style ergonomics. If we add
711// accessors here, we may want to support deferred transitions (queued until lock
712// release), but this raises design questions about conditional transitions.
713//
714// The relationship between TransitionLock (for safe state access) and OperationScope
715// (for pending operation tracking with deferred transitions) also needs clarification.
716template <typename Machine>
717class TransitionLock {
718 public:
719 explicit TransitionLock(Machine& m): machine(m) {
720 machine.lockTransitions();
721 }
722 
723 ~TransitionLock() {
724 machine.unlockTransitions();
725 }
726 
727 KJ_DISALLOW_COPY_AND_MOVE(TransitionLock);
728 
729 private:
730 Machine& machine;
731};
732 
733// =============================================================================
734// State Name Trait
735// =============================================================================
736 
737// Add NAME to your state types for introspection support:
738//
739// struct Closed {
740// static constexpr kj::StringPtr NAME = "closed"_kj;
741// };
742//
743// struct Errored {
744// jsg::Value error;
745// static constexpr kj::StringPtr NAME = "errored"_kj;
746// };
747 
748// Forward declaration
749template <typename... Args>
750class StateMachine;
751 
752// =============================================================================
753// State Machine
754// =============================================================================
755 
756// A unified state machine that supports all features via spec types.
757// Features are conditionally enabled based on which specs are provided.
758//
759// Usage:
760// // Simple (no specs)
761// StateMachine<Idle, Running, Done> simple;
762//
763// // With terminal states
764// StateMachine<TerminalStates<Done>, Idle, Running, Done> withTerminal;
765//
766// // Full-featured (stream pattern)
767// StateMachine<
768// TerminalStates<Closed, Errored>,
769// ErrorState<Errored>,
770// ActiveState<Readable>,
771// PendingStates<Closed, Errored>,
772// Readable, Closed, Errored
773// > stream;
774//
775// All features from separate classes are available when their spec is provided:
776// - TerminalStates<...> -> isTerminal(), enforces no transitions from terminal
777// - ErrorState<T> -> isErrored(), tryGetErrorUnsafe(), getErrorUnsafe()
778// - ActiveState<T> -> isActive(), isInactive(), whenActive(), tryGetActiveUnsafe()
779// - PendingStates<...> -> beginOperation(), endOperation(), deferTransitionTo(), etc.
780 
781template <typename... Args>
782class StateMachine {
783 public:
784 // Extract specs from Args
785 using TerminalSpec = _::FindTerminalStatesSpec<Args...>::Type;
786 using ErrorSpec = _::FindErrorStateSpec<Args...>::Type;
787 using ActiveSpec = _::FindActiveStateSpec<Args...>::Type;
788 using PendingSpec = _::FindPendingStatesSpec<Args...>::Type;
789 
790 // Filter out specs to get actual states
791 using StatesTuple = _::FilterStates<Args...>;
792 using StateUnion = _::TupleToOneOf<StatesTuple>;
793 static constexpr size_t STATE_COUNT = std::tuple_size_v<StatesTuple>;
794 
795 // Feature detection
796 static constexpr bool HAS_TERMINAL = !std::is_void_v<TerminalSpec>;
797 static constexpr bool HAS_ERROR = !std::is_void_v<ErrorSpec>;
798 static constexpr bool HAS_ACTIVE = !std::is_void_v<ActiveSpec>;
799 static constexpr bool HAS_PENDING = !std::is_void_v<PendingSpec>;
800 
801 // Get the error state type (PlaceholderType if not specified)
802 // Uses helper to avoid accessing ::Type on void
803 using ErrorStateType = _::ExtractSpecType<ErrorSpec>;
804 using ActiveStateType = _::ExtractSpecType<ActiveSpec>;
805 
806 private:
807 // ==========================================================================
808 // Compile-time validation
809 // ==========================================================================
810 
811 // Detect duplicate specs
812 static_assert(_::countTerminalStatesSpecs<Args...> <= 1,
813 "Multiple TerminalStates<...> specs provided. Only one is allowed.");
814 static_assert(_::countErrorStateSpecs<Args...> <= 1,
815 "Multiple ErrorState<...> specs provided. Only one is allowed.");
816 static_assert(_::countActiveStateSpecs<Args...> <= 1,
817 "Multiple ActiveState<...> specs provided. Only one is allowed.");
818 static_assert(_::countPendingStatesSpecs<Args...> <= 1,
819 "Multiple PendingStates<...> specs provided. Only one is allowed.");
820 
821 // Validate that spec types reference actual states
822 static consteval bool validateErrorSpec() {
823 if constexpr (HAS_ERROR) {
824 static_assert(_::isInTuple<ErrorStateType, StatesTuple>,
825 "ErrorState<T> must reference a type that is one of the state machine's states");
826 }
827 return true;
828 }
829 
830 static consteval bool validateActiveSpec() {
831 if constexpr (HAS_ACTIVE) {
832 static_assert(_::isInTuple<ActiveStateType, StatesTuple>,
833 "ActiveState<T> must reference a type that is one of the state machine's states");
834 }
835 return true;
836 }
837 
838 static consteval bool validateTerminalSpec() {
839 if constexpr (HAS_TERMINAL) {
840 static_assert(_::ValidateTerminalSpec<StatesTuple, TerminalSpec>::valid,
841 "All types in TerminalStates<...> must be actual state types");
842 }
843 return true;
844 }
845 
846 static consteval bool validatePendingSpec() {
847 if constexpr (HAS_PENDING) {
848 static_assert(_::ValidatePendingSpec<StatesTuple, PendingSpec>::valid,
849 "All types in PendingStates<...> must be actual state types");
850 }
851 return true;
852 }
853 
854 // Force validation at class instantiation time
855 static_assert(validateErrorSpec(), "ErrorState validation failed");
856 static_assert(validateActiveSpec(), "ActiveState validation failed");
857 static_assert(validateTerminalSpec(), "TerminalStates validation failed");
858 static_assert(validatePendingSpec(), "PendingStates validation failed");
859 
860 public:
861 // ==========================================================================
862 // Constructors and assignment
863 // ==========================================================================
864 
865 // Default constructor is private - use StateMachine::create<State>(...) instead.
866 // This ensures all state machines are properly initialized.
867 
868 // Destructor checks for outstanding locks
869 ~StateMachine() {
870 KJ_DASSERT(transitionLockCount == 0, "StateMachine destroyed while transition locks are held");
871 }
872 
873 // Move operations - both source and destination must not have locks held
874 StateMachine(StateMachine&& other) noexcept: state(kj::mv(other.state)), transitionLockCount(0) {
875 KJ_DASSERT(other.transitionLockCount == 0,
876 "Cannot move from StateMachine while transition locks are held");
877 if constexpr (HAS_PENDING) {
878 operationCount = other.operationCount;
879 pendingState = kj::mv(other.pendingState);
880 other.operationCount = 0;
881 }
882 }
883 
884 StateMachine& operator=(StateMachine&& other) noexcept {
885 KJ_DASSERT(transitionLockCount == 0,
886 "Cannot move-assign to StateMachine while transition locks are held");
887 KJ_DASSERT(other.transitionLockCount == 0,
888 "Cannot move from StateMachine while transition locks are held");
889 state = kj::mv(other.state);
890 if constexpr (HAS_PENDING) {
891 operationCount = other.operationCount;
892 pendingState = kj::mv(other.pendingState);
893 other.operationCount = 0;
894 }
895 return *this;
896 }
897 
898 // State machines are generally not copyable - they're owned by classes
899 // that typically aren't copyable either (e.g., stream controllers).
900 KJ_DISALLOW_COPY(StateMachine);
901 
902 // Factory function for clearer initialization
903 template <typename S, typename... TArgs>
904 static StateMachine create(TArgs&&... args)
905 requires(_::isInTuple<S, StatesTuple>)
906 {
907 StateMachine m;
908 m.state.template init<S>(kj::fwd<TArgs>(args)...);
909 return m;
910 }
911 
912 // ---------------------------------------------------------------------------
913 // Core State Queries (always available)
914 // ---------------------------------------------------------------------------
915 
916 template <typename S>
917 bool is() const
918 requires(_::isInTuple<S, StatesTuple>)
919 {
920 return state.template is<S>();
921 }
922 
923 template <typename... Ss>
924 bool isAnyOf() const
925 requires((_::isInTuple<Ss, StatesTuple>) && ...)
926 {
927 return (is<Ss>() || ...);
928 }
929 
930 // Check if the machine is initialized (not in the null state).
931 // Call transitionTo<>() to initialize the state machine.
932 bool isInitialized() const {
933 return !(state == nullptr);
934 }
935 
936 // Assert that the machine is initialized, with a clear error message.
937 void requireInitialized() const {
938 KJ_REQUIRE(isInitialized(),
939 "State machine used before initialization. Call transitionTo<InitialState>() first.");
940 }
941 
942 // ---------------------------------------------------------------------------
943 // Core State Access (always available)
944 // ---------------------------------------------------------------------------
945 //
946 // NAMING CONVENTION: Methods with "Unsafe" suffix return raw references to
947 // state data without any protection against use-after-free. These references
948 // can dangle if a state transition occurs while the reference is held.
949 //
950 // The "Unsafe" suffix serves as a visual warning at every call site,
951 // encouraging developers to:
952 // 1. Use safe alternatives (whenState(), whenActive()) when possible
953 // 2. Carefully audit code paths that could trigger transitions
954 // 3. Keep the reference's lifetime as short as possible
955 //
956 // Safe alternatives:
957 // - whenState<S>(callback) - Locks transitions during callback
958 // - whenActive(callback) - Locks transitions, only runs if active
959 // - acquireTransitionLock() - RAII lock for manual control
960 
961 template <typename S>
962 S& getUnsafe() KJ_LIFETIMEBOUND
963 requires(_::isInTuple<S, StatesTuple>)
964 {
965 requireInitialized();
966 KJ_REQUIRE(is<S>(), "State machine is not in the expected state");
967 return state.template get<S>();
968 }
969 
970 template <typename S>
971 const S& getUnsafe() const KJ_LIFETIMEBOUND
972 requires(_::isInTuple<S, StatesTuple>)
973 {
974 requireInitialized();
975 KJ_REQUIRE(is<S>(), "State machine is not in the expected state");
976 return state.template get<S>();
977 }
978 
979 template <typename S>
980 kj::Maybe<S&> tryGetUnsafe() KJ_LIFETIMEBOUND
981 requires(_::isInTuple<S, StatesTuple>)
982 {
983 return state.template tryGet<S>();
984 }
985 
986 template <typename S>
987 kj::Maybe<const S&> tryGetUnsafe() const KJ_LIFETIMEBOUND
988 requires(_::isInTuple<S, StatesTuple>)
989 {
990 return state.template tryGet<S>();
991 }
992 
993 // ---------------------------------------------------------------------------
994 // Transition Locking (always available)
995 // ---------------------------------------------------------------------------
996 
997 bool isTransitionLocked() const {
998 return transitionLockCount > 0;
999 }
1000 
1001 void lockTransitions() {
1002 ++transitionLockCount;
1003 }
1004 
1005 void unlockTransitions() {
1006 KJ_DASSERT(transitionLockCount > 0, "Transition lock underflow");
1007 --transitionLockCount;
1008 }
1009 
1010 TransitionLock<StateMachine> acquireTransitionLock() {
1011 return TransitionLock<StateMachine>(*this);
1012 }
1013 
1014 // ---------------------------------------------------------------------------
1015 // Safe State Access with Locking (always available)
1016 // ---------------------------------------------------------------------------
1017 
1018 // Execute a function with the current state, locking transitions.
1019 // This is the SAFEST way to access state data as it prevents
1020 // use-after-free by blocking transitions during the callback.
1021 //
1022 // Returns the function's result wrapped in Maybe (none if not in state).
1023 // For void functions, returns true if executed, false if not in state.
1024 template <typename S, typename Func>
1025 auto whenState(
1026 Func&& func) -> std::conditional_t<std::is_void_v<decltype(func(kj::instance<S&>()))>,
1027 bool,
1028 kj::Maybe<decltype(func(kj::instance<S&>()))>>
1029 requires(_::isInTuple<S, StatesTuple>)
1030 {
1031 if (!is<S>()) {
1032 if constexpr (std::is_void_v<decltype(func(kj::instance<S&>()))>) {
1033 return false;
1034 } else {
1035 return kj::none;
1036 }
1037 }
1038 
1039 auto lock = acquireTransitionLock();
1040 if constexpr (std::is_void_v<decltype(func(kj::instance<S&>()))>) {
1041 func(state.template get<S>());
1042 return true;
1043 } else {
1044 return func(state.template get<S>());
1045 }
1046 }
1047 
1048 // Const version for read-only access
1049 template <typename S, typename Func>
1050 auto whenState(Func&& func) const
1051 -> std::conditional_t<std::is_void_v<decltype(func(kj::instance<const S&>()))>,
1052 bool,
1053 kj::Maybe<decltype(func(kj::instance<const S&>()))>>
1054 requires(_::isInTuple<S, StatesTuple>)
1055 {
1056 if (!is<S>()) {
1057 if constexpr (std::is_void_v<decltype(func(kj::instance<const S&>()))>) {
1058 return false;
1059 } else {
1060 return kj::none;
1061 }
1062 }
1063 
1064 // Note: We still acquire the lock for consistency, even though const
1065 // methods shouldn't transition. This catches bugs where someone
1066 // tries to transition through a captured non-const reference.
1067 ++transitionLockCount;
1068 KJ_DEFER(--transitionLockCount);
1069 if constexpr (std::is_void_v<decltype(func(kj::instance<const S&>()))>) {
1070 func(state.template get<S>());
1071 return true;
1072 } else {
1073 return func(state.template get<S>());
1074 }
1075 }
1076 
1077 // ---------------------------------------------------------------------------
1078 // Visitor Pattern (always available)
1079 // ---------------------------------------------------------------------------
1080 
1081 // Visit the current state with a generic lambda.
1082 // The lambda must be able to accept any state type.
1083 //
1084 // Usage:
1085 // state.visit([](auto& s) {
1086 // // s is a reference to the current state
1087 // });
1088 //
1089 // Or with explicit type handling:
1090 // state.visit([](auto& s) {
1091 // using S = kj::Decay<decltype(s)>;
1092 // if constexpr (kj::isSameType<S, Readable>()) { ... }
1093 // });
1094 template <typename Visitor>
1095 decltype(auto) visit(Visitor&& visitor) {
1096 return visitImpl(kj::fwd<Visitor>(visitor), std::make_index_sequence<STATE_COUNT>{});
1097 }
1098 
1099 template <typename Visitor>
1100 decltype(auto) visit(Visitor&& visitor) const {
1101 return visitConstImpl(kj::fwd<Visitor>(visitor), std::make_index_sequence<STATE_COUNT>{});
1102 }
1103 
1104 // ---------------------------------------------------------------------------
1105 // State Transitions (always available, but terminal-aware if spec provided)
1106 // ---------------------------------------------------------------------------
1107 
1108 template <typename S, typename... TArgs>
1109 S& transitionTo(TArgs&&... args) KJ_LIFETIMEBOUND
1110 requires(_::isInTuple<S, StatesTuple>)
1111 {
1112 requireUnlocked();
1113 if constexpr (HAS_TERMINAL || HAS_ERROR) {
1114 KJ_REQUIRE(!isTerminal(), "Cannot transition from terminal state");
1115 }
1116 if constexpr (HAS_PENDING) {
1117 clearPendingState();
1118 }
1119 return state.template init<S>(kj::fwd<TArgs>(args)...);
1120 }
1121 
1122 // Force transition bypassing terminal state protection.
1123 //
1124 // WARNING: This bypasses terminal state protection! Use sparingly and only
1125 // for legitimate cleanup/reset scenarios. If you find yourself using this
1126 // frequently, reconsider whether your state should actually be terminal.
1127 //
1128 // Legitimate uses:
1129 // - Resetting a state machine for reuse
1130 // - Cleanup during destruction
1131 // - Test fixtures
1132 //
1133 // Suspicious uses (reconsider your design):
1134 // - Regular business logic transitions
1135 // - "Retry" or "restart" operations
1136 template <typename S, typename... TArgs>
1137 S& forceTransitionTo(TArgs&&... args) KJ_LIFETIMEBOUND
1138 requires(_::isInTuple<S, StatesTuple>)
1139 {
1140 requireUnlocked();
1141 if constexpr (HAS_PENDING) {
1142 clearPendingState();
1143 }
1144 return state.template init<S>(kj::fwd<TArgs>(args)...);
1145 }
1146 
1147 // Conditionally transition from one state to another.
1148 // If the current state is From, transitions to To and returns a reference to the new state.
1149 // If the current state is NOT From, does nothing and returns kj::none.
1150 // This is useful for atomic "check and transition" operations.
1151 template <typename From, typename To, typename... TArgs>
1152 KJ_WARN_UNUSED_RESULT kj::Maybe<To&> transitionFromTo(TArgs&&... args) KJ_LIFETIMEBOUND
1153 requires(_::isInTuple<From, StatesTuple>) && (_::isInTuple<To, StatesTuple>)
1154 {
1155 requireUnlocked();
1156 if (!is<From>()) {
1157 return kj::none;
1158 }
1159 if constexpr (HAS_TERMINAL || HAS_ERROR) {
1160 KJ_REQUIRE(!isTerminal(), "Cannot transition from terminal state");
1161 }
1162 if constexpr (HAS_PENDING) {
1163 clearPendingState();
1164 }
1165 return state.template init<To>(kj::fwd<TArgs>(args)...);
1166 }
1167 
1168 // ---------------------------------------------------------------------------
1169 // State Introspection (always available)
1170 // ---------------------------------------------------------------------------
1171 
1172 kj::StringPtr currentStateName() const {
1173 kj::StringPtr result = "(uninitialized)"_kj;
1174 visitStateNames([&result]<typename S>(const S&) { result = _::getStateName<S>(); });
1175 return result;
1176 }
1177 
1178 // ---------------------------------------------------------------------------
1179 // Terminal State Features (enabled when TerminalStates<...> or ErrorState<T> is provided)
1180 // ---------------------------------------------------------------------------
1181 
1182 // Check if currently in a terminal state (no further transitions allowed).
1183 // Note: Error states are implicitly terminal - you cannot transition out of an error state.
1184 bool isTerminal() const
1185 requires(HAS_TERMINAL || HAS_ERROR)
1186 {
1187 bool terminal = false;
1188 if constexpr (HAS_TERMINAL) {
1189 terminal = TerminalSpec::isTerminal(*this);
1190 }
1191 if constexpr (HAS_ERROR) {
1192 terminal = terminal || is<ErrorStateType>();
1193 }
1194 return terminal;
1195 }
1196 
1197 // ---------------------------------------------------------------------------
1198 // Error State Features (enabled when ErrorState<T> is provided)
1199 // ---------------------------------------------------------------------------
1200 
1201 // Check if currently in the error state.
1202 bool isErrored() const
1203 requires(HAS_ERROR)
1204 {
1205 return is<ErrorStateType>();
1206 }
1207 
1208 // Get the error state if currently errored.
1209 //
1210 // WARNING: Returns an UNLOCKED reference - can dangle if the machine transitions.
1211 kj::Maybe<ErrorStateType&> tryGetErrorUnsafe() KJ_LIFETIMEBOUND
1212 requires(HAS_ERROR)
1213 {
1214 return tryGetUnsafe<ErrorStateType>();
1215 }
1216 
1217 kj::Maybe<const ErrorStateType&> tryGetErrorUnsafe() const KJ_LIFETIMEBOUND
1218 requires(HAS_ERROR)
1219 {
1220 return tryGetUnsafe<ErrorStateType>();
1221 }
1222 
1223 // Get the error state, asserting we are errored.
1224 //
1225 // WARNING: Returns an UNLOCKED reference - can dangle if the machine transitions.
1226 ErrorStateType& getErrorUnsafe() KJ_LIFETIMEBOUND
1227 requires(HAS_ERROR)
1228 {
1229 return getUnsafe<ErrorStateType>();
1230 }
1231 
1232 const ErrorStateType& getErrorUnsafe() const KJ_LIFETIMEBOUND
1233 requires(HAS_ERROR)
1234 {
1235 return getUnsafe<ErrorStateType>();
1236 }
1237 
1238 // ---------------------------------------------------------------------------
1239 // Active State Features (enabled when ActiveState<T> is provided)
1240 // ---------------------------------------------------------------------------
1241 
1242 // Check if currently in the active state.
1243 bool isActive() const
1244 requires(HAS_ACTIVE)
1245 {
1246 return is<ActiveStateType>();
1247 }
1248 
1249 // Returns true if not in the active state (i.e., closed, errored, or any non-active state).
1250 // Note: This is different from isTerminal() which checks if transitions are blocked.
1251 bool isInactive() const
1252 requires(HAS_ACTIVE)
1253 {
1254 return !isActive();
1255 }
1256 
1257 // Get the active state if currently active.
1258 //
1259 // WARNING: Returns an UNLOCKED reference - can dangle if the machine transitions.
1260 // Prefer whenActive() for safe access with locked transitions.
1261 kj::Maybe<ActiveStateType&> tryGetActiveUnsafe() KJ_LIFETIMEBOUND
1262 requires(HAS_ACTIVE)
1263 {
1264 return tryGetUnsafe<ActiveStateType>();
1265 }
1266 
1267 kj::Maybe<const ActiveStateType&> tryGetActiveUnsafe() const KJ_LIFETIMEBOUND
1268 requires(HAS_ACTIVE)
1269 {
1270 return tryGetUnsafe<ActiveStateType>();
1271 }
1272 
1273 // Get the active state, throwing KJ_REQUIRE if not active.
1274 //
1275 // WARNING: Returns an UNLOCKED reference - can dangle if the machine transitions.
1276 ActiveStateType& requireActiveUnsafe(kj::StringPtr message = nullptr) KJ_LIFETIMEBOUND
1277 requires(HAS_ACTIVE)
1278 {
1279 if (message == nullptr) {
1280 message = "State machine is not in the active state"_kj;
1281 }
1282 KJ_REQUIRE(isActive(), message);
1283 return state.template get<ActiveStateType>();
1284 }
1285 
1286 const ActiveStateType& requireActiveUnsafe(kj::StringPtr message = nullptr) const KJ_LIFETIMEBOUND
1287 requires(HAS_ACTIVE)
1288 {
1289 if (message == nullptr) {
1290 message = "State machine is not in the active state"_kj;
1291 }
1292 KJ_REQUIRE(isActive(), message);
1293 return state.template get<ActiveStateType>();
1294 }
1295 
1296 // Execute a function only if in the active state.
1297 // LOCKS TRANSITIONS during callback execution to prevent use-after-free.
1298 // Returns the function's result wrapped in Maybe, or none if not active.
1299 // For void functions, returns true if executed, false if not active.
1300 template <typename Func>
1301 auto whenActive(Func&& func)
1302 -> std::conditional_t<std::is_void_v<decltype(func(kj::instance<ActiveStateType&>()))>,
1303 bool,
1304 kj::Maybe<decltype(func(kj::instance<ActiveStateType&>()))>>
1305 requires(HAS_ACTIVE)
1306 {
1307 return whenState<ActiveStateType>(kj::fwd<Func>(func));
1308 }
1309 
1310 template <typename Func>
1311 auto whenActive(Func&& func) const
1312 -> std::conditional_t<std::is_void_v<decltype(func(kj::instance<const ActiveStateType&>()))>,
1313 bool,
1314 kj::Maybe<decltype(func(kj::instance<const ActiveStateType&>()))>>
1315 requires(HAS_ACTIVE)
1316 {
1317 return whenState<ActiveStateType>(kj::fwd<Func>(func));
1318 }
1319 
1320 // Execute a function if active, or return a default value.
1321 // LOCKS TRANSITIONS during callback execution.
1322 template <typename Func, typename Default>
1323 auto whenActiveOr(
1324 Func&& func, Default&& defaultValue) -> decltype(func(kj::instance<ActiveStateType&>()))
1325 requires(HAS_ACTIVE)
1326 {
1327 if (!isActive()) {
1328 return kj::fwd<Default>(defaultValue);
1329 }
1330 auto lock = acquireTransitionLock();
1331 return func(state.template get<ActiveStateType>());
1332 }
1333 
1334 template <typename Func, typename Default>
1335 auto whenActiveOr(Func&& func,
1336 Default&& defaultValue) const -> decltype(func(kj::instance<const ActiveStateType&>()))
1337 requires(HAS_ACTIVE)
1338 {
1339 if (!isActive()) {
1340 return kj::fwd<Default>(defaultValue);
1341 }
1342 ++transitionLockCount;
1343 KJ_DEFER(--transitionLockCount);
1344 return func(state.template get<ActiveStateType>());
1345 }
1346 
1347 // ---------------------------------------------------------------------------
1348 // Pending State Features (enabled when PendingStates<...> is provided)
1349 // ---------------------------------------------------------------------------
1350 //
1351 // RECOMMENDATION: Prefer scopedOperation() RAII guard over manual
1352 // beginOperation()/endOperation() calls. Manual calls are error-prone:
1353 //
1354 // void badExample() {
1355 // machine.beginOperation();
1356 // if (condition) return; // BUG: leaks operation count!
1357 // machine.endOperation();
1358 // }
1359 //
1360 // void goodExample() {
1361 // auto op = machine.scopedOperation();
1362 // if (condition) return; // OK: destructor calls endOperation()
1363 // }
1364 //
1365 // void exampleWithEarlyEnd() {
1366 // auto op = machine.scopedOperation();
1367 // // ... do work ...
1368 // if (op.end()) { // End early and check if transition occurred
1369 // // A pending state was applied
1370 // }
1371 // } // destructor is now a no-op
1372 //
1373 // Manual beginOperation()/endOperation() may still be appropriate when:
1374 // - You need different exception handling (e.g., clearPendingState() before endOperation())
1375 // - You need to conditionally execute callbacks after the pending state is applied
1376 
1377 // Mark that an operation is starting. While operations are in progress,
1378 // certain transitions (via deferTransitionTo) will be deferred rather than
1379 // applied immediately. Prefer scopedOperation() for automatic cleanup.
1380 void beginOperation()
1381 requires(HAS_PENDING)
1382 {
1383 ++operationCount;
1384 }
1385 
1386 // Mark that an operation has completed. If no more operations are pending
1387 // and there's a deferred state transition, it will be applied.
1388 // Returns true if a pending state was applied.
1389 // Prefer scopedOperation() for automatic cleanup.
1390 KJ_WARN_UNUSED_RESULT bool endOperation()
1391 requires(HAS_PENDING)
1392 {
1393 KJ_REQUIRE(operationCount > 0, "endOperation() called without matching beginOperation()");
1394 --operationCount;
1395 
1396 if (operationCount == 0 && hasPendingState()) {
1397 applyPendingStateImpl();
1398 return true;
1399 }
1400 return false;
1401 }
1402 
1403 // Check if any operations are currently in progress.
1404 bool hasOperationInProgress() const
1405 requires(HAS_PENDING)
1406 {
1407 return operationCount > 0;
1408 }
1409 
1410 // Check if there's a pending state transition waiting to be applied.
1411 bool hasPendingState() const
1412 requires(HAS_PENDING)
1413 {
1414 return !(pendingState == nullptr);
1415 }
1416 
1417 // Check if a specific state is pending.
1418 template <typename S>
1419 bool pendingStateIs() const
1420 requires(HAS_PENDING) && (PendingSpec::template contains<S>)
1421 {
1422 return pendingState.template is<S>();
1423 }
1424 
1425 // Get the pending state if it matches the specified type.
1426 //
1427 // WARNING: Returns an UNLOCKED reference - can dangle if the pending state is applied.
1428 template <typename S>
1429 kj::Maybe<S&> tryGetPendingStateUnsafe() KJ_LIFETIMEBOUND
1430 requires(HAS_PENDING) && (PendingSpec::template contains<S>)
1431 {
1432 return pendingState.template tryGet<S>();
1433 }
1434 
1435 template <typename S>
1436 kj::Maybe<const S&> tryGetPendingStateUnsafe() const KJ_LIFETIMEBOUND
1437 requires(HAS_PENDING) && (PendingSpec::template contains<S>)
1438 {
1439 return pendingState.template tryGet<S>();
1440 }
1441 
1442 // Clear any pending state without applying it.
1443 void clearPendingState()
1444 requires(HAS_PENDING)
1445 {
1446 pendingState = StateUnion();
1447 }
1448 
1449 // Transition to a pending state. If no operation is in progress, the
1450 // transition happens immediately. Otherwise, it's deferred until all
1451 // operations complete.
1452 //
1453 // Returns true if the transition happened immediately, false if deferred.
1454 //
1455 // IMPORTANT: First-wins semantics! If a pending state is already set, this
1456 // call is SILENTLY IGNORED. The first deferred transition wins:
1457 //
1458 // machine.beginOperation();
1459 // machine.deferTransitionTo<Closed>(); // This one wins
1460 // machine.deferTransitionTo<Errored>(e); // IGNORED - Closed already pending!
1461 // machine.endOperation(); // Transitions to Closed, not Errored
1462 //
1463 // If you need error to take precedence over close, you must either:
1464 // 1. Use forceTransitionTo<Errored>() which bypasses deferral, or
1465 // 2. Check hasPendingState() before deferring, or
1466 // 3. Use clearPendingState() first to override
1467 template <typename S, typename... TArgs>
1468 KJ_WARN_UNUSED_RESULT bool deferTransitionTo(TArgs&&... args)
1469 requires(HAS_PENDING) && (PendingSpec::template contains<S>)
1470 {
1471 requireUnlocked();
1472 
1473 // Check terminal state if applicable (same as transitionTo)
1474 if constexpr (HAS_TERMINAL || HAS_ERROR) {
1475 KJ_REQUIRE(!isTerminal(), "Cannot transition from terminal state");
1476 }
1477 
1478 if (operationCount == 0) {
1479 // No operation in progress, transition immediately
1480 state.template init<S>(kj::fwd<TArgs>(args)...);
1481 return true;
1482 } else {
1483 // Operation in progress, defer the transition (first wins)
1484 if (pendingState == nullptr) {
1485 pendingState.template init<S>(kj::fwd<TArgs>(args)...);
1486 }
1487 return false;
1488 }
1489 }
1490 
1491 // Check if the machine is in state S OR has S pending.
1492 // Useful for "is closed or closing" type checks.
1493 template <typename S>
1494 bool isOrPending() const
1495 requires(HAS_PENDING) && (_::isInTuple<S, StatesTuple>)
1496 {
1497 if (is<S>()) {
1498 return true;
1499 }
1500 if constexpr (PendingSpec::template contains<S>) {
1501 return pendingState.template is<S>();
1502 }
1503 return false;
1504 }
1505 
1506 // Get the name of the pending state (or "(none)" if no pending state).
1507 kj::StringPtr pendingStateName() const
1508 requires(HAS_PENDING)
1509 {
1510 if (pendingState == nullptr) {
1511 return "(none)"_kj;
1512 }
1513 kj::StringPtr result = "(unknown)"_kj;
1514 visitPendingStates([&result]<typename S>(const S&) { result = _::getStateName<S>(); });
1515 return result;
1516 }
1517 
1518 // RAII guard for operation tracking.
1519 //
1520 // EXCEPTION SAFETY: If endOperation() triggers a pending state transition
1521 // and the state constructor throws, the exception will propagate from the
1522 // destructor. This is generally acceptable since state machine corruption
1523 // is unrecoverable, but be aware when using this in exception-sensitive code.
1524 //
1525 // TODO(maybe): Currently, OperationScope does not check for transition locks at
1526 // construction time - it only throws when endOperation() tries to apply a pending
1527 // state while locks are held. This allows legitimate interleaved patterns like:
1528 // start operation -> acquire lock -> read state -> release lock -> end operation.
1529 // However, if TransitionLock and OperationScope become the only public APIs for
1530 // mutating their respective counts (i.e., beginOperation()/endOperation() and
1531 // lockTransitions()/unlockTransitions() are made private or removed), it might be
1532 // reasonable to throw at construction time, making the error easier to diagnose.
1533 class OperationScope {
1534 public:
1535 explicit OperationScope(StateMachine& m): machine(&m) {
1536 m.beginOperation();
1537 }
1538 
1539 ~OperationScope() noexcept(false) {
1540 // Note: endOperation() may throw if pending state constructor throws.
1541 // We mark this noexcept(false) to be explicit about this.
1542 KJ_IF_SOME(m, machine) {
1543 auto applied KJ_UNUSED = m.endOperation();
1544 }
1545 }
1546 
1547 OperationScope(const OperationScope&) = delete;
1548 OperationScope& operator=(const OperationScope&) = delete;
1549 OperationScope(OperationScope&&) = delete;
1550 OperationScope& operator=(OperationScope&&) = delete;
1551 
1552 // End the operation early, returning whether a pending state was applied.
1553 // After calling end(), the destructor becomes a no-op.
1554 // Similar to kj::Locked<T>::unlock().
1555 KJ_WARN_UNUSED_RESULT bool end() {
1556 KJ_IF_SOME(m, machine) {
1557 machine = kj::none;
1558 return m.endOperation();
1559 }
1560 return false;
1561 }
1562 
1563 private:
1564 kj::Maybe<StateMachine&> machine;
1565 };
1566 
1567 OperationScope scopedOperation()
1568 requires(HAS_PENDING)
1569 {
1570 return OperationScope(*this);
1571 }
1572 
1573 // ---------------------------------------------------------------------------
1574 // GC Visitation Support
1575 // ---------------------------------------------------------------------------
1576 
1577 // Visit the current state for garbage collection.
1578 // The visitor should have a visit() method that accepts references to
1579 // GC-visitable types (like jsg::GcVisitor).
1580 //
1581 // Usage in a class with a state machine member:
1582 // void visitForGc(jsg::GcVisitor& visitor) {
1583 // state.visitForGc(visitor);
1584 // }
1585 //
1586 // The visitor's visit() method will be called with the current state.
1587 // If the state type doesn't support GC visitation, the visit() call
1588 // will be a no-op (assuming the visitor handles non-visitable types).
1589 template <typename Visitor>
1590 void visitForGc(Visitor& visitor) {
1591 visitForGcImpl(visitor, std::make_index_sequence<STATE_COUNT>{});
1592 }
1593 
1594 template <typename Visitor>
1595 void visitForGc(Visitor& visitor) const {
1596 visitForGcImpl(visitor, std::make_index_sequence<STATE_COUNT>{});
1597 }
1598 
1599 // ---------------------------------------------------------------------------
1600 // Interop (use sparingly - bypasses safety features)
1601 // ---------------------------------------------------------------------------
1602 
1603 // Access the underlying kj::OneOf for interop with existing code.
1604 //
1605 // WARNING: Use this sparingly! The returned reference bypasses ALL safety
1606 // features of the state machine:
1607 // - No transition locking (references can dangle)
1608 // - No terminal state enforcement
1609 // - No pending state handling
1610 // - Modifications won't trigger pending state application
1611 //
1612 // This is primarily useful for:
1613 // - Migrating existing code to use StateMachine
1614 // - Implementing new patterns that the state machine doesn't support yet
1615 // - Interfacing with APIs that expect kj::OneOf directly
1616 //
1617 // STRONGLY PREFER: whenState(), transitionTo(), and other type-safe methods.
1618 // TODO(later): Revisit whether these should be kept.
1619 StateUnion& underlying() KJ_LIFETIMEBOUND {
1620 return state;
1621 }
1622 const StateUnion& underlying() const KJ_LIFETIMEBOUND {
1623 return state;
1624 }
1625 
1626 // For use with KJ_SWITCH_ONEOF.
1627 //
1628 // WARNING: KJ_SWITCH_ONEOF does NOT acquire a transition lock! References
1629 // obtained inside KJ_CASE_ONEOF blocks can become dangling if any code
1630 // in that block triggers a state transition:
1631 //
1632 // KJ_SWITCH_ONEOF(machine) {
1633 // KJ_CASE_ONEOF(active, Active) {
1634 // someFunction(); // If this transitions machine...
1635 // active.foo(); // ...this is UAF!
1636 // }
1637 // }
1638 //
1639 // For safe access, use whenState() instead:
1640 //
1641 // machine.whenState<Active>([](Active& active) {
1642 // someFunction(); // If this tries to transition, it throws
1643 // active.foo(); // Safe - transitions are locked
1644 // });
1645 auto _switchSubject() & {
1646 requireInitialized();
1647 return state._switchSubject();
1648 }
1649 auto _switchSubject() const& {
1650 requireInitialized();
1651 return state._switchSubject();
1652 }
1653 auto _switchSubject() && {
1654 requireInitialized();
1655 return kj::mv(state)._switchSubject();
1656 }
1657 
1658 private:
1659 // Private default constructor - use create<State>() factory function instead.
1660 // Making this private ensures state machines are always initialized.
1661 StateMachine() = default;
1662 
1663 StateUnion state;
1664 
1665 // Counter for detecting illegal transitions from within whenState()/whenActiveOr() callbacks.
1666 // Marked mutable because const methods use it for internal bookkeeping while not changing
1667 // the logical state (i.e., which state the machine is in). This class is NOT thread-safe;
1668 // callers are responsible for synchronization if needed. The const qualifier on methods
1669 // indicates "does not transition the state machine", not "thread-safe".
1670 mutable uint32_t transitionLockCount = 0;
1671 
1672 // Pending state support (only allocated when HAS_PENDING is true)
1673 // Using _::Empty instead of char for proper [[no_unique_address]] optimization
1674 WD_NO_UNIQUE_ADDRESS std::conditional_t<HAS_PENDING, StateUnion, _::Empty> pendingState{};
1675 WD_NO_UNIQUE_ADDRESS std::conditional_t<HAS_PENDING, uint32_t, _::Empty> operationCount{};
1676 
1677 void requireUnlocked() const {
1678 KJ_REQUIRE(transitionLockCount == 0,
1679 "Cannot transition state machine while transitions are locked. "
1680 "This usually means you're trying to transition inside a whenState() callback.");
1681 }
1682 
1683 // Helper for currentStateName()
1684 template <typename Visitor>
1685 void visitStateNames(Visitor&& visitor) const {
1686 visitStateNamesImpl(kj::fwd<Visitor>(visitor), std::make_index_sequence<STATE_COUNT>{});
1687 }
1688 
1689 template <typename Visitor, size_t... Is>
1690 void visitStateNamesImpl(Visitor&& visitor, std::index_sequence<Is...>) const {
1691 auto tryVisit = [&]<size_t I>() {
1692 using S = std::tuple_element_t<I, StatesTuple>;
1693 if (state.template is<S>()) {
1694 visitor.template operator()<S>(state.template get<S>());
1695 }
1696 };
1697 (tryVisit.template operator()<Is>(), ...);
1698 }
1699 
1700 // Helper for visit() - non-const version
1701 template <typename Visitor, size_t... Is>
1702 decltype(auto) visitImpl(Visitor&& visitor, std::index_sequence<Is...>) {
1703 KJ_REQUIRE(isInitialized(), "Cannot visit uninitialized state machine");
1704 
1705 // Use common_type to handle visitors that return compatible but different types
1706 using ReturnType = std::common_type_t<decltype(visitor(
1707 kj::instance<std::tuple_element_t<Is, StatesTuple>&>()))...>;
1708 
1709 if constexpr (std::is_void_v<ReturnType>) {
1710 auto tryVisit = [&]<size_t I>() {
1711 using S = std::tuple_element_t<I, StatesTuple>;
1712 if (state.template is<S>()) {
1713 visitor(state.template get<S>());
1714 }
1715 };
1716 (tryVisit.template operator()<Is>(), ...);
1717 } else {
1718 ReturnType result{};
1719 auto tryVisit = [&]<size_t I>() {
1720 using S = std::tuple_element_t<I, StatesTuple>;
1721 if (state.template is<S>()) {
1722 result = visitor(state.template get<S>());
1723 }
1724 };
1725 (tryVisit.template operator()<Is>(), ...);
1726 return result;
1727 }
1728 }
1729 
1730 // Helper for visit() - const version
1731 template <typename Visitor, size_t... Is>
1732 decltype(auto) visitConstImpl(Visitor&& visitor, std::index_sequence<Is...>) const {
1733 KJ_REQUIRE(isInitialized(), "Cannot visit uninitialized state machine");
1734 
1735 using ReturnType = std::common_type_t<decltype(visitor(
1736 kj::instance<const std::tuple_element_t<Is, StatesTuple>&>()))...>;
1737 
1738 if constexpr (std::is_void_v<ReturnType>) {
1739 auto tryVisit = [&]<size_t I>() {
1740 using S = std::tuple_element_t<I, StatesTuple>;
1741 if (state.template is<S>()) {
1742 visitor(state.template get<S>());
1743 }
1744 };
1745 (tryVisit.template operator()<Is>(), ...);
1746 } else {
1747 ReturnType result{};
1748 auto tryVisit = [&]<size_t I>() {
1749 using S = std::tuple_element_t<I, StatesTuple>;
1750 if (state.template is<S>()) {
1751 result = visitor(state.template get<S>());
1752 }
1753 };
1754 (tryVisit.template operator()<Is>(), ...);
1755 return result;
1756 }
1757 }
1758 
1759 void applyPendingStateImpl()
1760 requires(HAS_PENDING)
1761 {
1762 // Applying a pending state is a transition, so we must not be locked.
1763 // This prevents UAF when endOperation() is called inside a whenState() callback:
1764 //
1765 // machine.whenState<Active>([&](Active& a) {
1766 // {
1767 // auto op = machine.scopedOperation();
1768 // machine.deferTransitionTo<Closed>();
1769 // } // op destroyed here - would transition while 'a' is still in use!
1770 // a.doSomething(); // UAF if transition happened above
1771 // });
1772 //
1773 // With this check, the above code will throw instead of causing UAF.
1774 requireUnlocked();
1775 
1776 // Check terminal state if applicable - don't apply pending state if we're
1777 // already in a terminal state (this can happen if a forceTransitionTo was
1778 // used to reach a terminal state while an operation was in progress).
1779 if constexpr (HAS_TERMINAL || HAS_ERROR) {
1780 if (isTerminal()) {
1781 // Already in terminal state, discard the pending state
1782 pendingState = StateUnion();
1783 return;
1784 }
1785 }
1786 
1787 visitPendingStates([this]<typename S>(S& s) { this->state.template init<S>(kj::mv(s)); });
1788 pendingState = StateUnion();
1789 }
1790 
1791 template <typename Visitor>
1792 void visitPendingStates(Visitor&& visitor) const
1793 requires(HAS_PENDING)
1794 {
1795 visitPendingStatesImpl(kj::fwd<Visitor>(visitor), std::make_index_sequence<STATE_COUNT>{});
1796 }
1797 
1798 template <typename Visitor>
1799 void visitPendingStates(Visitor&& visitor)
1800 requires(HAS_PENDING)
1801 {
1802 visitPendingStatesImpl(kj::fwd<Visitor>(visitor), std::make_index_sequence<STATE_COUNT>{});
1803 }
1804 
1805 template <typename Visitor, size_t... Is>
1806 void visitPendingStatesImpl(Visitor&& visitor, std::index_sequence<Is...>) const
1807 requires(HAS_PENDING)
1808 {
1809 auto tryVisit = [&]<size_t I>() {
1810 using S = std::tuple_element_t<I, StatesTuple>;
1811 if (pendingState.template is<S>()) {
1812 visitor.template operator()<S>(pendingState.template get<S>());
1813 }
1814 };
1815 (tryVisit.template operator()<Is>(), ...);
1816 }
1817 
1818 template <typename Visitor, size_t... Is>
1819 void visitPendingStatesImpl(Visitor&& visitor, std::index_sequence<Is...>)
1820 requires(HAS_PENDING)
1821 {
1822 auto tryVisit = [&]<size_t I>() {
1823 using S = std::tuple_element_t<I, StatesTuple>;
1824 if (pendingState.template is<S>()) {
1825 visitor.template operator()<S>(pendingState.template get<S>());
1826 }
1827 };
1828 (tryVisit.template operator()<Is>(), ...);
1829 }
1830 
1831 // TODO(later): If we decide to ever move state-machine.h into kj, then the visitForGc
1832 // details will need to be revisited since those are specific to workerd.
1833 // The reasons we can't support this in the regular visit() public API are:
1834 // * Need to support uninitialized states
1835 // * Need to support visitors which don't implement overloads for all state types
1836 // * Need to support visitors with visit() functions instead of operator()
1837 // Points 1 and 2 could perhaps be encapsulated in a public API named something like weakVisit(),
1838 // and point 3 could be taken care of by saying "Your visitor must have either a visit() or
1839 // operator(), but not both."
1840 // For now, tho, we will just keep this here and we can revisit later.
1841 
1842 // Helper for visitForGc - visits the current state if the visitor can handle it
1843 template <typename Visitor, size_t... Is>
1844 void visitForGcImpl(Visitor& visitor, std::index_sequence<Is...>) {
1845 auto tryVisit = [&]<size_t I>(StateUnion& s) {
1846 using S = std::tuple_element_t<I, StatesTuple>;
1847 if (s.template is<S>()) {
1848 // Only call visit if the visitor can handle this type
1849 if constexpr (requires { visitor.visit(s.template get<S>()); }) {
1850 visitor.visit(s.template get<S>());
1851 }
1852 }
1853 };
1854 (tryVisit.template operator()<Is>(state), ...);
1855 // Also visit pending state if present
1856 if constexpr (HAS_PENDING) {
1857 if (hasPendingState()) {
1858 (tryVisit.template operator()<Is>(pendingState), ...);
1859 }
1860 }
1861 }
1862 
1863 template <typename Visitor, size_t... Is>
1864 void visitForGcImpl(Visitor& visitor, std::index_sequence<Is...>) const {
1865 auto tryVisit = [&]<size_t I>(const StateUnion& s) {
1866 using S = std::tuple_element_t<I, StatesTuple>;
1867 if (s.template is<S>()) {
1868 // Only call visit if the visitor can handle this type
1869 if constexpr (requires { visitor.visit(s.template get<S>()); }) {
1870 visitor.visit(s.template get<S>());
1871 }
1872 }
1873 };
1874 (tryVisit.template operator()<Is>(state), ...);
1875 // Also visit pending state if present
1876 if constexpr (HAS_PENDING) {
1877 if (hasPendingState()) {
1878 (tryVisit.template operator()<Is>(pendingState), ...);
1879 }
1880 }
1881 }
1882};
1883 
1884} // namespace workerd
1885 
1886// =============================================================================
1887// DETAILED USAGE EXAMPLES
1888// =============================================================================
1889//
1890// Example 1: Basic Resource State Machine (Streams Pattern)
1891// ---------------------------------------------------------
1892//
1893// struct Open {
1894// static constexpr kj::StringPtr NAME = "open"_kj;
1895// kj::Own<kj::AsyncInputStream> stream;
1896// };
1897//
1898// struct Closed {
1899// static constexpr kj::StringPtr NAME = "closed"_kj;
1900// };
1901//
1902// // Full-featured stream state machine (actual pattern used in streams code)
1903// using StreamState = StateMachine<
1904// TerminalStates<Closed, kj::Exception>, // Cannot transition out of these
1905// ErrorState<kj::Exception>, // Enables tryGetErrorUnsafe(), isErrored()
1906// ActiveState<Open>, // Enables tryGetActiveUnsafe(), isActive()
1907// Open, Closed, kj::Exception>;
1908//
1909// StreamState state;
1910// state.transitionTo<Open>(kj::mv(stream));
1911//
1912// // Check state
1913// if (state.isActive()) { ... }
1914// if (state.isTerminal()) { ... } // Closed or errored
1915//
1916// // COMMON PATTERN: tryGetActiveUnsafe() with KJ_IF_SOME
1917// // This is the most frequently used pattern in actual streams code.
1918// // It works well with early returns and coroutines.
1919// KJ_IF_SOME(open, state.tryGetActiveUnsafe()) {
1920// // CAUTION: Don't transition state in this scope!
1921// co_return co_await open.stream->read(buffer);
1922// }
1923//
1924// // ALTERNATIVE: whenActive() for safe access (transitions locked)
1925// // Use when the callback might indirectly trigger state transitions.
1926// state.whenActive([](Open& open) {
1927// open.stream->doSomething(); // Safe - transitions blocked
1928// });
1929//
1930// // Error checking
1931// KJ_IF_SOME(exception, state.tryGetErrorUnsafe()) {
1932// kj::throwFatalException(kj::cp(exception));
1933// }
1934//
1935// Example 2: Terminal State Enforcement
1936// -------------------------------------
1937//
1938// StateMachine<
1939// TerminalStates<Closed, kj::Exception>,
1940// Open, Closed, kj::Exception
1941// > state;
1942//
1943// state.transitionTo<Open>(...);
1944//
1945// // This works
1946// state.transitionTo<Closed>();
1947//
1948// // This throws! Cannot leave terminal state
1949// state.transitionTo<Open>(...); // KJ_REQUIRE fails
1950//
1951// // For cleanup/reset, use forceTransitionTo
1952// state.forceTransitionTo<Open>(...); // Bypasses terminal check
1953//
1954// Example 3: Error State Helpers
1955// ------------------------------
1956//
1957// StateMachine<ErrorState<kj::Exception>, Open, Closed, kj::Exception> state;
1958//
1959// // Old pattern (verbose):
1960// KJ_IF_SOME(err, state.tryGetUnsafe<kj::Exception>()) {
1961// kj::throwFatalException(kj::cp(err));
1962// }
1963//
1964// // New pattern (cleaner):
1965// KJ_IF_SOME(err, state.tryGetErrorUnsafe()) {
1966// kj::throwFatalException(kj::cp(err));
1967// }
1968//
1969// // Or check first:
1970// if (state.isErrored()) {
1971// auto& err = state.getErrorUnsafe();
1972// }
1973//
1974// Example 4: State Introspection for Debugging
1975// --------------------------------------------
1976//
1977// struct Active { static constexpr kj::StringPtr NAME = "active"_kj; };
1978// struct Paused { static constexpr kj::StringPtr NAME = "paused"_kj; };
1979// struct Done { static constexpr kj::StringPtr NAME = "done"_kj; };
1980//
1981// StateMachine<Active, Paused, Done> state;
1982// state.transitionTo<Active>();
1983//
1984// // Get current state name for logging/debugging
1985// kj::StringPtr name = state.currentStateName(); // "active"
1986//
1987// // Use in inspectState for JS visibility
1988// jsg::JsString inspectState(jsg::Lock& js) {
1989// return js.strIntern(state.currentStateName());
1990// }
1991//
1992// Example 5: Lock State Machine (no terminal states)
1993// --------------------------------------------------
1994//
1995// struct ReaderLocked {
1996// static constexpr kj::StringPtr NAME = "reader_locked"_kj;
1997// };
1998// struct Unlocked {
1999// static constexpr kj::StringPtr NAME = "unlocked"_kj;
2000// };
2001// struct Locked {
2002// static constexpr kj::StringPtr NAME = "locked"_kj;
2003// };
2004//
2005// // No TerminalStates - locks can always be released
2006// using LockState = StateMachine<Unlocked, Locked, ReaderLocked>;
2007//
2008// LockState lockState;
2009// lockState.transitionTo<Unlocked>();
2010//
2011// // Acquire lock
2012// if (lockState.is<Unlocked>()) {
2013// lockState.transitionTo<ReaderLocked>();
2014// }
2015//
2016// // Release lock - always allowed
2017// lockState.transitionTo<Unlocked>();
2018//
2019// Example 6: Safe State Access with whenState()
2020// ---------------------------------------------
2021//
2022// StateMachine<Active, Paused, Done> state;
2023// state.transitionTo<Active>();
2024//
2025// // SAFE: whenState() locks transitions during callback
2026// auto result = state.whenState<Active>([](Active& a) {
2027// return a.computeResult(); // a is guaranteed valid
2028// }); // Returns kj::Maybe<ResultType>
2029//
2030// // Handle result after callback (transitions now allowed)
2031// KJ_IF_SOME(r, result) {
2032// state.transitionTo<Done>(kj::mv(r));
2033// }
2034//
2035// // whenActiveOr() provides a default for non-active states
2036// size_t count = state.whenActiveOr(
2037// [](Active& a) { return a.itemCount; },
2038// size_t{0}); // Default if not active
2039//
2040// Example 7: Manual Transition Locking
2041// ------------------------------------
2042//
2043// StateMachine<Active, Paused, Done> state;
2044// state.transitionTo<Active>();
2045//
2046// // For complex operations that need multiple state accesses
2047// {
2048// auto lock = state.acquireTransitionLock();
2049//
2050// // All transitions blocked while lock is held
2051// auto& active = state.getUnsafe<Active>();
2052// active.doStep1();
2053// active.doStep2();
2054// active.doStep3();
2055//
2056// } // lock released, transitions now allowed
2057//
2058// state.transitionTo<Done>();
2059//
2060// Example 8: Deferred State Transitions
2061// -------------------------------------
2062//
2063// // For deferring close/error until pending operations complete
2064// StateMachine<
2065// TerminalStates<Closed, Errored>,
2066// PendingStates<Closed, Errored>, // States that can be deferred
2067// Active, Closed, Errored
2068// > state;
2069//
2070// state.transitionTo<Active>();
2071//
2072// // Start an operation
2073// state.beginOperation(); // Or: auto scope = state.scopedOperation();
2074//
2075// // Close is requested, but we're mid-operation - defer it
2076// state.deferTransitionTo<Closed>();
2077//
2078// KJ_EXPECT(state.is<Active>()); // Still active!
2079// KJ_EXPECT(state.hasPendingState()); // Close is pending
2080//
2081// // Complete the operation - pending state is auto-applied
2082// state.endOperation();
2083// KJ_EXPECT(state.is<Closed>()); // Now closed!
2084//
2085// // Common pattern for streams:
2086// void doRead(jsg::Lock& js) {
2087// auto scope = state.scopedOperation(); // RAII operation tracking
2088//
2089// if (state.hasPendingState()) {
2090// // Don't start new work, we're shutting down
2091// return;
2092// }
2093//
2094// // ... do the read ...
2095// } // Operation ends, pending state applied if any
2096//
2097// Example 9: Visitor Pattern
2098// --------------------------
2099//
2100// StateMachine<Active, Paused, Done> state;
2101//
2102// // Generic visitor (does NOT lock transitions)
2103// state.visit([](auto& s) {
2104// using S = kj::Decay<decltype(s)>;
2105// if constexpr (kj::isSameType<S, Active>()) {
2106// // Handle active
2107// } else if constexpr (kj::isSameType<S, Paused>()) {
2108// // Handle paused
2109// } else {
2110// // Handle done
2111// }
2112// });
2113//
2114// =============================================================================
2115// ACTUAL USAGE PATTERNS FROM STREAMS CODE
2116// =============================================================================
2117//
2118// The streams code uses StateMachine extensively. Here are the actual patterns:
2119//
2120// Common state machine declaration:
2121// ---------------------------------
2122// using StreamState = StateMachine<
2123// TerminalStates<Closed, kj::Exception>,
2124// ErrorState<kj::Exception>,
2125// ActiveState<Open>,
2126// Open, Closed, kj::Exception>;
2127//
2128// Most common access pattern (tryGetActiveUnsafe + KJ_IF_SOME):
2129// -------------------------------------------------------------
2130// // This pattern is used 100+ times in streams code because it works
2131// // well with coroutines and early returns.
2132// KJ_IF_SOME(open, state.tryGetActiveUnsafe()) {
2133// co_return co_await open.stream->read(buffer);
2134// }
2135// // Falls through if not in active state
2136//
2137// Error checking pattern:
2138// -----------------------
2139// KJ_IF_SOME(exception, state.tryGetErrorUnsafe()) {
2140// output.abort(kj::cp(exception));
2141// kj::throwFatalException(kj::cp(exception));
2142// }
2143//
2144// Simple state checks:
2145// --------------------
2146// if (state.is<Closed>()) { co_return 0; }
2147// if (state.isActive()) { ... }
2148// if (state.isTerminal()) { ... }
2149//
2150// whenActiveOr for default values:
2151// --------------------------------
2152// return state.whenActiveOr(
2153// [](Queue& q) { return q.getConsumerCount(); },
2154// size_t{0});
2155//