rsnext/packages/next/lib/worker.ts
Tobias Koppers f9795fdd26
improve static generation UX (#27171)
#### improve export spinner

update at least once a minute in non-tty
update progress regularly when using the spinner
decrease frequency of the spinner (windows console output is expensive)

#### restart static page generation and collecting page data worker pools when hanging

when for 1 minute no activity happens on the worker pool, restart it
log a warning for hanging jobs

#### add page generation duration to summary tree

![image](https://user-images.githubusercontent.com/1365881/125750454-8845f1b1-faf0-4598-b7a4-ea796b884691.png)

for `[+n more pages]` is will show `(avg 321 ms)` when the average is over the threshold.
It will allocate 8 lines for preview pages (instead of 4) when they contain slow pages

## Feature

- [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [x] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [x] Errors have helpful link attached, see `contributing.md`

## Documentation / Examples

- [ ] Make sure the linting passes
2021-07-16 09:21:44 +00:00

88 lines
2.4 KiB
TypeScript

import { Worker as JestWorker } from 'jest-worker'
type FarmOptions = ConstructorParameters<typeof JestWorker>[1]
const RESTARTED = Symbol('restarted')
export class Worker {
private _worker: JestWorker | undefined
constructor(
workerPath: string,
options: FarmOptions & {
timeout?: number
onRestart?: (method: string, args: any[], attempts: number) => void
exposedMethods: ReadonlyArray<string>
}
) {
let { timeout, onRestart, ...farmOptions } = options
let restartPromise: Promise<typeof RESTARTED>
let resolveRestartPromise: (arg: typeof RESTARTED) => void
let activeTasks = 0
this._worker = undefined
const createWorker = () => {
this._worker = new JestWorker(workerPath, farmOptions) as JestWorker
restartPromise = new Promise(
(resolve) => (resolveRestartPromise = resolve)
)
this._worker.getStdout().pipe(process.stdout)
this._worker.getStderr().pipe(process.stderr)
}
createWorker()
const onHanging = () => {
const worker = this._worker
if (!worker) return
const resolve = resolveRestartPromise
createWorker()
worker.end().then(() => {
resolve(RESTARTED)
})
}
let hangingTimer: number | false = false
const onActivity = () => {
if (hangingTimer) clearTimeout(hangingTimer)
hangingTimer = activeTasks > 0 && setTimeout(onHanging, timeout)
}
for (const method of farmOptions.exposedMethods) {
if (method.startsWith('_')) continue
;(this as any)[method] = timeout
? // eslint-disable-next-line no-loop-func
async (...args: any[]) => {
activeTasks++
try {
let attempts = 0
for (;;) {
onActivity()
const result = await Promise.race([
(this._worker as any)[method](...args),
restartPromise,
])
if (result !== RESTARTED) return result
if (onRestart) onRestart(method, args, ++attempts)
}
} finally {
activeTasks--
onActivity()
}
}
: (this._worker as any)[method].bind(this._worker)
}
}
end(): ReturnType<JestWorker['end']> {
const worker = this._worker
if (!worker) {
throw new Error('Farm is ended, no more calls can be done to it')
}
this._worker = undefined
return worker.end()
}
}