Skip to content

Commit 021a848

Browse files
cjpillsburyclaude
andcommitted
refactor(spf): drive failover-monitor cooldowns via reactor effects
Replace the hand-rolled `effect()` + `stop()` inside the resolved state's `entry` with the reactor's own `effects:` block, and move the timers map up to the `setup` closure so the scheduler (`effects:`) and the exit cleanup (`entry`'s returned teardown) can share it. The exit still clears pending timers and resets `failedCdns` for the next source. Behavior-preserving; now structurally identical to its sibling `deriveCdnPriority` (entry-cleanup + effects:). Drops the unused `effect` import. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 779086f commit 021a848

1 file changed

Lines changed: 18 additions & 20 deletions

File tree

packages/spf/src/playback/behaviors/setup-failover-monitor.ts

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
import { defineBehavior } from '../../core/composition/create-composition';
1919
import { createMachineReactor } from '../../core/reactors/create-machine-reactor';
20-
import { effect } from '../../core/signals/effect';
2120
import { computed, type ReadonlySignal, type Signal, update } from '../../core/signals/primitives';
2221
import { isResolvedPresentation, type MaybeResolvedPresentation } from '../../media/types';
2322

@@ -65,6 +64,10 @@ export const setupFailoverMonitor = defineBehavior({
6564
config?: SetupFailoverMonitorConfig;
6665
}) => {
6766
const cooldownMs = config.failover?.cooldownMs ?? DEFAULT_FAILOVER_MONITOR_CONFIG.cooldownMs;
67+
// CDN id → its pending cooldown-removal timer. Shared by the `effects`
68+
// scheduler (adds a timer per newly-failed CDN) and the exit cleanup
69+
// (clears them). Per-source: emptied on exit, so it re-enters clean.
70+
const timers = new Map<string, ReturnType<typeof setTimeout>>();
6871
const derivedStateSignal = computed(() =>
6972
isResolvedPresentation(state.presentation.get())
7073
? ('presentation-resolved' as const)
@@ -77,33 +80,28 @@ export const setupFailoverMonitor = defineBehavior({
7780
states: {
7881
'presentation-unresolved': {},
7982
'presentation-resolved': {
80-
entry: () => {
81-
// CDN id → its pending cooldown-removal timer. Fetch sites add a
82-
// failed CDN to `failedCdns`; we schedule its removal once the
83-
// cooldown lapses. Per-source: all timers cleared on exit.
84-
const timers = new Map<string, ReturnType<typeof setTimeout>>();
85-
86-
const stop = effect(() => {
83+
// Cleanup-binds-to-setup: on exit (src unload + destroy) clear the
84+
// pending timers and reset `failedCdns` for the next source.
85+
entry: () => () => {
86+
timers.forEach((timer) => clearTimeout(timer));
87+
timers.clear();
88+
state.failedCdns.set(undefined);
89+
},
90+
effects: [
91+
() => {
8792
const failed = state.failedCdns.get() ?? [];
88-
for (const cdn of failed) {
93+
failed.forEach((cdn) => {
8994
// Idempotent: a CDN already counting down keeps its original
9095
// deadline (re-failing it mid-cooldown doesn't extend it).
91-
if (timers.has(cdn)) continue;
96+
if (timers.has(cdn)) return;
9297
const timer = setTimeout(() => {
9398
timers.delete(cdn);
9499
update(state.failedCdns, (current) => current?.filter((c) => c !== cdn));
95100
}, cooldownMs);
96101
timers.set(cdn, timer);
97-
}
98-
});
99-
100-
return () => {
101-
stop();
102-
for (const timer of timers.values()) clearTimeout(timer);
103-
timers.clear();
104-
state.failedCdns.set(undefined);
105-
};
106-
},
102+
});
103+
},
104+
],
107105
},
108106
},
109107
});

0 commit comments

Comments
 (0)