Skip to content

Commit 93c4bd4

Browse files
Refactor to use event Emitter
1 parent a311433 commit 93c4bd4

File tree

3 files changed

+56
-40
lines changed

3 files changed

+56
-40
lines changed

src/node/heart.ts

Lines changed: 26 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,30 @@
11
import { logger } from "@coder/logger"
22
import { promises as fs } from "fs"
3-
import { wrapper } from "./wrapper"
3+
import { Emitter } from "../common/emitter"
44

55
/**
66
* Provides a heartbeat using a local file to indicate activity.
77
*/
88
export class Heart {
99
private heartbeatTimer?: NodeJS.Timeout
10-
private idleShutdownTimer?: NodeJS.Timeout
1110
private heartbeatInterval = 60000
1211
public lastHeartbeat = 0
12+
private readonly _onChange = new Emitter<"alive" | "idle" | "unknown">()
13+
readonly onChange = this._onChange.event
14+
private state: "alive" | "idle" | "unknown" = "idle"
1315

1416
public constructor(
1517
private readonly heartbeatPath: string,
16-
private idleTimeoutSeconds: number | undefined,
1718
private readonly isActive: () => Promise<boolean>,
1819
) {
1920
this.beat = this.beat.bind(this)
2021
this.alive = this.alive.bind(this)
22+
}
2123

22-
if (this.idleTimeoutSeconds) {
23-
this.idleShutdownTimer = setTimeout(() => this.exitIfIdle(), this.idleTimeoutSeconds * 1000)
24+
private setState(state: typeof this.state) {
25+
if (this.state !== state) {
26+
this.state = state
27+
this._onChange.emit(this.state)
2428
}
2529
}
2630

@@ -35,6 +39,7 @@ export class Heart {
3539
*/
3640
public async beat(): Promise<void> {
3741
if (this.alive()) {
42+
this.setState("alive")
3843
return
3944
}
4045

@@ -43,13 +48,22 @@ export class Heart {
4348
if (typeof this.heartbeatTimer !== "undefined") {
4449
clearTimeout(this.heartbeatTimer)
4550
}
46-
if (typeof this.idleShutdownTimer !== "undefined") {
47-
clearInterval(this.idleShutdownTimer)
48-
}
49-
this.heartbeatTimer = setTimeout(() => heartbeatTimer(this.isActive, this.beat), this.heartbeatInterval)
50-
if (this.idleTimeoutSeconds) {
51-
this.idleShutdownTimer = setTimeout(() => this.exitIfIdle(), this.idleTimeoutSeconds * 1000)
52-
}
51+
52+
this.heartbeatTimer = setTimeout(async () => {
53+
try {
54+
if (await this.isActive()) {
55+
this.beat()
56+
} else {
57+
this.setState("idle")
58+
}
59+
} catch (error: unknown) {
60+
logger.warn((error as Error).message)
61+
this.setState("unknown")
62+
}
63+
}, this.heartbeatInterval)
64+
65+
this.setState("alive")
66+
5367
try {
5468
return await fs.writeFile(this.heartbeatPath, "")
5569
} catch (error: any) {
@@ -65,26 +79,4 @@ export class Heart {
6579
clearTimeout(this.heartbeatTimer)
6680
}
6781
}
68-
69-
private exitIfIdle(): void {
70-
logger.warn(`Idle timeout of ${this.idleTimeoutSeconds} seconds exceeded`)
71-
wrapper.exit(0)
72-
}
73-
}
74-
75-
/**
76-
* Helper function for the heartbeatTimer.
77-
*
78-
* If heartbeat is active, call beat. Otherwise do nothing.
79-
*
80-
* Extracted to make it easier to test.
81-
*/
82-
export async function heartbeatTimer(isActive: Heart["isActive"], beat: Heart["beat"]) {
83-
try {
84-
if (await isActive()) {
85-
beat()
86-
}
87-
} catch (error: unknown) {
88-
logger.warn((error as Error).message)
89-
}
9082
}

src/node/main.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { loadCustomStrings } from "./i18n"
1111
import { register } from "./routes"
1212
import { VSCodeModule } from "./routes/vscode"
1313
import { isDirectory, open } from "./util"
14+
import { wrapper } from "./wrapper"
1415

1516
/**
1617
* Return true if the user passed an extension-related VS Code flag.
@@ -141,7 +142,7 @@ export const runCodeServer = async (
141142
const app = await createApp(args)
142143
const protocol = args.cert ? "https" : "http"
143144
const serverAddress = ensureAddress(app.server, protocol)
144-
const disposeRoutes = await register(app, args)
145+
const { disposeRoutes, heart } = await register(app, args)
145146

146147
logger.info(`Using config file ${args.config}`)
147148
logger.info(`${protocol.toUpperCase()} server listening on ${serverAddress.toString()}`)
@@ -168,6 +169,23 @@ export const runCodeServer = async (
168169

169170
if (args["idle-timeout-seconds"]) {
170171
logger.info(` - Idle timeout set to ${args["idle-timeout-seconds"]} seconds`)
172+
173+
let idleShutdownTimer: NodeJS.Timeout | undefined
174+
const startIdleShutdownTimer = () => {
175+
idleShutdownTimer = setTimeout(() => {
176+
logger.warn(`Idle timeout of ${args["idle-timeout-seconds"]} seconds exceeded`)
177+
wrapper.exit(0)
178+
}, args["idle-timeout-seconds"]! * 1000)
179+
}
180+
181+
startIdleShutdownTimer()
182+
183+
heart.onChange((state) => {
184+
clearTimeout(idleShutdownTimer)
185+
if (state === "idle") {
186+
startIdleShutdownTimer()
187+
}
188+
})
171189
}
172190

173191
if (args["disable-proxy"]) {

src/node/routes/index.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,11 @@ import * as vscode from "./vscode"
2828
/**
2929
* Register all routes and middleware.
3030
*/
31-
export const register = async (app: App, args: DefaultedArgs): Promise<Disposable["dispose"]> => {
32-
const heart = new Heart(path.join(paths.data, "heartbeat"), args["idle-timeout-seconds"], async () => {
31+
export const register = async (
32+
app: App,
33+
args: DefaultedArgs,
34+
): Promise<{ disposeRoutes: Disposable["dispose"]; heart: Heart }> => {
35+
const heart = new Heart(path.join(paths.data, "heartbeat"), async () => {
3336
return new Promise((resolve, reject) => {
3437
// getConnections appears to not call the callback when there are no more
3538
// connections. Feels like it must be a bug? For now add a timer to make
@@ -173,8 +176,11 @@ export const register = async (app: App, args: DefaultedArgs): Promise<Disposabl
173176
app.router.use(errorHandler)
174177
app.wsRouter.use(wsErrorHandler)
175178

176-
return () => {
177-
heart.dispose()
178-
vscode.dispose()
179+
return {
180+
disposeRoutes: () => {
181+
heart.dispose()
182+
vscode.dispose()
183+
},
184+
heart,
179185
}
180186
}

0 commit comments

Comments
 (0)