@@ -5,6 +5,7 @@ import type {
55 ContainerStatusTracker ,
66} from "./container-status.ts" ;
77import { createEmitter } from "./emitter.ts" ;
8+ import { fetchHealth } from "./manage.ts" ;
89import type { TimeTracker } from "./time-tracker.ts" ;
910
1011export type LocalStackStatus = "starting" | "running" | "stopping" | "stopped" ;
@@ -18,16 +19,19 @@ export interface LocalStackStatusTracker extends Disposable {
1819/**
1920 * Checks the status of the LocalStack instance in realtime.
2021 */
21- export async function createLocalStackStatusTracker (
22+ export function createLocalStackStatusTracker (
2223 containerStatusTracker : ContainerStatusTracker ,
2324 outputChannel : LogOutputChannel ,
2425 timeTracker : TimeTracker ,
25- ) : Promise < LocalStackStatusTracker > {
26+ ) : LocalStackStatusTracker {
2627 let containerStatus : ContainerStatus | undefined ;
2728 let status : LocalStackStatus | undefined ;
2829 const emitter = createEmitter < LocalStackStatus > ( outputChannel ) ;
2930
30- let healthCheck : boolean | undefined ;
31+ const healthCheckStatusTracker = createHealthStatusTracker (
32+ outputChannel ,
33+ timeTracker ,
34+ ) ;
3135
3236 const setStatus = ( newStatus : LocalStackStatus ) => {
3337 if ( status !== newStatus ) {
@@ -37,7 +41,11 @@ export async function createLocalStackStatusTracker(
3741 } ;
3842
3943 const deriveStatus = ( ) => {
40- const newStatus = getLocalStackStatus ( containerStatus , healthCheck , status ) ;
44+ const newStatus = getLocalStackStatus (
45+ containerStatus ,
46+ healthCheckStatusTracker . status ( ) ,
47+ status ,
48+ ) ;
4149 setStatus ( newStatus ) ;
4250 } ;
4351
@@ -48,15 +56,26 @@ export async function createLocalStackStatusTracker(
4856 }
4957 } ) ;
5058
51- let healthCheckTimeout : NodeJS . Timeout | undefined ;
52- const startHealthCheck = async ( ) => {
53- healthCheck = await fetchHealth ( ) ;
54- deriveStatus ( ) ;
55- healthCheckTimeout = setTimeout ( ( ) => void startHealthCheck ( ) , 1_000 ) ;
56- } ;
59+ emitter . on ( ( newStatus ) => {
60+ outputChannel . trace ( `[localstack-status] localstack=${ newStatus } ` ) ;
5761
58- await timeTracker . run ( "localstack-status.healthCheck" , async ( ) => {
59- await startHealthCheck ( ) ;
62+ if ( newStatus === "running" ) {
63+ healthCheckStatusTracker . stop ( ) ;
64+ }
65+ } ) ;
66+
67+ containerStatusTracker . onChange ( ( newContainerStatus ) => {
68+ outputChannel . trace (
69+ `[localstack-status] container=${ newContainerStatus } (localstack=${ status } )` ,
70+ ) ;
71+
72+ if ( newContainerStatus === "running" && status !== "running" ) {
73+ healthCheckStatusTracker . start ( ) ;
74+ }
75+ } ) ;
76+
77+ healthCheckStatusTracker . onChange ( ( ) => {
78+ deriveStatus ( ) ;
6079 } ) ;
6180
6281 return {
@@ -77,18 +96,18 @@ export async function createLocalStackStatusTracker(
7796 }
7897 } ,
7998 dispose ( ) {
80- clearTimeout ( healthCheckTimeout ) ;
99+ healthCheckStatusTracker . dispose ( ) ;
81100 } ,
82101 } ;
83102}
84103
85104function getLocalStackStatus (
86105 containerStatus : ContainerStatus | undefined ,
87- healthCheck : boolean | undefined ,
106+ healthStatus : HealthStatus | undefined ,
88107 previousStatus ?: LocalStackStatus ,
89108) : LocalStackStatus {
90109 if ( containerStatus === "running" ) {
91- if ( healthCheck === true ) {
110+ if ( healthStatus === "healthy" ) {
92111 return "running" ;
93112 } else {
94113 // When the LS container is running, and the health check fails:
@@ -106,20 +125,79 @@ function getLocalStackStatus(
106125 }
107126}
108127
109- async function fetchHealth ( ) : Promise < boolean > {
110- // Abort the fetch if it takes more than 500ms.
111- const controller = new AbortController ( ) ;
112- setTimeout ( ( ) => controller . abort ( ) , 500 ) ;
113-
114- try {
115- // health is ok in the majority of use cases, however, determining status based on it can be flaky.
116- // for example, if localstack becomes unhealthy while running for reasons other that stop then reporting "stopping" may be misleading.
117- // though we don't know if it happens often.
118- const response = await fetch ( "http://localhost:4566/_localstack/health" , {
119- signal : controller . signal ,
128+ type HealthStatus = "healthy" | "unhealthy" ;
129+
130+ interface HealthStatusTracker extends Disposable {
131+ status ( ) : HealthStatus | undefined ;
132+ start ( ) : void ;
133+ stop ( ) : void ;
134+ onChange ( callback : ( status : HealthStatus | undefined ) => void ) : void ;
135+ }
136+
137+ function createHealthStatusTracker (
138+ outputChannel : LogOutputChannel ,
139+ timeTracker : TimeTracker ,
140+ ) : HealthStatusTracker {
141+ let status : HealthStatus | undefined ;
142+ const emitter = createEmitter < HealthStatus | undefined > ( outputChannel ) ;
143+
144+ let healthCheckTimeout : NodeJS . Timeout | undefined ;
145+
146+ const updateStatus = ( newStatus : HealthStatus | undefined ) => {
147+ if ( status !== newStatus ) {
148+ status = newStatus ;
149+ void emitter . emit ( status ) ;
150+ }
151+ } ;
152+
153+ const fetchAndUpdateStatus = async ( ) => {
154+ await timeTracker . run ( "localstack-status.health" , async ( ) => {
155+ const newStatus = ( await fetchHealth ( ) ) ? "healthy" : "unhealthy" ;
156+ updateStatus ( newStatus ) ;
120157 } ) ;
121- return response . ok ;
122- } catch ( err ) {
123- return false ;
124- }
158+ } ;
159+
160+ let enqueueAgain = false ;
161+
162+ const enqueueUpdateStatus = ( ) => {
163+ if ( healthCheckTimeout ) {
164+ return ;
165+ }
166+
167+ healthCheckTimeout = setTimeout ( ( ) => {
168+ void fetchAndUpdateStatus ( ) . then ( ( ) => {
169+ if ( ! enqueueAgain ) {
170+ return ;
171+ }
172+
173+ healthCheckTimeout = undefined ;
174+ enqueueUpdateStatus ( ) ;
175+ } ) ;
176+ } , 1_000 ) ;
177+ } ;
178+
179+ return {
180+ status ( ) {
181+ return status ;
182+ } ,
183+ start ( ) {
184+ enqueueAgain = true ;
185+ enqueueUpdateStatus ( ) ;
186+ } ,
187+ stop ( ) {
188+ status = undefined ;
189+ enqueueAgain = false ;
190+ clearTimeout ( healthCheckTimeout ) ;
191+ healthCheckTimeout = undefined ;
192+ } ,
193+ onChange ( callback ) {
194+ emitter . on ( callback ) ;
195+ if ( status ) {
196+ callback ( status ) ;
197+ }
198+ } ,
199+ dispose ( ) {
200+ clearTimeout ( healthCheckTimeout ) ;
201+ } ,
202+ } ;
125203}
0 commit comments