Skip to content

Commit 541cb19

Browse files
committed
WIP Assignments
1 parent c52813d commit 541cb19

1 file changed

Lines changed: 125 additions & 16 deletions

File tree

src/utils/localstack-status.ts

Lines changed: 125 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,94 @@ import type {
77
import { createEmitter } from "./emitter.ts";
88
import type { TimeTracker } from "./time-tracker.ts";
99

10+
type HealthStatus = "healthy" | "not_healthy";
11+
12+
const FAST_HEALTH_CHECK_INTERVAL = 1_000;
13+
const SLOW_HEALTH_CHECK_INTERVAL = 3_000;
14+
15+
interface HealthTracker extends Disposable {
16+
status(): HealthStatus | undefined;
17+
start(interval: "fast" | "slow"): Promise<void>;
18+
stop(): void;
19+
onChange(callback: (status: HealthStatus) => void): void;
20+
}
21+
22+
const createHealthTracker = (
23+
outputChannel: LogOutputChannel,
24+
): HealthTracker => {
25+
const emitter = createEmitter<HealthStatus>(outputChannel);
26+
27+
let interval: "fast" | "slow" = "fast";
28+
let status: HealthStatus | undefined;
29+
let timeout: NodeJS.Timeout | undefined;
30+
31+
const startHealthCheck = async () => {
32+
if (timeout) {
33+
return;
34+
}
35+
36+
outputChannel.debug(`[health]: Checking health...`);
37+
const newStatus = await fetchHealth();
38+
outputChannel.debug(`[health]: Health = ${status}`);
39+
if (status !== newStatus) {
40+
status = newStatus;
41+
outputChannel.debug(`[health]: EMITTING Health = ${status}`);
42+
void emitter.emit(status);
43+
}
44+
45+
outputChannel.debug(
46+
`[health]: Next check in ${
47+
interval === "fast"
48+
? FAST_HEALTH_CHECK_INTERVAL
49+
: SLOW_HEALTH_CHECK_INTERVAL
50+
} interval.`,
51+
);
52+
53+
timeout = setTimeout(
54+
() => {
55+
timeout = undefined;
56+
void startHealthCheck();
57+
},
58+
interval === "fast"
59+
? FAST_HEALTH_CHECK_INTERVAL
60+
: SLOW_HEALTH_CHECK_INTERVAL,
61+
);
62+
};
63+
64+
const stop = () => {
65+
if (timeout === undefined) {
66+
return;
67+
}
68+
69+
outputChannel.debug(`[health]: Stopping health checks.`);
70+
clearTimeout(timeout);
71+
timeout = undefined;
72+
};
73+
74+
return {
75+
status() {
76+
return status;
77+
},
78+
async start(newInterval: "fast" | "slow") {
79+
outputChannel.debug(
80+
`[health]: Starting health checks at interval "${newInterval}"... `,
81+
);
82+
interval = newInterval;
83+
await startHealthCheck();
84+
},
85+
stop,
86+
onChange(callback) {
87+
emitter.on(callback);
88+
if (status !== undefined) {
89+
callback(status);
90+
}
91+
},
92+
dispose() {
93+
clearTimeout(timeout);
94+
},
95+
};
96+
};
97+
1098
export type LocalStackStatus = "starting" | "running" | "stopping" | "stopped";
1199

12100
export interface LocalStackStatusTracker extends Disposable {
@@ -27,7 +115,7 @@ export async function createLocalStackStatusTracker(
27115
let status: LocalStackStatus | undefined;
28116
const emitter = createEmitter<LocalStackStatus>(outputChannel);
29117

30-
let healthCheck: boolean | undefined;
118+
const healthTracker = createHealthTracker(outputChannel);
31119

32120
const setStatus = (newStatus: LocalStackStatus) => {
33121
if (status !== newStatus) {
@@ -37,7 +125,13 @@ export async function createLocalStackStatusTracker(
37125
};
38126

39127
const deriveStatus = () => {
40-
const newStatus = getLocalStackStatus(containerStatus, healthCheck);
128+
outputChannel.debug(
129+
`Container Status = ${containerStatus}, Health Status = ${healthTracker.status()}`,
130+
);
131+
const newStatus = getLocalStackStatus(
132+
containerStatus,
133+
healthTracker.status(),
134+
);
41135
setStatus(newStatus);
42136
};
43137

@@ -48,17 +142,32 @@ export async function createLocalStackStatusTracker(
48142
}
49143
});
50144

51-
let healthCheckTimeout: NodeJS.Timeout | undefined;
52-
const startHealthCheck = async () => {
53-
healthCheck = await fetchHealth();
145+
healthTracker.onChange(() => {
54146
deriveStatus();
55-
healthCheckTimeout = setTimeout(() => void startHealthCheck(), 1_000);
56-
};
147+
});
57148

58-
await timeTracker.run("localstack-status.healthCheck", async () => {
59-
await startHealthCheck();
149+
containerStatusTracker.onChange((newContainerStatus) => {
150+
switch (newContainerStatus) {
151+
case "running":
152+
void healthTracker.start("fast");
153+
break;
154+
case "stopping":
155+
case "stopped":
156+
healthTracker.stop();
157+
break;
158+
}
60159
});
61160

161+
emitter.on((localStackStatus) => {
162+
if (localStackStatus === "running") {
163+
void healthTracker.start("slow");
164+
}
165+
});
166+
167+
// await timeTracker.run("localstack-status.healthCheck", async () => {
168+
// await healthTracker.start("slow");
169+
// });
170+
62171
return {
63172
status() {
64173
// biome-ignore lint/style/noNonNullAssertion: false positive
@@ -77,17 +186,17 @@ export async function createLocalStackStatusTracker(
77186
}
78187
},
79188
dispose() {
80-
clearTimeout(healthCheckTimeout);
189+
healthTracker.dispose();
81190
},
82191
};
83192
}
84193

85194
function getLocalStackStatus(
86195
containerStatus: ContainerStatus | undefined,
87-
healthCheck: boolean | undefined,
196+
healthStatus: HealthStatus | undefined,
88197
): LocalStackStatus {
89198
if (containerStatus === "running") {
90-
if (healthCheck === true) {
199+
if (healthStatus === "healthy") {
91200
return "running";
92201
} else {
93202
return "starting";
@@ -99,7 +208,7 @@ function getLocalStackStatus(
99208
}
100209
}
101210

102-
async function fetchHealth(): Promise<boolean> {
211+
async function fetchHealth(): Promise<"healthy" | "not_healthy"> {
103212
// Abort the fetch if it takes more than 500ms.
104213
const controller = new AbortController();
105214
setTimeout(() => controller.abort(), 500);
@@ -111,8 +220,8 @@ async function fetchHealth(): Promise<boolean> {
111220
const response = await fetch("http://localhost:4566/_localstack/health", {
112221
signal: controller.signal,
113222
});
114-
return response.ok;
115-
} catch (err) {
116-
return false;
223+
return response.ok ? "healthy" : "not_healthy";
224+
} catch {
225+
return "not_healthy";
117226
}
118227
}

0 commit comments

Comments
 (0)