@@ -2850,3 +2850,172 @@ test.group('Worker | Scheduler Integration', () => {
28502850 assert . includeMembers ( executedJobs , [ 'scheduled' , 'regular' ] )
28512851 } )
28522852} )
2853+
2854+ test . group ( 'Worker | one per process' , ( group ) => {
2855+ group . each . teardown ( ( ) => Locator . clear ( ) )
2856+
2857+ const config = ( adapter : MemoryAdapter ) => ( {
2858+ default : 'memory' ,
2859+ adapters : { memory : ( ) => adapter } ,
2860+ logger : new MemoryLogger ( ) ,
2861+ worker : { idleDelay : 5 , gracefulShutdown : false } ,
2862+ } )
2863+
2864+ async function waitFor ( condition : ( ) => boolean ) {
2865+ for ( let i = 0 ; i < 200 && ! condition ( ) ; i ++ ) {
2866+ await setTimeout ( 5 )
2867+ }
2868+ }
2869+
2870+ test ( 'start() rejects a second Worker and keeps the first one working' , async ( {
2871+ assert,
2872+ cleanup,
2873+ } ) => {
2874+ let executed = 0
2875+
2876+ class CountedJob extends Job {
2877+ async execute ( ) {
2878+ executed ++
2879+ }
2880+ }
2881+
2882+ Locator . register ( 'CountedJob' , CountedJob )
2883+ const adapter = new MemoryAdapter ( )
2884+ const first = new Worker ( config ( adapter ) )
2885+ const second = new Worker ( config ( adapter ) )
2886+ cleanup ( ( ) => first . stop ( ) )
2887+
2888+ const running = first . start ( [ 'default' ] )
2889+ await setTimeout ( 20 )
2890+
2891+ await assert . rejects ( ( ) => second . start ( [ 'default' ] ) , errors . E_WORKER_ALREADY_RUNNING )
2892+
2893+ // Starting the second Worker did not re-initialize the QueueManager and
2894+ // destroy the Adapter of the first one.
2895+ await adapter . push ( { id : 'after-second' , name : 'CountedJob' , payload : { } , attempts : 0 } )
2896+ await waitFor ( ( ) => executed === 1 )
2897+ assert . equal ( executed , 1 )
2898+
2899+ await first . stop ( )
2900+ await running
2901+ } )
2902+
2903+ test ( 'start() accepts another Worker once the first one stopped' , async ( { cleanup } ) => {
2904+ const adapter = new MemoryAdapter ( )
2905+ const first = new Worker ( config ( adapter ) )
2906+ const second = new Worker ( config ( adapter ) )
2907+ cleanup ( async ( ) => {
2908+ await first . stop ( )
2909+ await second . stop ( )
2910+ } )
2911+
2912+ const firstRun = first . start ( [ 'default' ] )
2913+ await setTimeout ( 20 )
2914+ await first . stop ( )
2915+ await firstRun
2916+
2917+ const secondRun = second . start ( [ 'default' ] )
2918+ await setTimeout ( 20 )
2919+ await second . stop ( )
2920+ await secondRun
2921+ } )
2922+
2923+ test ( 'a second start() of the same Worker keeps the process reserved' , async ( {
2924+ assert,
2925+ cleanup,
2926+ } ) => {
2927+ const first = new Worker ( config ( new MemoryAdapter ( ) ) )
2928+ const other = new Worker ( config ( new MemoryAdapter ( ) ) )
2929+ cleanup ( ( ) => first . stop ( ) )
2930+
2931+ const running = first . start ( [ 'default' ] )
2932+ await setTimeout ( 20 )
2933+
2934+ // The loop already runs: this call returns at once and must not release the process.
2935+ await first . start ( [ 'default' ] )
2936+ await assert . rejects ( ( ) => other . start ( [ 'default' ] ) , errors . E_WORKER_ALREADY_RUNNING )
2937+
2938+ await first . stop ( )
2939+ await running
2940+ } )
2941+
2942+ test ( 'the process stays reserved until stop() has finalized every job' , async ( {
2943+ assert,
2944+ cleanup,
2945+ } ) => {
2946+ class BlockedJob extends Job {
2947+ async execute ( ) { }
2948+ }
2949+
2950+ const fixture = createWorkerFixture ( )
2951+ fixture . adapter . finalizations . block ( 1 )
2952+ const other = new Worker ( config ( new MemoryAdapter ( ) ) )
2953+ cleanup ( ( ) => fixture . cleanup ( ) )
2954+
2955+ await fixture . push ( BlockedJob , { id : 'blocked-finalization' } )
2956+ const running = fixture . start ( )
2957+ await fixture . adapter . finalizations . waitForStarted ( 1 )
2958+
2959+ // The stop has begun, but the finalization still needs the Adapter.
2960+ const stop = trackPromise ( fixture . worker . stop ( ) )
2961+ await setTimeout ( 10 )
2962+ await assert . rejects ( ( ) => other . start ( [ 'default' ] ) , errors . E_WORKER_ALREADY_RUNNING )
2963+ assert . isFalse ( stop . settled )
2964+
2965+ fixture . adapter . finalizations . release ( 1 )
2966+ await stop . promise
2967+ await running
2968+
2969+ const otherRun = other . start ( [ 'default' ] )
2970+ await setTimeout ( 20 )
2971+ await other . stop ( )
2972+ await otherRun
2973+ } )
2974+
2975+ test ( 'a restart requested during stop() reserves the process again' , async ( {
2976+ assert,
2977+ cleanup,
2978+ } ) => {
2979+ class BlockedJob extends Job {
2980+ async execute ( ) { }
2981+ }
2982+
2983+ const fixture = createWorkerFixture ( )
2984+ fixture . adapter . finalizations . block ( 1 )
2985+ const other = new Worker ( config ( new MemoryAdapter ( ) ) )
2986+ cleanup ( ( ) => fixture . cleanup ( ) )
2987+
2988+ await fixture . push ( BlockedJob , { id : 'blocked-before-restart' } )
2989+ const firstRun = fixture . start ( )
2990+ await fixture . adapter . finalizations . waitForStarted ( 1 )
2991+
2992+ const stop = trackPromise ( fixture . worker . stop ( ) )
2993+ const restart = fixture . start ( )
2994+ fixture . adapter . finalizations . release ( 1 )
2995+ await stop . promise
2996+ await firstRun
2997+ await fixture . adapter . acquisitions . waitForStarted ( 2 )
2998+
2999+ // The restarted Worker holds the process again.
3000+ await assert . rejects ( ( ) => other . start ( [ 'default' ] ) , errors . E_WORKER_ALREADY_RUNNING )
3001+
3002+ await fixture . worker . stop ( )
3003+ await restart
3004+ } )
3005+
3006+ test ( 'a start that fails to initialize does not block the next Worker' , async ( {
3007+ assert,
3008+ cleanup,
3009+ } ) => {
3010+ const broken = new Worker ( { default : 'memory' , adapters : { } } )
3011+ await assert . rejects ( ( ) => broken . start ( [ 'default' ] ) , errors . E_CONFIGURATION_ERROR )
3012+
3013+ const worker = new Worker ( config ( new MemoryAdapter ( ) ) )
3014+ cleanup ( ( ) => worker . stop ( ) )
3015+
3016+ const run = worker . start ( [ 'default' ] )
3017+ await setTimeout ( 20 )
3018+ await worker . stop ( )
3019+ await run
3020+ } )
3021+ } )
0 commit comments