-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathworkflow.ts
More file actions
147 lines (123 loc) · 4.15 KB
/
workflow.ts
File metadata and controls
147 lines (123 loc) · 4.15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import type { Context } from "@resonatehq/sdk";
// ---------------------------------------------------------------------------
// Infinite Health Monitor — runs forever, checking services
// ---------------------------------------------------------------------------
//
// A monitoring workflow that loops indefinitely: check health → alert if
// unhealthy → sleep → repeat. Designed to run for days, weeks, or months.
//
// Temporal's approach: requires `continueAsNew()` to periodically reset
// the event history (limit: 51,200 events). Without it, the workflow
// eventually crashes with "history too large." The developer must manually
// extract state and pass it forward via function arguments.
//
// Resonate's approach: just loop. Each yield* is an independent checkpoint.
// No history accumulates. No continueAsNew needed. No state serialization.
export interface MonitorConfig {
services: string[];
intervalMs: number; // sleep between checks
maxIterations: number; // for demo purposes; production = Infinity
}
export interface MonitorResult {
iterations: number;
alerts: Alert[];
uptime: number;
}
export interface Alert {
service: string;
message: string;
iteration: number;
timestamp: string;
}
export function* healthMonitor(
ctx: Context,
config: MonitorConfig,
shouldCrash: boolean,
): Generator<any, MonitorResult, any> {
const alerts: Alert[] = [];
const startTime = Date.now();
let iteration = 0;
// Loop forever (capped at maxIterations for the demo).
// In production, set maxIterations = Infinity.
// No continueAsNew required — Resonate has no history limit.
while (iteration < config.maxIterations) {
iteration++;
// Check all services in this iteration
const crashThisIteration = shouldCrash && iteration === 3;
const result = yield* ctx.run(
checkAllServices,
config.services,
iteration,
crashThisIteration,
);
// Collect any alerts
for (const alert of result.alerts) {
alerts.push(alert);
}
// Sleep between checks (durable — survives crashes)
if (iteration < config.maxIterations) {
yield* ctx.sleep(config.intervalMs);
}
}
return {
iterations: iteration,
alerts,
uptime: Date.now() - startTime,
};
}
// ---------------------------------------------------------------------------
// Health check step — checks all services in one iteration
// ---------------------------------------------------------------------------
interface CheckResult {
iteration: number;
healthy: string[];
unhealthy: string[];
alerts: Alert[];
}
const attemptMap = new Map<string, number>();
async function checkAllServices(
_ctx: Context,
services: string[],
iteration: number,
shouldCrash: boolean,
): Promise<CheckResult> {
const key = `iteration-${iteration}`;
const attempt = (attemptMap.get(key) ?? 0) + 1;
attemptMap.set(key, attempt);
// Simulate checking each service
await sleep(50);
if (shouldCrash && attempt === 1) {
console.log(` [iter ${pad(iteration)}] Checking services... MONITORING AGENT CRASH`);
throw new Error("Monitoring agent lost connection to metrics endpoint");
}
const healthy: string[] = [];
const unhealthy: string[] = [];
const alerts: Alert[] = [];
for (const service of services) {
// Simulate: database is unhealthy on iteration 5
const isHealthy = !(service === "database" && iteration === 5);
if (isHealthy) {
healthy.push(service);
} else {
unhealthy.push(service);
alerts.push({
service,
message: `${service} health check failed — connection timeout`,
iteration,
timestamp: new Date().toISOString(),
});
}
}
const retryTag = attempt > 1 ? ` (retry ${attempt})` : "";
const status = unhealthy.length > 0
? `ALERT: ${unhealthy.join(", ")} unhealthy`
: `all ${healthy.length} services healthy`;
console.log(` [iter ${pad(iteration)}] ${status}${retryTag}`);
return { iteration, healthy, unhealthy, alerts };
}
function pad(n: number): string {
return String(n).padStart(2, "0");
}
function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}