mirror of
https://github.com/cdr/code-server.git
synced 2026-01-26 18:14:21 +01:00
Refactor to use event Emitter
This commit is contained in:
parent
a52383d7f7
commit
ea538ed79e
3 changed files with 56 additions and 40 deletions
|
|
@ -1,26 +1,30 @@
|
||||||
import { logger } from "@coder/logger"
|
import { logger } from "@coder/logger"
|
||||||
import { promises as fs } from "fs"
|
import { promises as fs } from "fs"
|
||||||
import { wrapper } from "./wrapper"
|
import { Emitter } from "../common/emitter"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides a heartbeat using a local file to indicate activity.
|
* Provides a heartbeat using a local file to indicate activity.
|
||||||
*/
|
*/
|
||||||
export class Heart {
|
export class Heart {
|
||||||
private heartbeatTimer?: NodeJS.Timeout
|
private heartbeatTimer?: NodeJS.Timeout
|
||||||
private idleShutdownTimer?: NodeJS.Timeout
|
|
||||||
private heartbeatInterval = 60000
|
private heartbeatInterval = 60000
|
||||||
public lastHeartbeat = 0
|
public lastHeartbeat = 0
|
||||||
|
private readonly _onChange = new Emitter<"alive" | "idle" | "unknown">()
|
||||||
|
readonly onChange = this._onChange.event
|
||||||
|
private state: "alive" | "idle" | "unknown" = "idle"
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
private readonly heartbeatPath: string,
|
private readonly heartbeatPath: string,
|
||||||
private idleTimeoutSeconds: number | undefined,
|
|
||||||
private readonly isActive: () => Promise<boolean>,
|
private readonly isActive: () => Promise<boolean>,
|
||||||
) {
|
) {
|
||||||
this.beat = this.beat.bind(this)
|
this.beat = this.beat.bind(this)
|
||||||
this.alive = this.alive.bind(this)
|
this.alive = this.alive.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
if (this.idleTimeoutSeconds) {
|
private setState(state: typeof this.state) {
|
||||||
this.idleShutdownTimer = setTimeout(() => this.exitIfIdle(), this.idleTimeoutSeconds * 1000)
|
if (this.state !== state) {
|
||||||
|
this.state = state
|
||||||
|
this._onChange.emit(this.state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -35,6 +39,7 @@ export class Heart {
|
||||||
*/
|
*/
|
||||||
public async beat(): Promise<void> {
|
public async beat(): Promise<void> {
|
||||||
if (this.alive()) {
|
if (this.alive()) {
|
||||||
|
this.setState("alive")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -43,13 +48,22 @@ export class Heart {
|
||||||
if (typeof this.heartbeatTimer !== "undefined") {
|
if (typeof this.heartbeatTimer !== "undefined") {
|
||||||
clearTimeout(this.heartbeatTimer)
|
clearTimeout(this.heartbeatTimer)
|
||||||
}
|
}
|
||||||
if (typeof this.idleShutdownTimer !== "undefined") {
|
|
||||||
clearInterval(this.idleShutdownTimer)
|
this.heartbeatTimer = setTimeout(async () => {
|
||||||
}
|
try {
|
||||||
this.heartbeatTimer = setTimeout(() => heartbeatTimer(this.isActive, this.beat), this.heartbeatInterval)
|
if (await this.isActive()) {
|
||||||
if (this.idleTimeoutSeconds) {
|
this.beat()
|
||||||
this.idleShutdownTimer = setTimeout(() => this.exitIfIdle(), this.idleTimeoutSeconds * 1000)
|
} else {
|
||||||
}
|
this.setState("idle")
|
||||||
|
}
|
||||||
|
} catch (error: unknown) {
|
||||||
|
logger.warn((error as Error).message)
|
||||||
|
this.setState("unknown")
|
||||||
|
}
|
||||||
|
}, this.heartbeatInterval)
|
||||||
|
|
||||||
|
this.setState("alive")
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await fs.writeFile(this.heartbeatPath, "")
|
return await fs.writeFile(this.heartbeatPath, "")
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
|
@ -65,26 +79,4 @@ export class Heart {
|
||||||
clearTimeout(this.heartbeatTimer)
|
clearTimeout(this.heartbeatTimer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private exitIfIdle(): void {
|
|
||||||
logger.warn(`Idle timeout of ${this.idleTimeoutSeconds} seconds exceeded`)
|
|
||||||
wrapper.exit(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper function for the heartbeatTimer.
|
|
||||||
*
|
|
||||||
* If heartbeat is active, call beat. Otherwise do nothing.
|
|
||||||
*
|
|
||||||
* Extracted to make it easier to test.
|
|
||||||
*/
|
|
||||||
export async function heartbeatTimer(isActive: Heart["isActive"], beat: Heart["beat"]) {
|
|
||||||
try {
|
|
||||||
if (await isActive()) {
|
|
||||||
beat()
|
|
||||||
}
|
|
||||||
} catch (error: unknown) {
|
|
||||||
logger.warn((error as Error).message)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import { loadCustomStrings } from "./i18n"
|
||||||
import { register } from "./routes"
|
import { register } from "./routes"
|
||||||
import { VSCodeModule } from "./routes/vscode"
|
import { VSCodeModule } from "./routes/vscode"
|
||||||
import { isDirectory, open } from "./util"
|
import { isDirectory, open } from "./util"
|
||||||
|
import { wrapper } from "./wrapper"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true if the user passed an extension-related VS Code flag.
|
* Return true if the user passed an extension-related VS Code flag.
|
||||||
|
|
@ -141,7 +142,7 @@ export const runCodeServer = async (
|
||||||
const app = await createApp(args)
|
const app = await createApp(args)
|
||||||
const protocol = args.cert ? "https" : "http"
|
const protocol = args.cert ? "https" : "http"
|
||||||
const serverAddress = ensureAddress(app.server, protocol)
|
const serverAddress = ensureAddress(app.server, protocol)
|
||||||
const disposeRoutes = await register(app, args)
|
const { disposeRoutes, heart } = await register(app, args)
|
||||||
|
|
||||||
logger.info(`Using config file ${args.config}`)
|
logger.info(`Using config file ${args.config}`)
|
||||||
logger.info(`${protocol.toUpperCase()} server listening on ${serverAddress.toString()}`)
|
logger.info(`${protocol.toUpperCase()} server listening on ${serverAddress.toString()}`)
|
||||||
|
|
@ -168,6 +169,23 @@ export const runCodeServer = async (
|
||||||
|
|
||||||
if (args["idle-timeout-seconds"]) {
|
if (args["idle-timeout-seconds"]) {
|
||||||
logger.info(` - Idle timeout set to ${args["idle-timeout-seconds"]} seconds`)
|
logger.info(` - Idle timeout set to ${args["idle-timeout-seconds"]} seconds`)
|
||||||
|
|
||||||
|
let idleShutdownTimer: NodeJS.Timeout | undefined
|
||||||
|
const startIdleShutdownTimer = () => {
|
||||||
|
idleShutdownTimer = setTimeout(() => {
|
||||||
|
logger.warn(`Idle timeout of ${args["idle-timeout-seconds"]} seconds exceeded`)
|
||||||
|
wrapper.exit(0)
|
||||||
|
}, args["idle-timeout-seconds"]! * 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
startIdleShutdownTimer()
|
||||||
|
|
||||||
|
heart.onChange((state) => {
|
||||||
|
clearTimeout(idleShutdownTimer)
|
||||||
|
if (state === "idle") {
|
||||||
|
startIdleShutdownTimer()
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args["disable-proxy"]) {
|
if (args["disable-proxy"]) {
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,11 @@ import * as vscode from "./vscode"
|
||||||
/**
|
/**
|
||||||
* Register all routes and middleware.
|
* Register all routes and middleware.
|
||||||
*/
|
*/
|
||||||
export const register = async (app: App, args: DefaultedArgs): Promise<Disposable["dispose"]> => {
|
export const register = async (
|
||||||
const heart = new Heart(path.join(paths.data, "heartbeat"), args["idle-timeout-seconds"], async () => {
|
app: App,
|
||||||
|
args: DefaultedArgs,
|
||||||
|
): Promise<{ disposeRoutes: Disposable["dispose"]; heart: Heart }> => {
|
||||||
|
const heart = new Heart(path.join(paths.data, "heartbeat"), async () => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// getConnections appears to not call the callback when there are no more
|
// getConnections appears to not call the callback when there are no more
|
||||||
// connections. Feels like it must be a bug? For now add a timer to make
|
// 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
|
||||||
app.router.use(errorHandler)
|
app.router.use(errorHandler)
|
||||||
app.wsRouter.use(wsErrorHandler)
|
app.wsRouter.use(wsErrorHandler)
|
||||||
|
|
||||||
return () => {
|
return {
|
||||||
heart.dispose()
|
disposeRoutes: () => {
|
||||||
vscode.dispose()
|
heart.dispose()
|
||||||
|
vscode.dispose()
|
||||||
|
},
|
||||||
|
heart,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue