An active execution context scope for a scoped Effect run.
Encapsulates the execution turn's lifecycle status flags and cleanup callback registration, safeguarding against in-flight race conditions and background zombie execution.
System Architectural Flow
Execution Turn State Machine
[Upstream Target Mutation / Turn Initialized]
│
▼
┌──────────────────────────────┐
│ EffectScope Active │ (isActive == true, isCancelled == false)
│ (Registers Cleanups via RAII)│
└──────────────┬───────────────┘
│
├─► [Upstream Mutates Again] ──┐
│ │ (Invokes _dispose())
├─► [Effect Terminal Deactivated]│
│ │
▼ ▼
┌────────────────────────────────────────────────────────┐
│ EffectScope Disposed │ (isActive == false)
│ (Executes Registered Cleanups in LIFO Order) │ (isCancelled == true)
└────────────────────────────────────────────────────────┘
Key Invariants & Architectural Benefits
- RAII Multi-Registration: Call onCleanup multiple times within a single turn.
Cleanups are stored directly on the fast path (single cleanup) without heap list allocation,
and composed recursively with
try-finallychaining when multiple cleanups are added. - Exception Safety during Setup: If acquiring a second resource throws an exception, previously registered cleanups for earlier resources remain intact and will be executed when the scope is disposed.
- Cancellation & Zombie Protection: Asynchronous or long-running tasks can inspect isActive and isCancelled to discard stale results if a newer mutation triggered.
- Immediate Teardown on Stale Registration: If onCleanup is invoked after this scope has already been cancelled, the cleanup executes immediately to prevent resource leaks.
Warning (Anti-Pattern Prevention):
- Do NOT retain EffectScope references across multiple effect execution turns.
- Do NOT omit cleanup registration for active timers, subscriptions, or sockets acquired inside scoped effect callbacks.
BAD:
// [Anti-Pattern] Unregistered timer continues running after effect teardown
Effect([room.bloom], () {
Timer.periodic(const Duration(seconds: 1), (_) => ping()); // Leaks!
});
GOOD:
// [Recommended] Bind resource teardown directly to the execution scope
Effect.scoped([room.bloom], (scope) {
final timer = Timer.periodic(const Duration(seconds: 1), (_) => ping());
scope.onCleanup(timer.cancel);
});
Examples
final room = blooming('lobby');
final roomEffect = Effect.scoped([room.bloom], (scope) {
final timer = Timer.periodic(const Duration(seconds: 5), (_) => sendHeartbeat());
scope.onCleanup(timer.cancel);
});
Properties
- hashCode → int
-
The hash code for this object.
no setterinherited
- isActive → bool
-
Whether this execution turn is currently active and valid (neither cancelled nor deactivated).
no setter
- isCancelled → bool
-
Whether this execution turn has been cancelled by a subsequent invalidation,
node deactivation, or lifecycle teardown.
no setter
- runtimeType → Type
-
A representation of the runtime type of the object.
no setterinherited
Methods
-
noSuchMethod(
Invocation invocation) → dynamic -
Invoked when a nonexistent method or property is accessed.
inherited
-
onCleanup(
void cleanup()) → void - Registers a synchronous cleanup callback to be executed upon turn cancellation or deactivation.
-
toString(
) → String -
A string representation of this object.
inherited
Operators
-
operator ==(
Object other) → bool -
The equality operator.
inherited