diff --git a/providers/queue_provider.ts b/providers/queue_provider.ts index e27d1eb..1e7ce24 100644 --- a/providers/queue_provider.ts +++ b/providers/queue_provider.ts @@ -43,12 +43,25 @@ 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. + */ + #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..ab0be8c 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,65 @@ 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) + }) })