11import type { Disposable , LogOutputChannel } from "vscode" ;
2+ import { de } from "zod/v4/locales" ;
23
34import type {
45 ContainerStatus ,
56 ContainerStatusTracker ,
67} from "./container-status.ts" ;
78import { createEmitter } from "./emitter.ts" ;
9+ import { fetchHealth } from "./manage.ts" ;
810import type { TimeTracker } from "./time-tracker.ts" ;
911
1012export type LocalStackStatus = "starting" | "running" | "stopping" | "stopped" ;
@@ -18,16 +20,19 @@ export interface LocalStackStatusTracker extends Disposable {
1820/**
1921 * Checks the status of the LocalStack instance in realtime.
2022 */
21- export async function createLocalStackStatusTracker (
23+ export function createLocalStackStatusTracker (
2224 containerStatusTracker : ContainerStatusTracker ,
2325 outputChannel : LogOutputChannel ,
2426 timeTracker : TimeTracker ,
25- ) : Promise < LocalStackStatusTracker > {
27+ ) : LocalStackStatusTracker {
2628 let containerStatus : ContainerStatus | undefined ;
2729 let status : LocalStackStatus | undefined ;
2830 const emitter = createEmitter < LocalStackStatus > ( outputChannel ) ;
2931
30- let healthCheck : boolean | undefined ;
32+ const healthCheckStatusTracker = createHealthStatusTracker (
33+ outputChannel ,
34+ timeTracker ,
35+ ) ;
3136
3237 const setStatus = ( newStatus : LocalStackStatus ) => {
3338 if ( status !== newStatus ) {
@@ -37,7 +42,11 @@ export async function createLocalStackStatusTracker(
3742 } ;
3843
3944 const deriveStatus = ( ) => {
40- const newStatus = getLocalStackStatus ( containerStatus , healthCheck , status ) ;
45+ const newStatus = getLocalStackStatus (
46+ containerStatus ,
47+ healthCheckStatusTracker . status ( ) ,
48+ status ,
49+ ) ;
4150 setStatus ( newStatus ) ;
4251 } ;
4352
@@ -48,15 +57,26 @@ export async function createLocalStackStatusTracker(
4857 }
4958 } ) ;
5059
51- let healthCheckTimeout : NodeJS . Timeout | undefined ;
52- const startHealthCheck = async ( ) => {
53- healthCheck = await fetchHealth ( ) ;
54- deriveStatus ( ) ;
55- healthCheckTimeout = setTimeout ( ( ) => void startHealthCheck ( ) , 1_000 ) ;
56- } ;
60+ emitter . on ( ( newStatus ) => {
61+ outputChannel . info ( `localstack=${ newStatus } ` ) ;
5762
58- await timeTracker . run ( "localstack-status.healthCheck" , async ( ) => {
59- await startHealthCheck ( ) ;
63+ if ( newStatus === "running" ) {
64+ healthCheckStatusTracker . stop ( ) ;
65+ }
66+ } ) ;
67+
68+ containerStatusTracker . onChange ( ( newContainerStatus ) => {
69+ outputChannel . info (
70+ `container=${ newContainerStatus } (localstack=${ status } )` ,
71+ ) ;
72+
73+ if ( newContainerStatus === "running" && status !== "running" ) {
74+ healthCheckStatusTracker . start ( ) ;
75+ }
76+ } ) ;
77+
78+ healthCheckStatusTracker . onChange ( ( ) => {
79+ deriveStatus ( ) ;
6080 } ) ;
6181
6282 return {
@@ -77,18 +97,18 @@ export async function createLocalStackStatusTracker(
7797 }
7898 } ,
7999 dispose ( ) {
80- clearTimeout ( healthCheckTimeout ) ;
100+ healthCheckStatusTracker . dispose ( ) ;
81101 } ,
82102 } ;
83103}
84104
85105function getLocalStackStatus (
86106 containerStatus : ContainerStatus | undefined ,
87- healthCheck : boolean | undefined ,
107+ healthStatus : HealthStatus | undefined ,
88108 previousStatus ?: LocalStackStatus ,
89109) : LocalStackStatus {
90110 if ( containerStatus === "running" ) {
91- if ( healthCheck === true ) {
111+ if ( healthStatus === "healthy" ) {
92112 return "running" ;
93113 } else {
94114 // When the LS container is running, and the health check fails:
@@ -106,20 +126,79 @@ function getLocalStackStatus(
106126 }
107127}
108128
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 ,
129+ type HealthStatus = "healthy" | "unhealthy" ;
130+
131+ interface HealthStatusTracker extends Disposable {
132+ status ( ) : HealthStatus | undefined ;
133+ start ( ) : void ;
134+ stop ( ) : void ;
135+ onChange ( callback : ( status : HealthStatus | undefined ) => void ) : void ;
136+ }
137+
138+ function createHealthStatusTracker (
139+ outputChannel : LogOutputChannel ,
140+ timeTracker : TimeTracker ,
141+ ) : HealthStatusTracker {
142+ let status : HealthStatus | undefined ;
143+ const emitter = createEmitter < HealthStatus | undefined > ( outputChannel ) ;
144+
145+ let healthCheckTimeout : NodeJS . Timeout | undefined ;
146+
147+ const updateStatus = ( newStatus : HealthStatus | undefined ) => {
148+ if ( status !== newStatus ) {
149+ status = newStatus ;
150+ void emitter . emit ( status ) ;
151+ }
152+ } ;
153+
154+ const fetchAndUpdateStatus = async ( ) => {
155+ await timeTracker . run ( "localstack-status.health" , async ( ) => {
156+ const newStatus = ( await fetchHealth ( ) ) ? "healthy" : "unhealthy" ;
157+ updateStatus ( newStatus ) ;
120158 } ) ;
121- return response . ok ;
122- } catch ( err ) {
123- return false ;
124- }
159+ } ;
160+
161+ let enqueueAgain = false ;
162+
163+ const enqueueUpdateStatus = ( ) => {
164+ if ( healthCheckTimeout ) {
165+ return ;
166+ }
167+
168+ healthCheckTimeout = setTimeout ( ( ) => {
169+ void fetchAndUpdateStatus ( ) . then ( ( ) => {
170+ if ( ! enqueueAgain ) {
171+ return ;
172+ }
173+
174+ healthCheckTimeout = undefined ;
175+ enqueueUpdateStatus ( ) ;
176+ } ) ;
177+ } , 1_000 ) ;
178+ } ;
179+
180+ return {
181+ status ( ) {
182+ return status ;
183+ } ,
184+ start ( ) {
185+ enqueueAgain = true ;
186+ enqueueUpdateStatus ( ) ;
187+ } ,
188+ stop ( ) {
189+ status = undefined ;
190+ enqueueAgain = false ;
191+ clearTimeout ( healthCheckTimeout ) ;
192+ healthCheckTimeout = undefined ;
193+ } ,
194+ onChange ( callback ) {
195+ emitter . on ( callback ) ;
196+ if ( status ) {
197+ callback ( status ) ;
198+ }
199+ } ,
200+ dispose ( ) {
201+ clearTimeout ( healthCheckTimeout ) ;
202+ } ,
203+ } ;
125204}
0 commit comments