Skip to content

Commit 77ec0ea

Browse files
authored
Merge pull request #763 from bcomnes/expose-wip
Expose wip
2 parents 79fd01c + 347444f commit 77ec0ea

3 files changed

Lines changed: 50 additions & 1 deletion

File tree

docs/api/workers.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
Adds a new polling worker for a queue and executes the provided callback function when jobs are found. Each call to work() will add a new worker and resolve a unqiue worker id.
66

7-
Workers can be stopped via `offWork()` all at once by queue name or individually by using the worker id. Worker activity may be monitored by listening to the `wip` event.
7+
Workers can be stopped via `offWork()` all at once by queue name or individually by using the worker id. Worker activity may be monitored by listening to the `wip` event or by polling [`getWipData()`](#getwipdataoptions).
88

99
The default options for `work()` is 1 job every 2 seconds.
1010

@@ -217,6 +217,33 @@ await boss.work('process-video', async ([ job ]) => {
217217
})
218218
```
219219

220+
### `getWipData(options)`
221+
222+
Returns a snapshot of all workers in this instance of pg-boss with state `created`, `active`, or `stopping`. This is the same data payload emitted by the `wip` event, but available on-demand without waiting for a job transition.
223+
224+
Use this for continuous monitoring of worker utilization — for example, driving metrics or autoscaling signals when jobs are long-running and the `wip` event may not fire frequently enough.
225+
226+
**Arguments**
227+
- `options`: object *(optional)*
228+
229+
**Options**
230+
231+
* **includeInternal**, bool, *(default=false)*
232+
233+
If true, includes workers for pg-boss internal queues (e.g., scheduling).
234+
235+
**Returns**: `WipData[]`
236+
237+
```js
238+
// Poll worker utilization every 2 seconds for metrics
239+
setInterval(() => {
240+
const workers = boss.getWipData()
241+
const working = workers.filter(w => w.state === 'active' && w.count > 0).length
242+
const idle = workers.filter(w => w.state === 'active' && w.count === 0).length
243+
console.log(`working: ${working}, idle: ${idle}`)
244+
}, 2000)
245+
```
246+
220247
### `notifyWorker(id)`
221248

222249
Notifies a worker by id to bypass the job polling interval (see `pollingIntervalSeconds`) for this iteration in the loop.

src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,10 @@ export class PgBoss extends EventEmitter<types.PgBossEventMap> {
327327
return this.#boss.supervise(name)
328328
}
329329

330+
getWipData (options?: { includeInternal?: boolean }): types.WipData[] {
331+
return this.#manager.getWipData(options)
332+
}
333+
330334
getSpy<T = object> (name: string): JobSpyInterface<T> {
331335
return this.#manager.getSpy<T>(name)
332336
}

test/workTest.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,24 @@ describe('work', function () {
328328
expect(wip2.length).toBe(1)
329329
})
330330

331+
it('getWipData() should return current worker state', async function () {
332+
ctx.boss = await helper.start(ctx.bossConfig)
333+
334+
await ctx.boss.send(ctx.schema)
335+
336+
await ctx.boss.work(ctx.schema, { pollingIntervalSeconds: 1 }, () => delay(2000))
337+
338+
// Wait for the job to be picked up
339+
const firstWipEvent = new Promise<void>(resolve => ctx.boss!.once('wip', () => resolve()))
340+
await firstWipEvent
341+
342+
const wip = ctx.boss.getWipData()
343+
344+
expect(wip.length).toBe(1)
345+
expect(wip[0].name).toBe(ctx.schema)
346+
expect(wip[0].state).toBe('active')
347+
})
348+
331349
it('should reject work() after stopping', async function () {
332350
ctx.boss = await helper.start(ctx.bossConfig)
333351

0 commit comments

Comments
 (0)