From 2da6055f823a7c62c940f08722140561fcc3dcea Mon Sep 17 00:00:00 2001 From: Davi de Carvalho Date: Fri, 16 Oct 2026 10:29:46 -0300 Subject: [PATCH 1/2] fix: do not resolve the queue manager when warming up the app A warmed up app never gets started and its providers are never shut down. Resolving the queue manager during boot opens the adapter connections (for example, redis), which keeps the process alive after commands like "node ace codegen" terminate the app. Co-Authored-By: Claude Opus 5.5 --- providers/queue_provider.ts | 16 +++++++++- tests/helpers.ts | 9 ++++-- tests/provider.spec.ts | 61 +++++++++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 3 deletions(-) diff --git a/providers/queue_provider.ts b/providers/queue_provider.ts index e27d1eb..d352a6b 100644 --- a/providers/queue_provider.ts +++ b/providers/queue_provider.ts @@ -43,12 +43,26 @@ export default class QueueProvider { }) } + /** + * A warmed up app never gets started and its providers are never shut + * down. Therefore, we must not resolve the queue manager (and open the + * adapter connections) in this mode. The "getMode" method is missing in + * the older versions of the framework core without the "warmup" mode. + */ + #isWarmingUp() { + return this.app.getMode?.() === 'warmup' + } + async boot() { + if (this.#isWarmingUp()) { + return + } + await this.app.container.make('queue.manager') } async start() { - if (this.app.getEnvironment() === 'console') { + if (this.app.getEnvironment() === 'console' || this.#isWarmingUp()) { return } diff --git a/tests/helpers.ts b/tests/helpers.ts index b9959cc..2c81887 100644 --- a/tests/helpers.ts +++ b/tests/helpers.ts @@ -9,7 +9,7 @@ import { getActiveTest } from '@japa/runner' import { IgnitorFactory } from '@adonisjs/core/factories' -import type { AppEnvironments } from '@adonisjs/core/types/app' +import type { AppEnvironments, ApplicationModes } from '@adonisjs/core/types/app' import type { DatabaseConfig } from '@adonisjs/lucid/types/database' import { defineConfig, drivers } from '../index.js' @@ -21,7 +21,8 @@ export async function setupApp( config: { queue?: ReturnType database?: DatabaseConfig - } = {} + } = {}, + mode?: ApplicationModes ) { const ignitor = new IgnitorFactory() .withCoreProviders() @@ -56,6 +57,10 @@ export async function setupApp( }) const app = ignitor.createApp(env || 'web') + if (mode) { + app.setMode(mode) + } + await app.init().then(() => app.boot()) getActiveTest()?.cleanup(() => app.terminate()) diff --git a/tests/provider.spec.ts b/tests/provider.spec.ts index 8f45d1d..8331a00 100644 --- a/tests/provider.spec.ts +++ b/tests/provider.spec.ts @@ -9,6 +9,7 @@ import { test } from '@japa/runner' import { fileURLToPath } from 'node:url' +import { configProvider } from '@adonisjs/core' import { Locator } from '@boringnode/queue' import { sync } from '@boringnode/queue/drivers/sync_adapter' @@ -272,4 +273,64 @@ test.group('Provider', (group) => { Worker.prototype.start = originalStart } }) + test('should not resolve the queue manager when warming up the app', async ({ assert, fs }) => { + await fs.create( + 'app/jobs/provider_warmup_job.ts', + ` + import { Job } from '@boringnode/queue' + + export default class ProviderWarmupJob extends Job { + async execute() {} + } + ` + ) + + /** + * Adapters like redis acquire their connection when resolved. Resolving + * them while warming up the app leaves open sockets behind, since the + * providers of a warmed up app are never shut down + */ + let resolvedAdapters = 0 + const app = await setupApp( + 'web', + { + queue: defineConfig({ + default: 'sync', + adapters: { + sync: configProvider.create(async () => { + resolvedAdapters++ + return sync() + }), + }, + locations: [`${fs.basePath}/app/jobs/provider_warmup_job.ts`], + }), + }, + 'warmup' + ) + + assert.equal(resolvedAdapters, 0) + + await app.warmUp() + + assert.equal(app.getState(), 'warmed') + assert.equal(resolvedAdapters, 0) + assert.isUndefined(Locator.get('ProviderWarmupJob')) + }) + + test('should resolve the queue manager when booting the app', async ({ assert }) => { + let resolvedAdapters = 0 + await setupApp('web', { + queue: defineConfig({ + default: 'sync', + adapters: { + sync: configProvider.create(async () => { + resolvedAdapters++ + return sync() + }), + }, + }), + }) + + assert.equal(resolvedAdapters, 1) + }) }) From e7691f823749b3a4edee74a777d08d1828e9512d Mon Sep 17 00:00:00 2001 From: Romain Lanz Date: Sun, 18 Oct 2026 09:37:30 +0000 Subject: [PATCH 2/2] refactor(provider): call getMode without optional chaining The core peer dependency is now ^7.6.0, which always ships getMode and the warmup mode, so the defensive optional call is no longer needed. --- providers/queue_provider.ts | 5 ++--- tests/provider.spec.ts | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/providers/queue_provider.ts b/providers/queue_provider.ts index d352a6b..1e7ce24 100644 --- a/providers/queue_provider.ts +++ b/providers/queue_provider.ts @@ -46,11 +46,10 @@ export default class QueueProvider { /** * A warmed up app never gets started and its providers are never shut * down. Therefore, we must not resolve the queue manager (and open the - * adapter connections) in this mode. The "getMode" method is missing in - * the older versions of the framework core without the "warmup" mode. + * adapter connections) in this mode. */ #isWarmingUp() { - return this.app.getMode?.() === 'warmup' + return this.app.getMode() === 'warmup' } async boot() { diff --git a/tests/provider.spec.ts b/tests/provider.spec.ts index 8331a00..ab0be8c 100644 --- a/tests/provider.spec.ts +++ b/tests/provider.spec.ts @@ -273,6 +273,7 @@ test.group('Provider', (group) => { Worker.prototype.start = originalStart } }) + test('should not resolve the queue manager when warming up the app', async ({ assert, fs }) => { await fs.create( 'app/jobs/provider_warmup_job.ts',