Skip to content

Commit c884f48

Browse files
committed
Update idle-timeout to have its own timer, use process.kill SIGTERM for shutdown
1 parent a968e70 commit c884f48

File tree

2 files changed

+23
-20
lines changed

2 files changed

+23
-20
lines changed

src/node/heart.ts

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { promises as fs } from "fs"
66
*/
77
export class Heart {
88
private heartbeatTimer?: NodeJS.Timeout
9+
private idleCheckTimer?: NodeJS.Timeout
910
private heartbeatInterval = 60000
1011
public lastHeartbeat = 0
1112

@@ -16,6 +17,10 @@ export class Heart {
1617
) {
1718
this.beat = this.beat.bind(this)
1819
this.alive = this.alive.bind(this)
20+
// Start idle check timer if timeout is configured
21+
if (this.idleTimeout) {
22+
this.startIdleCheck()
23+
}
1924
}
2025

2126
public alive(): boolean {
@@ -37,24 +42,35 @@ export class Heart {
3742
if (typeof this.heartbeatTimer !== "undefined") {
3843
clearTimeout(this.heartbeatTimer)
3944
}
40-
this.heartbeatTimer = setTimeout(
41-
() => heartbeatTimer(this.isActive, this.beat, this.lastHeartbeat, this.idleTimeout),
42-
this.heartbeatInterval
43-
)
45+
this.heartbeatTimer = setTimeout(() => heartbeatTimer(this.isActive, this.beat), this.heartbeatInterval)
4446
try {
4547
return await fs.writeFile(this.heartbeatPath, "")
4648
} catch (error: any) {
4749
logger.warn(error.message)
4850
}
4951
}
5052

53+
private startIdleCheck(): void {
54+
// Check every minute if the idle timeout has been exceeded
55+
this.idleCheckTimer = setInterval(() => {
56+
const timeSinceLastBeat = Date.now() - this.lastHeartbeat
57+
if (timeSinceLastBeat > this.idleTimeout! * 60 * 1000) {
58+
logger.warn(`Idle timeout of ${this.idleTimeout} minutes exceeded`)
59+
process.kill(process.pid, "SIGTERM")
60+
}
61+
}, 60000)
62+
}
63+
5164
/**
5265
* Call to clear any heartbeatTimer for shutdown.
5366
*/
5467
public dispose(): void {
5568
if (typeof this.heartbeatTimer !== "undefined") {
5669
clearTimeout(this.heartbeatTimer)
5770
}
71+
if (typeof this.idleCheckTimer !== "undefined") {
72+
clearInterval(this.idleCheckTimer)
73+
}
5874
}
5975
}
6076

@@ -65,21 +81,8 @@ export class Heart {
6581
*
6682
* Extracted to make it easier to test.
6783
*/
68-
export async function heartbeatTimer(
69-
isActive: Heart["isActive"],
70-
beat: Heart["beat"],
71-
lastHeartbeat: number,
72-
idleTimeout?: number,
73-
) {
84+
export async function heartbeatTimer(isActive: Heart["isActive"], beat: Heart["beat"]) {
7485
try {
75-
// Check for idle timeout first
76-
if (idleTimeout) {
77-
const timeSinceLastBeat = Date.now() - lastHeartbeat
78-
if (timeSinceLastBeat > idleTimeout * 60 * 1000) {
79-
logger.warn(`Idle timeout of ${idleTimeout} minutes exceeded`)
80-
process.exit(0)
81-
}
82-
}
8386
if (await isActive()) {
8487
beat()
8588
}

test/unit/node/heart.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ describe("heartbeatTimer", () => {
9595
const isActive = true
9696
const mockIsActive = jest.fn().mockResolvedValue(isActive)
9797
const mockBeatFn = jest.fn()
98-
await heartbeatTimer(mockIsActive, mockBeatFn, 0)
98+
await heartbeatTimer(mockIsActive, mockBeatFn)
9999
expect(mockIsActive).toHaveBeenCalled()
100100
expect(mockBeatFn).toHaveBeenCalled()
101101
})
@@ -104,7 +104,7 @@ describe("heartbeatTimer", () => {
104104
const error = new Error(errorMsg)
105105
const mockIsActive = jest.fn().mockRejectedValue(error)
106106
const mockBeatFn = jest.fn()
107-
await heartbeatTimer(mockIsActive, mockBeatFn, 0)
107+
await heartbeatTimer(mockIsActive, mockBeatFn)
108108
expect(mockIsActive).toHaveBeenCalled()
109109
expect(mockBeatFn).not.toHaveBeenCalled()
110110
expect(logger.warn).toHaveBeenCalledWith(errorMsg)

0 commit comments

Comments
 (0)