Skip to content

Commit

Permalink
feat: allow disabling timeouts and add error types
Browse files Browse the repository at this point in the history
  • Loading branch information
ianshade committed Nov 3, 2022
1 parent e50e19c commit 5616a1a
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 23 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,11 @@ An optional options object can be passed to threadedClass() with the following p
| `threadUsage` | number | A number between 0 - 1, how large part of a thread the instance takes up. For example; if set to 0.1, a thread will be re-used for up to 10 instances. |
| `threadId` | string | Set to an arbitrary id to put the instance in a specific thread. Instances with the same threadIds will be put in the same thread. |
| `autoRestart` | boolean | If the process crashes or freezes it's automatically restarted. (ThreadedClassManager will emit the "restarted" event upon restart) |
| `restartTimeout` | number | (milliseconds), if the process needs to restart, how long to wait for it to initalize, before failing. (default is 1000ms, 0 disables this timeout) |
| `killTimeout` | number | (milliseconds), if the process is being killed, how long to wait for it to terminate, before failing. (default is 1000ms, 0 disables this timeout) |
| `disableMultithreading` | boolean | Set to true to disable multi-threading, this might be useful when you want to disable multi-threading but keep the interface unchanged. |
| `pathToWorker` | string | Set path to worker, used in browser |
| `freezeLimit` | number | (milliseconds), how long to wait before considering the child to be unresponsive. (default is 1000 ms) |
| `freezeLimit` | number | (milliseconds), how long to wait before considering the child to be unresponsive. (default is 1000 ms, 0 disables this timeout) |

## Features

Expand Down
9 changes: 6 additions & 3 deletions src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@ export interface ThreadedClassConfig {
threadId?: string
/** If the process crashes or freezes it's automatically restarted. (ThreadedClassManager will emit the "restarted" event upon restart) */
autoRestart?: boolean
/** If the process needs to restart, how long to wait for it to initalize, before failing. (default is 1000ms) */
/** If the process needs to restart, how long to wait for it to initalize, before failing. (default is 1000ms, 0 disables this timeout) */
restartTimeout?: number
/** If the process is being killed, how long to wait for it to terminate, before failing. (default is 1000ms) */
/** If the process is being killed, how long to wait for it to terminate, before failing. (default is 1000ms, 0 disables this timeout) */
killTimeout?: number
/** Set to true to disable multi-threading, this might be useful when you want to disable multi-threading but keep the interface unchanged. */
disableMultithreading?: boolean
/** Set path to worker, used in browser */
pathToWorker?: string
/** (milliseconds), how long to wait before considering the child to be unresponsive. (default is 1000 ms) */
/** (milliseconds), how long to wait before considering the child to be unresponsive. (default is 1000 ms, 0 disables this timeout) */
freezeLimit?: number
/** Optional: name of the instance, used in debugging */
instanceName?: string
Expand All @@ -51,3 +51,6 @@ export interface WebWorkerMemoryUsage {
export type MemUsageReportInner = NodeJS.MemoryUsage | WebWorkerMemoryUsage | { error: string }

export type MemUsageReport = MemUsageReportInner & { description: string }

export class RestartTimeoutError extends Error { name = 'RestartTimeoutError' }
export class KillTimeoutError extends Error { name = 'KillTimeoutError' }
47 changes: 28 additions & 19 deletions src/parent-process/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
ArgDefinition,
decodeArguments
} from '../shared/sharedApi'
import { ThreadedClassConfig, ThreadedClass, MemUsageReport, MemUsageReportInner } from '../api'
import { ThreadedClassConfig, ThreadedClass, MemUsageReport, MemUsageReportInner, RestartTimeoutError, KillTimeoutError } from '../api'
import { isBrowser, nodeSupportsWorkerThreads, browserSupportsWebWorkers } from '../shared/lib'
import { forkWebWorker } from './workerPlatform/webWorkers'
import { forkWorkerThread } from './workerPlatform/workerThreads'
Expand Down Expand Up @@ -425,8 +425,6 @@ export class ThreadedClassManagerClassInternal extends EventEmitter {
await this.killChild(child, 'restart child', true)
}

const restartTimeout = child.config.restartTimeout ?? DEFAULT_RESTART_TIMEOUT

if (!child.alive) {
// clear old process:
child.process.removeAllListeners()
Expand All @@ -444,17 +442,23 @@ export class ThreadedClassManagerClassInternal extends EventEmitter {
this._setupChildProcess(child)
}
let p = new Promise<void>((resolve, reject) => {
const timeout = setTimeout(() => {
reject(`Timeout when trying to restart after ${restartTimeout}`)
this.killChild(child, 'timeout when restarting').catch((e) => {
this.consoleError(`Could not kill child: "${child.id}"`, e)
})
this.removeListener('initialized', onInit)
}, restartTimeout)
let timeout: NodeJS.Timeout | undefined
if (child.config.restartTimeout !== 0) {
const restartTimeout = child.config.restartTimeout ?? DEFAULT_RESTART_TIMEOUT
timeout = setTimeout(() => {
reject(new RestartTimeoutError(`Timeout when trying to restart after ${restartTimeout}`))
this.killChild(child, 'timeout when restarting').catch((e) => {
this.consoleError(`Could not kill child: "${child.id}"`, e)
})
this.removeListener('initialized', onInit)
}, restartTimeout)
}

const onInit = (child: Child) => {
if (child === child) {
clearTimeout(timeout)
if (timeout) {
clearTimeout(timeout)
}
resolve()
this.removeListener('initialized', onInit)
}
Expand Down Expand Up @@ -520,6 +524,9 @@ export class ThreadedClassManagerClassInternal extends EventEmitter {
})
}
public startMonitoringChild (instance: ChildInstance) {
if (instance.freezeLimit === 0) {
return
}
const pingTime: number = instance.freezeLimit || DEFAULT_CHILD_FREEZE_TIME
const monitorChild = () => {

Expand Down Expand Up @@ -852,7 +859,6 @@ export class ThreadedClassManagerClassInternal extends EventEmitter {
delete this._children[child.id]
resolve()
} else {
const killTimeout = child.config.killTimeout ?? DEFAULT_KILL_TIMEOUT

child.process.once('close', () => {
if (!dontCleanUp) {
Expand All @@ -866,13 +872,16 @@ export class ThreadedClassManagerClassInternal extends EventEmitter {
}
resolve()
})
setTimeout(() => {
delete this._children[child.id]
reject(`Timeout: Kill child process "${child.id}"`)
},killTimeout)
if (!child.isClosing) {
child.isClosing = true
child.process.kill()
if (child.config.killTimeout !== 0) {
const killTimeout = child.config.killTimeout ?? DEFAULT_KILL_TIMEOUT
setTimeout(() => {
delete this._children[child.id]
reject(new KillTimeoutError(`Timeout: Kill child process "${child.id}"`))
}, killTimeout)
if (!child.isClosing) {
child.isClosing = true
child.process.kill()
}
}
}
}
Expand Down

0 comments on commit 5616a1a

Please sign in to comment.