chore: medusa shutdown (#6865)

* chore: medusa shutdown

* continue

* use shutdown

* on application shutdown

* consume shutdown

* more connection close

* more cleanup

* more cleanup

* update lock

* revert package

* graceful shutdown

* Create yellow-apples-attack.md

* graceful shutdown

* graceful shutdown

---------

Co-authored-by: Sebastian Rindom <skrindom@gmail.com>
Co-authored-by: Riqwan Thamir <rmthamir@gmail.com>
This commit is contained in:
Adrien de Peretti
2024-03-29 09:22:29 -03:00
committed by GitHub
co-authored by Sebastian Rindom Riqwan Thamir
parent 0c0b425de7
commit 8fd1488938
20 changed files with 234 additions and 76 deletions
@@ -54,7 +54,10 @@ export default async function ({ port, cpus, directory }) {
const app = express()
const { dbConnection } = await loaders({ directory, expressApp: app })
const { dbConnection, shutdown } = await loaders({
directory,
expressApp: app,
})
const serverActivity = Logger.activity(`Creating server`)
const server = GracefulShutdownServer.create(
app.listen(port, (err) => {
@@ -70,7 +73,9 @@ export default async function ({ port, cpus, directory }) {
server
.shutdown()
.then(() => {
process.exit(0)
shutdown().then(() => {
process.exit(0)
})
})
.catch((e) => {
process.exit(1)
+5 -4
View File
@@ -19,14 +19,13 @@ export default async function ({ port, directory }) {
const app = express()
try {
const { dbConnection, configModule, container } = await loaders({
const { dbConnection, shutdown } = await loaders({
directory,
expressApp: app,
})
let server
const serverActivity = Logger.activity(`Creating server`)
server = GracefulShutdownServer.create(
const server = GracefulShutdownServer.create(
app.listen(port, (err) => {
if (err) {
return
@@ -42,7 +41,9 @@ export default async function ({ port, directory }) {
.shutdown()
.then(() => {
Logger.info("Gracefully stopping the server.")
process.exit(0)
shutdown().then(() => {
process.exit(0)
})
})
.catch((e) => {
Logger.error("Error received when shutting down the server.", e)
+12 -3
View File
@@ -11,7 +11,10 @@ type Options = {
configModule: ConfigModule
}
export default async ({ app, configModule }: Options): Promise<Express> => {
export default async ({ app, configModule }: Options): Promise<{
app: Express,
shutdown: () => Promise<void>
}> => {
let sameSite: string | boolean = false
let secure = false
if (
@@ -38,9 +41,11 @@ export default async ({ app, configModule }: Options): Promise<Express> => {
store: null,
}
let redisClient
if (configModule?.projectConfig?.redis_url) {
const RedisStore = createStore(session)
const redisClient = new Redis(
redisClient = new Redis(
configModule.projectConfig.redis_url,
configModule.projectConfig.redis_options ?? {}
)
@@ -63,5 +68,9 @@ export default async ({ app, configModule }: Options): Promise<Express> => {
res.status(200).send("OK")
})
return app
const shutdown = async () => {
redisClient?.disconnect()
}
return { app, shutdown }
}
+34 -8
View File
@@ -6,8 +6,8 @@ import {
import { ConfigModule, MODULE_RESOURCE_TYPE } from "@medusajs/types"
import {
ContainerRegistrationKeys,
MedusaV2Flag,
isString,
MedusaV2Flag, promiseAll,
} from "@medusajs/utils"
import { asValue } from "awilix"
import { Express, NextFunction, Request, Response } from "express"
@@ -97,18 +97,21 @@ async function loadMedusaV2({
container.register({
[ContainerRegistrationKeys.LOGGER]: asValue(Logger),
featureFlagRouter: asValue(featureFlagRouter),
[ContainerRegistrationKeys.FEATURE_FLAG_ROUTER]: asValue(featureFlagRouter),
[ContainerRegistrationKeys.CONFIG_MODULE]: asValue(configModule),
["remoteQuery"]: asValue(null),
[ContainerRegistrationKeys.REMOTE_QUERY]: asValue(null),
})
await loadMedusaApp({
const { onApplicationShutdown: medusaAppOnApplicationShutdown } = await loadMedusaApp({
configModule,
container,
})
let expressShutdown = async () => {}
if (shouldStartAPI) {
await expressLoader({ app: expressApp, configModule })
const { shutdown } = await expressLoader({ app: expressApp, configModule })
expressShutdown = shutdown
expressApp.use((req: Request, res: Response, next: NextFunction) => {
req.scope = container.createScope() as MedusaContainer
@@ -145,11 +148,21 @@ async function loadMedusaV2({
await createDefaultsWorkflow(container).run()
const shutdown = async () => {
await promiseAll([
container.dispose(),
pgConnection?.context?.destroy(),
expressShutdown(),
medusaAppOnApplicationShutdown()
])
}
return {
configModule,
container,
app: expressApp,
pgConnection,
shutdown,
}
}
@@ -163,6 +176,7 @@ export default async ({
dbConnection?: Connection
app: Express
pgConnection: unknown
shutdown: () => Promise<void>
}> => {
const configModule = loadConfig(rootDirectory)
const featureFlagRouter = featureFlagsLoader(configModule, Logger)
@@ -197,7 +211,7 @@ export default async ({
featureFlagRouter: asValue(featureFlagRouter),
})
await redisLoader({ container, configModule, logger: Logger })
const { shutdown: redisShutdown } = await redisLoader({ container, configModule, logger: Logger })
const modelsActivity = Logger.activity(`Initializing models${EOL}`)
track("MODELS_INIT_STARTED")
@@ -257,7 +271,7 @@ export default async ({
track("MODULES_INIT_STARTED")
// Move before services init once all modules are migrated and do not rely on core resources anymore
await loadMedusaApp({
const { onApplicationShutdown: medusaAppOnApplicationShutdown } = await loadMedusaApp({
configModule,
container,
})
@@ -267,7 +281,7 @@ export default async ({
const expActivity = Logger.activity(`Initializing express${EOL}`)
track("EXPRESS_INIT_STARTED")
await expressLoader({ app: expressApp, configModule })
const { shutdown: expressShutdown } = await expressLoader({ app: expressApp, configModule })
await passportLoader({ app: expressApp, configModule })
const exAct = Logger.success(expActivity, "Express intialized") || {}
track("EXPRESS_INIT_COMPLETED", { duration: exAct.duration })
@@ -324,11 +338,23 @@ export default async ({
Logger.success(searchActivity, "Indexing event emitted") || {}
track("SEARCH_ENGINE_INDEXING_COMPLETED", { duration: searchAct.duration })
async function shutdown() {
await promiseAll([
container.dispose(),
dbConnection?.destroy(),
pgConnection?.context?.destroy(),
redisShutdown(),
expressShutdown(),
medusaAppOnApplicationShutdown(),
])
}
return {
configModule,
container,
dbConnection,
app: expressApp,
pgConnection,
shutdown,
}
}
@@ -249,7 +249,6 @@ export async function runModulesLoader({
}
container: MedusaContainer
}): Promise<void> {
const featureFlagRouter = container.resolve<FlagRouter>("featureFlagRouter")
const injectedDependencies = {
[ContainerRegistrationKeys.PG_CONNECTION]: container.resolve(
ContainerRegistrationKeys.PG_CONNECTION
+13 -5
View File
@@ -15,23 +15,25 @@ async function redisLoader({
container,
configModule,
logger,
}: Options): Promise<void> {
}: Options): Promise<{ shutdown: () => Promise<void> }> {
let client!: Redis | FakeRedis
if (configModule.projectConfig.redis_url) {
const redisClient = new Redis(configModule.projectConfig.redis_url, {
client = new Redis(configModule.projectConfig.redis_url, {
// Lazy connect to properly handle connection errors
lazyConnect: true,
...(configModule.projectConfig.redis_options ?? {}),
})
try {
await redisClient.connect()
await client.connect()
logger?.info(`Connection to Redis established`)
} catch (err) {
logger?.error(`An error occurred while connecting to Redis:${EOL} ${err}`)
}
container.register({
redisClient: asValue(redisClient),
redisClient: asValue(client),
})
} else {
if (process.env.NODE_ENV === "production") {
@@ -43,12 +45,18 @@ async function redisLoader({
logger.info("Using fake Redis")
// Economical way of dealing with redis clients
const client = new FakeRedis()
client = new FakeRedis()
container.register({
redisClient: asValue(client),
})
}
return {
shutdown: async () => {
client.disconnect()
},
}
}
export default redisLoader