77 * file that was distributed with this source code.
88 */
99
10- import { Is } from '@athenna/common'
1110import { Log } from '@athenna/logger'
1211import { Queue } from '#src/facades/Queue'
12+ import { Is , Parser } from '@athenna/common'
1313import { WorkerImpl } from '#src/worker/WorkerImpl'
1414import type { Context , ConnectionOptions } from '#src/types'
1515import type { WorkerHandler } from '#src/types/WorkerHandler'
16+ import { WorkerTimeoutException } from '#src/exceptions/WorkerTimeoutException'
1617
1718export class WorkerTaskBuilder {
1819 public worker : {
@@ -22,14 +23,14 @@ export class WorkerTaskBuilder {
2223 name ?: string
2324
2425 /**
25- * The queue connection of the worker task .
26+ * Define the maximun number of concurrent processes of the same worker .
2627 */
27- connection ?: string
28+ concurrency ?: number
2829
2930 /**
30- * The interval instance of the worker task.
31+ * The queue connection of the worker task.
3132 */
32- interval ?: NodeJS . Timeout
33+ connection ?: string
3334
3435 /**
3536 * Define if the worker task is registered.
@@ -45,13 +46,10 @@ export class WorkerTaskBuilder {
4546 * The handler of the worker task.
4647 */
4748 handler ?: ( ctx : Context ) => any | Promise < any >
48-
49- /**
50- * Define if the worker task is running.
51- */
52- isRunning ?: boolean
5349 } = { }
5450
51+ private timers : NodeJS . Timeout [ ] = [ ]
52+
5553 public constructor ( ) {
5654 this . worker . connection = Config . get ( 'queue.default' )
5755 }
@@ -70,6 +68,20 @@ export class WorkerTaskBuilder {
7068 return this
7169 }
7270
71+ /**
72+ * Set the max number of concurrent worker tasks.
73+ *
74+ * @example
75+ * ```ts
76+ * new WorkerTaskBuilder().name('my_worker_task').concurrency(5)
77+ * ```
78+ */
79+ public concurrency ( concurrency : number ) {
80+ this . worker . concurrency = concurrency
81+
82+ return this
83+ }
84+
7385 /**
7486 * Set the handler of the worker task.
7587 *
@@ -208,25 +220,62 @@ export class WorkerTaskBuilder {
208220 return
209221 }
210222
223+ if ( this . timers . length ) {
224+ return
225+ }
226+
227+ const n = this . worker . concurrency ?? 1
228+
229+ for ( let i = 0 ; i < n ; i ++ ) {
230+ this . spawn ( )
231+ }
232+ }
233+
234+ /**
235+ * Use spawn to force a worker instance to run.
236+ */
237+ private spawn ( ) {
211238 const intervalToRun =
212239 this . worker . options ?. workerInterval ||
213240 Config . get (
214241 `queue.connections.${ this . worker . connection } .workerInterval` ,
215242 1000
216243 )
217244
245+ const timeoutMs =
246+ this . worker . options ?. workerTimeoutMs ??
247+ Config . get (
248+ `queue.connections.${ this . worker . connection } .workerTimeoutMs` ,
249+ Parser . timeToMs ( '5m' )
250+ )
251+
218252 const initialOffset = this . computeInitialOffset ( intervalToRun )
219253
220- this . worker . interval = setTimeout ( async ( ) => {
221- if ( ! this . worker . isRunning ) {
222- this . worker . isRunning = true
254+ const loop = async ( ) => {
255+ if ( ! this . worker . isRegistered ) {
256+ return
257+ }
223258
224- await this . run ( )
225- this . worker . isRunning = false
259+ try {
260+ await Promise . race ( [
261+ this . run ( ) ,
262+ new Promise ( ( resolve , reject ) =>
263+ setTimeout (
264+ ( ) => reject ( new WorkerTimeoutException ( this . worker . name ) ) ,
265+ timeoutMs
266+ )
267+ )
268+ ] )
269+ } catch ( err ) {
270+ Log . channelOrVanilla ( 'exception' ) . error ( err )
271+ } finally {
272+ const delay = intervalToRun + this . computeJitter ( intervalToRun )
273+
274+ this . timers . push ( setTimeout ( loop , delay ) )
226275 }
276+ }
227277
228- this . scheduleNext ( intervalToRun )
229- } , initialOffset )
278+ this . timers . push ( setTimeout ( loop , initialOffset ) )
230279 }
231280
232281 /**
@@ -242,17 +291,11 @@ export class WorkerTaskBuilder {
242291 return
243292 }
244293
245- if ( ! this . worker . interval ) {
246- return
247- }
248-
249294 this . worker . isRegistered = false
250- this . worker . isRunning = false
251295
252- if ( this . worker . interval ) {
253- clearTimeout ( this . worker . interval )
254- this . worker . interval = undefined
255- }
296+ this . timers . forEach ( t => clearTimeout ( t ) )
297+
298+ this . timers = [ ]
256299 }
257300
258301 /**
@@ -304,29 +347,4 @@ export class WorkerTaskBuilder {
304347
305348 return Math . floor ( Math . random ( ) * ( max + 1 ) )
306349 }
307-
308- /**
309- * Schedule the next worker task.
310- */
311- private scheduleNext ( baseMs : number ) {
312- if ( ! this . worker . isRegistered ) {
313- return
314- }
315-
316- const delay = baseMs + this . computeJitter ( baseMs )
317-
318- this . worker . interval = setTimeout ( async ( ) => {
319- if ( this . worker . isRunning ) {
320- return this . scheduleNext ( baseMs )
321- }
322-
323- this . worker . isRunning = true
324-
325- await this . run ( )
326-
327- this . worker . isRunning = false
328-
329- this . scheduleNext ( baseMs )
330- } , delay )
331- }
332350}
0 commit comments