backend-plugin-api: separate out hook in addShutdownHook
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-plugin-api': minor
|
||||
---
|
||||
|
||||
**BREAKING**: Split out the hook for both lifecycle services so that the first parameter of `addShutdownHook` is the hook function, and the second is the options.
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
'@backstage/backend-test-utils': patch
|
||||
'@backstage/backend-app-api': patch
|
||||
'@backstage/backend-common': patch
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
---
|
||||
|
||||
Updated usage of the lifecycle service.
|
||||
@@ -479,10 +479,7 @@ createBackendPlugin({
|
||||
// do some other stuff.
|
||||
}, 1000);
|
||||
|
||||
lifecycle.addShutdownHook({
|
||||
fn: () => clearInterval(interval),
|
||||
logger,
|
||||
});
|
||||
lifecycle.addShutdownHook(() => clearInterval(interval));
|
||||
},
|
||||
});
|
||||
},
|
||||
@@ -499,17 +496,19 @@ The following example shows how to override the default implementation of the li
|
||||
|
||||
```ts
|
||||
class MyCustomLifecycleService implements RootLifecycleService {
|
||||
constructor(private readonly logger: LoggerService) {
|
||||
['SIGKILL', 'SIGTERM'].map(signal =>
|
||||
process.on(signal, () => this.shutdown()),
|
||||
);
|
||||
}
|
||||
constructor(private readonly logger: LoggerService) {}
|
||||
|
||||
#isCalled = false;
|
||||
#shutdownTasks: Array<LifecycleServiceShutdownHook> = [];
|
||||
#shutdownTasks: Array<{
|
||||
hook: LifecycleServiceShutdownHook;
|
||||
options?: LifecycleServiceShutdownOptions;
|
||||
}> = [];
|
||||
|
||||
addShutdownHook(options: LifecycleServiceShutdownHook): void {
|
||||
this.#shutdownTasks.push(options);
|
||||
addShutdownHook(
|
||||
hook: LifecycleServiceShutdownHook,
|
||||
options?: LifecycleServiceShutdownOptions,
|
||||
): void {
|
||||
this.#shutdownTasks.push({ hook, options });
|
||||
}
|
||||
|
||||
async shutdown(): Promise<void> {
|
||||
@@ -520,10 +519,10 @@ class MyCustomLifecycleService implements RootLifecycleService {
|
||||
|
||||
this.logger.info(`Running ${this.#shutdownTasks.length} shutdown tasks...`);
|
||||
await Promise.all(
|
||||
this.#shutdownTasks.map(async hook => {
|
||||
const { logger = this.logger } = hook;
|
||||
this.#shutdownTasks.map(async ({ hook, options }) => {
|
||||
const logger = options?.logger ?? this.logger;
|
||||
try {
|
||||
await hook.fn();
|
||||
await hook();
|
||||
logger.info(`Shutdown hook succeeded`);
|
||||
} catch (error) {
|
||||
logger.error(`Shutdown hook failed, ${error}`);
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
createServiceFactory,
|
||||
coreServices,
|
||||
LifecycleServiceShutdownHook,
|
||||
LifecycleServiceShutdownOptions,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
|
||||
/**
|
||||
@@ -33,11 +34,12 @@ export const lifecycleFactory = createServiceFactory({
|
||||
async factory({ rootLifecycle, logger, pluginMetadata }) {
|
||||
const plugin = pluginMetadata.getId();
|
||||
return {
|
||||
addShutdownHook(options: LifecycleServiceShutdownHook): void {
|
||||
rootLifecycle.addShutdownHook({
|
||||
...options,
|
||||
|
||||
logger: options.logger?.child({ plugin }) ?? logger,
|
||||
addShutdownHook(
|
||||
hook: LifecycleServiceShutdownHook,
|
||||
options?: LifecycleServiceShutdownOptions,
|
||||
): void {
|
||||
rootLifecycle.addShutdownHook(hook, {
|
||||
logger: options?.logger?.child({ plugin }) ?? logger,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
+1
-6
@@ -104,12 +104,7 @@ export const rootHttpRouterFactory = createServiceFactory({
|
||||
{ logger },
|
||||
);
|
||||
|
||||
lifecycle.addShutdownHook({
|
||||
async fn() {
|
||||
await server.stop();
|
||||
},
|
||||
logger,
|
||||
});
|
||||
lifecycle.addShutdownHook(() => server.stop());
|
||||
|
||||
await server.start();
|
||||
|
||||
|
||||
+11
-9
@@ -21,11 +21,7 @@ describe('lifecycleService', () => {
|
||||
it('should execute registered shutdown hook', async () => {
|
||||
const service = new BackendLifecycleImpl(getVoidLogger());
|
||||
const hook = jest.fn();
|
||||
service.addShutdownHook({
|
||||
fn: async () => {
|
||||
hook();
|
||||
},
|
||||
});
|
||||
service.addShutdownHook(() => hook());
|
||||
// should not execute the hook more than once.
|
||||
await service.shutdown();
|
||||
await service.shutdown();
|
||||
@@ -35,10 +31,16 @@ describe('lifecycleService', () => {
|
||||
|
||||
it('should not throw errors', async () => {
|
||||
const service = new BackendLifecycleImpl(getVoidLogger());
|
||||
service.addShutdownHook({
|
||||
fn: async () => {
|
||||
throw new Error('oh no');
|
||||
},
|
||||
service.addShutdownHook(() => {
|
||||
throw new Error('oh no');
|
||||
});
|
||||
await expect(service.shutdown()).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('should not throw async errors', async () => {
|
||||
const service = new BackendLifecycleImpl(getVoidLogger());
|
||||
service.addShutdownHook(async () => {
|
||||
throw new Error('oh no');
|
||||
});
|
||||
await expect(service.shutdown()).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
+13
-6
@@ -18,6 +18,7 @@ import {
|
||||
createServiceFactory,
|
||||
coreServices,
|
||||
LifecycleServiceShutdownHook,
|
||||
LifecycleServiceShutdownOptions,
|
||||
RootLifecycleService,
|
||||
LoggerService,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
@@ -26,10 +27,16 @@ export class BackendLifecycleImpl implements RootLifecycleService {
|
||||
constructor(private readonly logger: LoggerService) {}
|
||||
|
||||
#isCalled = false;
|
||||
#shutdownTasks: Array<LifecycleServiceShutdownHook> = [];
|
||||
#shutdownTasks: Array<{
|
||||
hook: LifecycleServiceShutdownHook;
|
||||
options?: LifecycleServiceShutdownOptions;
|
||||
}> = [];
|
||||
|
||||
addShutdownHook(options: LifecycleServiceShutdownHook): void {
|
||||
this.#shutdownTasks.push(options);
|
||||
addShutdownHook(
|
||||
hook: LifecycleServiceShutdownHook,
|
||||
options?: LifecycleServiceShutdownOptions,
|
||||
): void {
|
||||
this.#shutdownTasks.push({ hook, options });
|
||||
}
|
||||
|
||||
async shutdown(): Promise<void> {
|
||||
@@ -40,10 +47,10 @@ export class BackendLifecycleImpl implements RootLifecycleService {
|
||||
|
||||
this.logger.info(`Running ${this.#shutdownTasks.length} shutdown tasks...`);
|
||||
await Promise.all(
|
||||
this.#shutdownTasks.map(async hook => {
|
||||
const { logger = this.logger } = hook;
|
||||
this.#shutdownTasks.map(async ({ hook, options }) => {
|
||||
const logger = options?.logger ?? this.logger;
|
||||
try {
|
||||
await hook.fn();
|
||||
await hook();
|
||||
logger.info(`Shutdown hook succeeded`);
|
||||
} catch (error) {
|
||||
logger.error(`Shutdown hook failed, ${error}`);
|
||||
|
||||
@@ -81,12 +81,10 @@ export function createSqliteDatabaseClient(
|
||||
});
|
||||
|
||||
// If the dev store is available we save the database state on shutdown
|
||||
deps.lifecycle.addShutdownHook({
|
||||
async fn() {
|
||||
const connection = await database.client.acquireConnection();
|
||||
const data = connection.serialize();
|
||||
await devStore.save(dataKey, data);
|
||||
},
|
||||
deps.lifecycle.addShutdownHook(async () => {
|
||||
const connection = await database.client.acquireConnection();
|
||||
const data = connection.serialize();
|
||||
await devStore.save(dataKey, data);
|
||||
});
|
||||
} else {
|
||||
database = knexFactory(knexConfig);
|
||||
|
||||
@@ -294,14 +294,19 @@ export interface IdentityService extends IdentityApi {}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface LifecycleService {
|
||||
addShutdownHook(options: LifecycleServiceShutdownHook): void;
|
||||
addShutdownHook(
|
||||
hook: LifecycleServiceShutdownHook,
|
||||
options?: LifecycleServiceShutdownOptions,
|
||||
): void;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type LifecycleServiceShutdownHook = {
|
||||
fn: () => void | Promise<void>;
|
||||
export type LifecycleServiceShutdownHook = () => void | Promise<void>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface LifecycleServiceShutdownOptions {
|
||||
logger?: LoggerService;
|
||||
};
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface LoggerService {
|
||||
|
||||
@@ -19,14 +19,17 @@ import { LoggerService } from './LoggerService';
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type LifecycleServiceShutdownHook = {
|
||||
fn: () => void | Promise<void>;
|
||||
export type LifecycleServiceShutdownHook = () => void | Promise<void>;
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface LifecycleServiceShutdownOptions {
|
||||
/**
|
||||
* Optional {@link LoggerService} that will be used for logging instead of the default logger.
|
||||
*/
|
||||
logger?: LoggerService;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
@@ -35,5 +38,8 @@ export interface LifecycleService {
|
||||
/**
|
||||
* Register a function to be called when the backend is shutting down.
|
||||
*/
|
||||
addShutdownHook(options: LifecycleServiceShutdownHook): void;
|
||||
addShutdownHook(
|
||||
hook: LifecycleServiceShutdownHook,
|
||||
options?: LifecycleServiceShutdownOptions,
|
||||
): void;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ export type { HttpRouterService } from './HttpRouterService';
|
||||
export type {
|
||||
LifecycleService,
|
||||
LifecycleServiceShutdownHook,
|
||||
LifecycleServiceShutdownOptions,
|
||||
} from './LifecycleService';
|
||||
export type { LoggerService, LogMeta } from './LoggerService';
|
||||
export type { PermissionsService } from './PermissionsService';
|
||||
|
||||
@@ -40,10 +40,8 @@ beforeAll(async () => {
|
||||
env.registerInit({
|
||||
deps: { lifecycle: coreServices.lifecycle },
|
||||
async init({ lifecycle }) {
|
||||
lifecycle.addShutdownHook({
|
||||
fn() {
|
||||
globalTestBackendHasBeenStopped = true;
|
||||
},
|
||||
lifecycle.addShutdownHook(() => {
|
||||
globalTestBackendHasBeenStopped = true;
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -145,7 +143,7 @@ describe('TestBackend', () => {
|
||||
lifecycle: coreServices.lifecycle,
|
||||
},
|
||||
async init({ lifecycle }) {
|
||||
lifecycle.addShutdownHook({ fn: shutdownSpy });
|
||||
lifecycle.addShutdownHook(shutdownSpy);
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
@@ -129,12 +129,7 @@ export async function startTestBackend<
|
||||
{ logger },
|
||||
);
|
||||
|
||||
lifecycle.addShutdownHook({
|
||||
async fn() {
|
||||
await server.stop();
|
||||
},
|
||||
logger,
|
||||
});
|
||||
lifecycle.addShutdownHook(() => server.stop(), { logger });
|
||||
|
||||
await server.start();
|
||||
|
||||
|
||||
@@ -97,11 +97,7 @@ export const catalogPlugin = createBackendPlugin({
|
||||
const { processingEngine, router } = await builder.build();
|
||||
|
||||
await processingEngine.start();
|
||||
lifecycle.addShutdownHook({
|
||||
fn: async () => {
|
||||
await processingEngine.stop();
|
||||
},
|
||||
});
|
||||
lifecycle.addShutdownHook(() => processingEngine.stop());
|
||||
httpRouter.use(router);
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user