c32d8b155f
This had me very confused for quite a while until I did a binary search inspection on route/index.ts. Only with the heart.beat line commented out did my tests pass without leaking. They weren't leaking fds but just this heartbeat timer and node of course prints just fds that are active when it detects some sort of leak I guess and that made the whole thing very confusing. These fds are not leaked and will close when node's event loop detects there are no more callbacks to run. no of handles 3 tcp stream { fd: 20, readable: false, writable: true, address: {}, serverAddr: null } tcp stream { fd: 22, readable: false, writable: true, address: {}, serverAddr: null } tcp stream { fd: 23, readable: true, writable: false, address: {}, serverAddr: null } It kept printing the above text again and again for 60s and then the test binary times out I think. I'm not sure if it was node printing the stuff above or if it was a mocha thing. But it was really confusing... cc @code-asher for thoughts on what was going on. edit: It was the leaked-handles import in socket.test.ts!!! Not sure if we should keep it, this was really confusing and misleading.
58 lines
1.5 KiB
TypeScript
58 lines
1.5 KiB
TypeScript
import { logger } from "@coder/logger"
|
|
import { promises as fs } from "fs"
|
|
|
|
/**
|
|
* Provides a heartbeat using a local file to indicate activity.
|
|
*/
|
|
export class Heart {
|
|
private heartbeatTimer?: NodeJS.Timeout
|
|
private heartbeatInterval = 60000
|
|
public lastHeartbeat = 0
|
|
|
|
public constructor(private readonly heartbeatPath: string, private readonly isActive: () => Promise<boolean>) {}
|
|
|
|
public alive(): boolean {
|
|
const now = Date.now()
|
|
return now - this.lastHeartbeat < this.heartbeatInterval
|
|
}
|
|
/**
|
|
* Write to the heartbeat file if we haven't already done so within the
|
|
* timeout and start or reset a timer that keeps running as long as there is
|
|
* activity. Failures are logged as warnings.
|
|
*/
|
|
public beat(): void {
|
|
if (this.alive()) {
|
|
return
|
|
}
|
|
|
|
logger.trace("heartbeat")
|
|
fs.writeFile(this.heartbeatPath, "").catch((error) => {
|
|
logger.warn(error.message)
|
|
})
|
|
this.lastHeartbeat = Date.now()
|
|
if (typeof this.heartbeatTimer !== "undefined") {
|
|
clearTimeout(this.heartbeatTimer)
|
|
}
|
|
this.heartbeatTimer = setTimeout(() => {
|
|
this.isActive()
|
|
.then((active) => {
|
|
if (active) {
|
|
this.beat()
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
logger.warn(error.message)
|
|
})
|
|
}, this.heartbeatInterval)
|
|
}
|
|
|
|
/**
|
|
* Call to clear any heartbeatTimer for shutdown.
|
|
*/
|
|
public dispose(): void {
|
|
if (typeof this.heartbeatTimer !== "undefined") {
|
|
clearTimeout(this.heartbeatTimer)
|
|
}
|
|
}
|
|
}
|