@@ -7,6 +7,94 @@ import type {
77import { createEmitter } from "./emitter.ts" ;
88import 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+
1098export type LocalStackStatus = "starting" | "running" | "stopping" | "stopped" ;
1199
12100export 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
85194function 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