diff --git a/packages/api/src/graphql/GraphqlSequencerModule.ts b/packages/api/src/graphql/GraphqlSequencerModule.ts index 01f1785e3..d44144c39 100644 --- a/packages/api/src/graphql/GraphqlSequencerModule.ts +++ b/packages/api/src/graphql/GraphqlSequencerModule.ts @@ -1,6 +1,6 @@ import assert from "node:assert"; -import { SequencerModule } from "@proto-kit/sequencer"; +import { Closeable, closeable, SequencerModule } from "@proto-kit/sequencer"; import { ChildContainerProvider, Configurable, @@ -29,9 +29,10 @@ export interface GraphqlModulesDefintion< config?: ModulesConfig; } +@closeable() export class GraphqlSequencerModule extends ModuleContainer - implements Configurable, SequencerModule + implements Configurable, SequencerModule, Closeable { public static from( definition: GraphqlModulesDefintion @@ -93,4 +94,10 @@ export class GraphqlSequencerModule } await this.graphqlServer.startServer(); } + + public async close() { + if (this.graphqlServer !== undefined) { + await this.graphqlServer.close(); + } + } } diff --git a/packages/api/src/graphql/GraphqlServer.ts b/packages/api/src/graphql/GraphqlServer.ts index b5b78431e..0a5658540 100644 --- a/packages/api/src/graphql/GraphqlServer.ts +++ b/packages/api/src/graphql/GraphqlServer.ts @@ -145,7 +145,18 @@ export class GraphqlServer extends SequencerModule { }); } - public close() { - this.server?.close(); + public async close() { + if (this.server !== undefined) { + const { server } = this; + + await new Promise((res) => { + server.close((error) => { + if (error !== undefined) { + log.error(error); + } + res(); + }); + }); + } } } diff --git a/packages/common/src/config/ModuleContainer.ts b/packages/common/src/config/ModuleContainer.ts index 5c0161857..83bf9b0a9 100644 --- a/packages/common/src/config/ModuleContainer.ts +++ b/packages/common/src/config/ModuleContainer.ts @@ -28,6 +28,7 @@ import { } from "./ConfigurableModule"; import { ChildContainerProvider } from "./ChildContainerProvider"; import { ChildContainerCreatable } from "./ChildContainerCreatable"; +import { getInjectAliases } from "./injectAlias"; const errors = { configNotSetInContainer: (moduleName: string) => @@ -228,6 +229,16 @@ export class ModuleContainer< } } + protected registerAliases(originalToken: string, clas: TypedClass) { + const aliases = getInjectAliases(clas); + + aliases.forEach((alias) => + this.container.register(alias, { + useToken: originalToken, + }) + ); + } + /** * Register modules into the current container, and registers * a respective resolution hook in order to decorate the module @@ -250,6 +261,8 @@ export class ModuleContainer< { lifecycle: Lifecycle.ContainerScoped } ); this.onAfterModuleResolution(moduleName); + + this.registerAliases(moduleName, useClass); } }); } @@ -412,7 +425,11 @@ export class ModuleContainer< this.container.register(key, declaration, { lifecycle: Lifecycle.Singleton, }); - // eslint-disable-next-line sonarjs/no-duplicated-branches + this.registerAliases( + key, + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + declaration.useClass as TypedClass + ); } else if (isTokenProvider(declaration)) { this.container.register(key, declaration, { lifecycle: Lifecycle.Singleton, diff --git a/packages/common/src/config/injectAlias.ts b/packages/common/src/config/injectAlias.ts new file mode 100644 index 000000000..d2d80a495 --- /dev/null +++ b/packages/common/src/config/injectAlias.ts @@ -0,0 +1,70 @@ +import { TypedClass } from "../types"; + +export const injectAliasMetadataKey = "protokit-inject-alias"; + +/** + * Attaches metadata to the class that the ModuleContainer can pick up + * and inject this class in the DI container under the specified aliases. + * This method supports inheritance, therefore also gets aliases defined + * on superclasses + */ +export function injectAlias(aliases: string[]) { + return (target: TypedClass) => { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + const superAliases = Reflect.getMetadata( + injectAliasMetadataKey, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + Object.getPrototypeOf(target) + ) as string[] | undefined; + + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + const existingAliases = Reflect.getMetadata( + injectAliasMetadataKey, + target + ) as string[] | undefined; + + let allAliases = aliases; + + if (superAliases !== undefined) { + allAliases = allAliases.concat(superAliases); + } + if (existingAliases !== undefined) { + allAliases = allAliases.concat(existingAliases); + } + + Reflect.defineMetadata( + injectAliasMetadataKey, + allAliases.filter( + (value, index, array) => array.indexOf(value) === index + ), + target + ); + }; +} + +/** + * Marks the class to implement a certain interface T, while also attaching + * a DI-injection alias as metadata, that will be picked up by the ModuleContainer + * to allow resolving by that interface name + * @param name The name of the injection alias, convention is to use the same as the name of T + */ +export function implement(name: string) { + return ( + /** + * Check if the target class extends RuntimeModule, while + * also providing static config presets + */ + target: TypedClass + ) => { + injectAlias([name])(target); + }; +} + +export function getInjectAliases(target: TypedClass): string[] { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + const aliases = Reflect.getMetadata( + injectAliasMetadataKey, + target + ) as string[]; + return aliases ?? []; +} diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 9b293c8ef..10b8ae4ba 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -22,3 +22,4 @@ export * from "./compiling/AtomicCompileHelper"; export * from "./compiling/CompileRegistry"; export * from "./compiling/CompilableModule"; export * from "./compiling/services/ChildVerificationKeyService"; +export * from "./config/injectAlias"; diff --git a/packages/common/test/config/ModuleContainer.test.ts b/packages/common/test/config/ModuleContainer.test.ts index 6dd5ddb1d..a418431ff 100644 --- a/packages/common/test/config/ModuleContainer.test.ts +++ b/packages/common/test/config/ModuleContainer.test.ts @@ -10,7 +10,8 @@ import { ModulesRecord, } from "../../src/config/ModuleContainer"; import { TypedClass } from "../../src/types"; -import { DependencyFactory } from "../../src"; +import { DependencyFactory, expectDefined } from "../../src"; +import { injectAlias } from "../../src/config/injectAlias"; // module container will accept modules that extend this type class BaseTestModule extends ConfigurableModule {} @@ -24,6 +25,7 @@ interface TestModuleConfig { } @injectable() +@injectAlias(["child-alias", "multi-alias"]) class ChildModule extends BaseTestModule { public constructor(@inject("TestModule") public readonly testModule: any) { super(); @@ -34,6 +36,7 @@ class ChildModule extends BaseTestModule { } } +@injectAlias(["base-alias", "multi-alias"]) class TestModule extends BaseTestModule implements DependencyFactory @@ -66,7 +69,11 @@ class WrongTestModule {} class TestModuleContainer< Modules extends TestModulesRecord, -> extends ModuleContainer {} +> extends ModuleContainer { + public get dependencyContainer() { + return this.container; + } +} describe("moduleContainer", () => { let container: TestModuleContainer<{ @@ -169,4 +176,40 @@ describe("moduleContainer", () => { expect(config.testConfigProperty2).toBe(2); expect(config.testConfigProperty3).toBe(undefined); }); + + it("should resolve dependencies correctly via alias", () => { + container.configure({ + TestModule: { + testConfigProperty, + }, + + OtherTestModule: { + otherTestConfigProperty: testConfigProperty, + }, + }); + + container.create(() => tsyringeContainer.createChildContainer()); + + // Unfortunately we still need this so that the dependencies are registered + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const m1 = container.resolve("base-alias" as any); + const m2 = container.resolve("TestModule"); + + expectDefined(m1); + // Check if its the same reference + expect(m1).toBe(m2); + + const dm1 = container.resolve("child-alias" as any) as ChildModule; + const dm2 = container.resolve("DependencyModule1"); + + expect(dm1.x()).toBe("dependency factory works"); + expect(dm1.testModule).toBeDefined(); + expect(dm1).toBe(dm2); + + const multi = + container.dependencyContainer.resolveAll>( + "multi-alias" + ); + expect(multi).toHaveLength(2); + }); }); diff --git a/packages/common/test/config/injectAlias.test.ts b/packages/common/test/config/injectAlias.test.ts new file mode 100644 index 000000000..b07ad10d7 --- /dev/null +++ b/packages/common/test/config/injectAlias.test.ts @@ -0,0 +1,28 @@ +import "reflect-metadata"; +import { getInjectAliases, injectAlias } from "../../src/config/injectAlias"; + +@injectAlias(["foo", "bar"]) +class TestClass {} + +@injectAlias(["ayy"]) +class TestClass2 extends TestClass {} + +describe("injectAlias metadata", () => { + it("set and retrieve", () => { + expect.assertions(2); + + const aliases = getInjectAliases(TestClass); + + expect(aliases).toHaveLength(2); + expect(aliases).toStrictEqual(["foo", "bar"]); + }); + + it("recursive", () => { + expect.assertions(2); + + const aliases = getInjectAliases(TestClass2); + + expect(aliases).toHaveLength(3); + expect(aliases).toStrictEqual(["ayy", "foo", "bar"]); + }); +}); diff --git a/packages/deployment/src/queue/BullQueue.ts b/packages/deployment/src/queue/BullQueue.ts index 7b8188aa5..1377cac39 100644 --- a/packages/deployment/src/queue/BullQueue.ts +++ b/packages/deployment/src/queue/BullQueue.ts @@ -6,6 +6,7 @@ import { InstantiatedQueue, TaskQueue, AbstractTaskQueue, + closeable, } from "@proto-kit/sequencer"; import { InstantiatedBullQueue } from "./InstantiatedBullQueue"; @@ -24,9 +25,10 @@ export interface BullQueueConfig { /** * TaskQueue implementation for BullMQ */ +@closeable() export class BullQueue extends AbstractTaskQueue - implements TaskQueue + implements TaskQueue, Closeable { private activePromise?: Promise; @@ -101,4 +103,10 @@ export class BullQueue public async start() { noop(); } + + public async close() { + await this.closeQueues(); + + // Closing of active workers is handled by the LocalTaskWorkerModule + } } diff --git a/packages/indexer/test/GeneratedResolverFactoryGraphqlModule.test.ts b/packages/indexer/test/GeneratedResolverFactoryGraphqlModule.test.ts index 0b681eab6..28ee7ecce 100644 --- a/packages/indexer/test/GeneratedResolverFactoryGraphqlModule.test.ts +++ b/packages/indexer/test/GeneratedResolverFactoryGraphqlModule.test.ts @@ -139,7 +139,7 @@ describe("GeneratedResolverFactoryGraphqlModule", () => { } }); - afterAll(() => { - indexer.resolve("GraphqlServer").close(); + afterAll(async () => { + await indexer.resolve("GraphqlServer").close(); }); }); diff --git a/packages/persistance/src/PrismaRedisDatabase.ts b/packages/persistance/src/PrismaRedisDatabase.ts index b3e2fb538..735703035 100644 --- a/packages/persistance/src/PrismaRedisDatabase.ts +++ b/packages/persistance/src/PrismaRedisDatabase.ts @@ -3,6 +3,7 @@ import { SequencerModule, StorageDependencyMinimumDependencies, Database, + closeable, } from "@proto-kit/sequencer"; import { ChildContainerProvider } from "@proto-kit/common"; import { PrismaClient } from "@prisma/client"; @@ -26,6 +27,7 @@ export interface PrismaRedisCombinedConfig { } @sequencerModule() +@closeable() export class PrismaRedisDatabase extends SequencerModule implements PrismaConnection, RedisConnection, Database diff --git a/packages/persistance/test-integration/PrismaBlockProduction.test.ts b/packages/persistance/test-integration/PrismaBlockProduction.test.ts index 2366f3c57..61eb02d3c 100644 --- a/packages/persistance/test-integration/PrismaBlockProduction.test.ts +++ b/packages/persistance/test-integration/PrismaBlockProduction.test.ts @@ -46,7 +46,7 @@ describe("prisma integration", () => { }; const teardown = async () => { - await appChain.sequencer.resolve("Database").close(); + await appChain.sequencer.close(); }; describe("produce fuzzed block", () => { diff --git a/packages/sdk/src/appChain/AppChain.ts b/packages/sdk/src/appChain/AppChain.ts index 993580f85..af73bbc36 100644 --- a/packages/sdk/src/appChain/AppChain.ts +++ b/packages/sdk/src/appChain/AppChain.ts @@ -348,5 +348,9 @@ export class AppChain< .resolve(WorkerReadyModule) .waitForReady(); } + + public async close() { + await this.sequencer.close(); + } } /* eslint-enable @typescript-eslint/consistent-type-assertions */ diff --git a/packages/sequencer/src/index.ts b/packages/sequencer/src/index.ts index 3e03702ab..d4476a506 100644 --- a/packages/sequencer/src/index.ts +++ b/packages/sequencer/src/index.ts @@ -6,6 +6,7 @@ export * from "./mempool/private/PrivateMempool"; export * from "./sequencer/executor/Sequencer"; export * from "./sequencer/executor/Sequenceable"; export * from "./sequencer/builder/SequencerModule"; +export * from "./sequencer/builder/Closeable"; export * from "./worker/flow/Flow"; export * from "./worker/flow/Task"; export * from "./worker/flow/JSONTaskSerializer"; diff --git a/packages/sequencer/src/protocol/production/trigger/TimedBlockTrigger.ts b/packages/sequencer/src/protocol/production/trigger/TimedBlockTrigger.ts index c1b8e132c..4b0300be0 100644 --- a/packages/sequencer/src/protocol/production/trigger/TimedBlockTrigger.ts +++ b/packages/sequencer/src/protocol/production/trigger/TimedBlockTrigger.ts @@ -2,7 +2,7 @@ import { inject, injectable } from "tsyringe"; import { injectOptional, log } from "@proto-kit/common"; import gcd from "compute-gcd"; -import { Closeable } from "../../../worker/queue/TaskQueue"; +import { closeable, Closeable } from "../../../sequencer/builder/Closeable"; import { BatchProducerModule } from "../BatchProducerModule"; import { Mempool } from "../../../mempool/Mempool"; import { BlockQueue } from "../../../storage/repositories/BlockStorage"; @@ -32,6 +32,7 @@ export interface TimedBlockTriggerEvent extends BlockEvents { } @injectable() +@closeable() export class TimedBlockTrigger extends BlockTriggerBase implements BlockTrigger, Closeable diff --git a/packages/sequencer/src/sequencer/SequencerStartupModule.ts b/packages/sequencer/src/sequencer/SequencerStartupModule.ts index 69ea8a890..4e59dc224 100644 --- a/packages/sequencer/src/sequencer/SequencerStartupModule.ts +++ b/packages/sequencer/src/sequencer/SequencerStartupModule.ts @@ -21,9 +21,14 @@ import { import { VerificationKeyService } from "../protocol/runtime/RuntimeVerificationKeyService"; import { SequencerModule, sequencerModule } from "./builder/SequencerModule"; +import { Closeable, closeable } from "./builder/Closeable"; @sequencerModule() -export class SequencerStartupModule extends SequencerModule { +@closeable() +export class SequencerStartupModule + extends SequencerModule + implements Closeable +{ public constructor( private readonly flowCreator: FlowCreator, @inject("Protocol") @@ -144,4 +149,8 @@ export class SequencerStartupModule extends SequencerModule { log.info("Protocol circuits compiled successfully, commencing startup"); } + + public async close() { + await this.registrationFlow.close(); + } } diff --git a/packages/sequencer/src/sequencer/builder/Closeable.ts b/packages/sequencer/src/sequencer/builder/Closeable.ts new file mode 100644 index 000000000..b7bf3780f --- /dev/null +++ b/packages/sequencer/src/sequencer/builder/Closeable.ts @@ -0,0 +1,9 @@ +import { implement } from "@proto-kit/common"; + +export interface Closeable { + close: () => Promise; +} + +export function closeable() { + return implement("Closeable"); +} diff --git a/packages/sequencer/src/sequencer/executor/Sequencer.ts b/packages/sequencer/src/sequencer/executor/Sequencer.ts index 7089f9882..c15282705 100644 --- a/packages/sequencer/src/sequencer/executor/Sequencer.ts +++ b/packages/sequencer/src/sequencer/executor/Sequencer.ts @@ -18,6 +18,7 @@ import { import { DependencyContainer, injectable } from "tsyringe"; import { SequencerModule } from "../builder/SequencerModule"; +import { Closeable } from "../builder/Closeable"; import { Sequenceable } from "./Sequenceable"; @@ -120,4 +121,15 @@ export class Sequencer ); } } + + public async close() { + log.info("Closing sequencer..."); + const closeables = this.container.resolveAll("Closeable"); + await Promise.all( + closeables.map(async (closeable) => { + await closeable.close(); + }) + ); + log.info("Sequencer closed"); + } } diff --git a/packages/sequencer/src/storage/Database.ts b/packages/sequencer/src/storage/Database.ts index 6013be2a5..12fbc71fe 100644 --- a/packages/sequencer/src/storage/Database.ts +++ b/packages/sequencer/src/storage/Database.ts @@ -1,6 +1,8 @@ +import { Closeable } from "../sequencer/builder/Closeable"; + import type { StorageDependencyFactory } from "./StorageDependencyFactory"; -export interface Database extends StorageDependencyFactory { +export interface Database extends StorageDependencyFactory, Closeable { /** * Prunes all data from the database connection. * Note: This function should only be called immediately at startup, diff --git a/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts b/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts index 159c5ceb4..a8eb330ab 100644 --- a/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts +++ b/packages/sequencer/src/storage/inmemory/InMemoryDatabase.ts @@ -7,6 +7,7 @@ import { } from "../../sequencer/builder/SequencerModule"; import { StorageDependencyMinimumDependencies } from "../StorageDependencyFactory"; import { Database } from "../Database"; +import { closeable } from "../../sequencer/builder/Closeable"; import { InMemoryBlockStorage } from "./InMemoryBlockStorage"; import { InMemoryAsyncMerkleTreeStore } from "./InMemoryAsyncMerkleTreeStore"; @@ -16,6 +17,7 @@ import { InMemorySettlementStorage } from "./InMemorySettlementStorage"; import { InMemoryTransactionStorage } from "./InMemoryTransactionStorage"; @sequencerModule() +@closeable() export class InMemoryDatabase extends SequencerModule implements Database { public dependencies(): StorageDependencyMinimumDependencies { return { @@ -59,6 +61,10 @@ export class InMemoryDatabase extends SequencerModule implements Database { noop(); } + public async close() { + noop(); + } + public async pruneDatabase(): Promise { // Figure out how to implement this nicely. // However, this would only be a op when pruneDatabase will be called diff --git a/packages/sequencer/src/worker/flow/Flow.ts b/packages/sequencer/src/worker/flow/Flow.ts index 985fc2df9..06dd22485 100644 --- a/packages/sequencer/src/worker/flow/Flow.ts +++ b/packages/sequencer/src/worker/flow/Flow.ts @@ -1,7 +1,8 @@ import { inject, injectable } from "tsyringe"; import { log, mapSequential } from "@proto-kit/common"; -import { Closeable, InstantiatedQueue, TaskQueue } from "../queue/TaskQueue"; +import { InstantiatedQueue, TaskQueue } from "../queue/TaskQueue"; +import { Closeable } from "../../sequencer/builder/Closeable"; import { Task, TaskPayload } from "./Task"; diff --git a/packages/sequencer/src/worker/queue/AbstractTaskQueue.ts b/packages/sequencer/src/worker/queue/AbstractTaskQueue.ts index d2816998d..160fec78f 100644 --- a/packages/sequencer/src/worker/queue/AbstractTaskQueue.ts +++ b/packages/sequencer/src/worker/queue/AbstractTaskQueue.ts @@ -16,4 +16,10 @@ export abstract class AbstractTaskQueue< } return this.queues[name]; } + + protected async closeQueues() { + await Promise.all( + Object.values(this.queues).map(async (queue) => await queue.close()) + ); + } } diff --git a/packages/sequencer/src/worker/queue/LocalTaskQueue.ts b/packages/sequencer/src/worker/queue/LocalTaskQueue.ts index 85e11db88..fe2c8606f 100644 --- a/packages/sequencer/src/worker/queue/LocalTaskQueue.ts +++ b/packages/sequencer/src/worker/queue/LocalTaskQueue.ts @@ -2,8 +2,9 @@ import { log, mapSequential, noop } from "@proto-kit/common"; import { sequencerModule } from "../../sequencer/builder/SequencerModule"; import { TaskPayload } from "../flow/Task"; +import { Closeable } from "../../sequencer/builder/Closeable"; -import { Closeable, InstantiatedQueue, TaskQueue } from "./TaskQueue"; +import { InstantiatedQueue, TaskQueue } from "./TaskQueue"; import { ListenerList } from "./ListenerList"; import { AbstractTaskQueue } from "./AbstractTaskQueue"; diff --git a/packages/sequencer/src/worker/queue/TaskQueue.ts b/packages/sequencer/src/worker/queue/TaskQueue.ts index c3040b3a1..f6d805b78 100644 --- a/packages/sequencer/src/worker/queue/TaskQueue.ts +++ b/packages/sequencer/src/worker/queue/TaskQueue.ts @@ -1,4 +1,5 @@ import { TaskPayload } from "../flow/Task"; +import { Closeable } from "../../sequencer/builder/Closeable"; /** * Definition of a connection-object that can generate queues and workers @@ -13,11 +14,6 @@ export interface TaskQueue { options?: { concurrency?: number } ) => Closeable; } - -export interface Closeable { - close: () => Promise; -} - /** * Object that abstracts a concrete connection to a queue instance. */ diff --git a/packages/sequencer/src/worker/worker/FlowTaskWorker.ts b/packages/sequencer/src/worker/worker/FlowTaskWorker.ts index 3bdc28b00..9dbe2a70d 100644 --- a/packages/sequencer/src/worker/worker/FlowTaskWorker.ts +++ b/packages/sequencer/src/worker/worker/FlowTaskWorker.ts @@ -1,9 +1,10 @@ import { log } from "@proto-kit/common"; -import { Closeable, TaskQueue } from "../queue/TaskQueue"; +import { TaskQueue } from "../queue/TaskQueue"; import { Task, TaskPayload } from "../flow/Task"; import { AbstractStartupTask } from "../flow/AbstractStartupTask"; import { UnpreparingTask } from "../flow/UnpreparingTask"; +import { Closeable } from "../../sequencer/builder/Closeable"; const errors = { notComputable: (name: string) => diff --git a/packages/sequencer/src/worker/worker/LocalTaskWorkerModule.ts b/packages/sequencer/src/worker/worker/LocalTaskWorkerModule.ts index 46c545482..cc3f5910e 100644 --- a/packages/sequencer/src/worker/worker/LocalTaskWorkerModule.ts +++ b/packages/sequencer/src/worker/worker/LocalTaskWorkerModule.ts @@ -7,6 +7,8 @@ import { ModulesRecord, NoConfig, Presets, + ResolvableModules, + StringKeyOf, TypedClass, } from "@proto-kit/common"; import { ReturnType } from "@proto-kit/protocol"; @@ -29,6 +31,7 @@ import { StateTransitionTask, } from "../../protocol/production/tasks/StateTransitionTask"; import { CircuitCompilerTask } from "../../protocol/production/tasks/CircuitCompilerTask"; +import { closeable } from "../../sequencer/builder/Closeable"; import { FlowTaskWorker } from "./FlowTaskWorker"; import { TaskWorkerModule } from "./TaskWorkerModule"; @@ -53,6 +56,7 @@ type LocalTaskWorkerModuleEvents = { ready: [boolean] }; * cloud workers. */ @sequencerModule() +@closeable() export class LocalTaskWorkerModule extends ModuleContainer implements @@ -63,6 +67,10 @@ export class LocalTaskWorkerModule public containerEvents = new EventEmitter(); + private worker?: FlowTaskWorker< + InstanceType[StringKeyOf]>[] + > = undefined; + public static from( modules: Tasks ): TypedClass> { @@ -103,6 +111,8 @@ export class LocalTaskWorkerModule }); const worker = new FlowTaskWorker(this.taskQueue(), [...tasks]); + this.worker = worker; + await worker.start(); void worker @@ -114,6 +124,12 @@ export class LocalTaskWorkerModule log.error("Error occurring waiting for the ready event", e); }); } + + public async close() { + if (this.worker !== undefined) { + await this.worker.close(); + } + } } export class VanillaTaskWorkerModules { diff --git a/packages/sequencer/src/worker/worker/startup/WorkerRegistrationFlow.ts b/packages/sequencer/src/worker/worker/startup/WorkerRegistrationFlow.ts index 17108ff0f..998158755 100644 --- a/packages/sequencer/src/worker/worker/startup/WorkerRegistrationFlow.ts +++ b/packages/sequencer/src/worker/worker/startup/WorkerRegistrationFlow.ts @@ -1,7 +1,7 @@ import { injectable } from "tsyringe"; import { log } from "@proto-kit/common"; -import { Closeable } from "../../queue/TaskQueue"; +import { Closeable } from "../../../sequencer/builder/Closeable"; import { FlowCreator } from "../../flow/Flow"; import { diff --git a/packages/sequencer/test-integration/workers/ChildProcessWorker.ts b/packages/sequencer/test-integration/workers/ChildProcessWorker.ts index 1e2614694..de54b5d4b 100644 --- a/packages/sequencer/test-integration/workers/ChildProcessWorker.ts +++ b/packages/sequencer/test-integration/workers/ChildProcessWorker.ts @@ -26,6 +26,7 @@ export class ChildProcessWorker { } kill() { - this?.process?.kill(); + this.process!.kill("SIGKILL"); + console.log("Killed", this.process!.killed); } } diff --git a/packages/sequencer/test-integration/workers/workers-proven.test.ts b/packages/sequencer/test-integration/workers/workers-proven.test.ts index 16f60586f..8a971093c 100644 --- a/packages/sequencer/test-integration/workers/workers-proven.test.ts +++ b/packages/sequencer/test-integration/workers/workers-proven.test.ts @@ -1,16 +1,10 @@ import "reflect-metadata"; -import { expectDefined, log, sleep } from "@proto-kit/common"; -import { AppChain } from "@proto-kit/sdk"; import { container } from "tsyringe"; import { PrivateKey, UInt64 } from "o1js"; -import { BlockTestService } from "../../test/integration/services/BlockTestService"; +import { expectDefined, log } from "@proto-kit/common"; +import { AppChain } from "@proto-kit/sdk"; import { BullQueue } from "@proto-kit/deployment"; -import { - BullConfig, - protocolClass, - runtimeClass, - runtimeProtocolConfig, -} from "./modules"; + import { BatchProducerModule, BlockProducerModule, @@ -21,7 +15,15 @@ import { Sequencer, SequencerStartupModule, } from "../../src"; +import { BlockTestService } from "../../test/integration/services/BlockTestService"; import { ConstantFeeStrategy } from "../../src/protocol/baselayer/fees/ConstantFeeStrategy"; + +import { + BullConfig, + protocolClass, + runtimeClass, + runtimeProtocolConfig, +} from "./modules"; import { ChildProcessWorker } from "./ChildProcessWorker"; const timeout = 300000; @@ -41,8 +43,10 @@ describe("worker-proven", () => { worker.start(true); }); - afterAll(() => { + afterAll(async () => { worker.kill(); + + await appChain.close(); }); it( diff --git a/packages/sequencer/test/sequencer/executor/Sequencer.test.ts b/packages/sequencer/test/sequencer/executor/Sequencer.test.ts new file mode 100644 index 000000000..e1bfe4ea1 --- /dev/null +++ b/packages/sequencer/test/sequencer/executor/Sequencer.test.ts @@ -0,0 +1,69 @@ +import "reflect-metadata"; +import { DependencyFactory, noop, sleep } from "@proto-kit/common"; +import { jest } from "@jest/globals"; +import { container } from "tsyringe"; + +import { + Closeable, + closeable, + Sequencer, + sequencerModule, + SequencerModule, +} from "../../../src"; + +describe("Sequencer close", () => { + it("should close all services", async () => { + const spyFn = jest.fn<() => void>(); + + @closeable() + @sequencerModule() + class CloseableModule extends SequencerModule implements Closeable { + public async start(): Promise { + noop(); + } + + public async close() { + await sleep(200); + spyFn.call(undefined); + } + } + + @sequencerModule() + class DependencyFactoryModule + extends SequencerModule + implements DependencyFactory + { + public async start(): Promise { + noop(); + } + + dependencies() { + return { + Dep2: { + useClass: CloseableModule, + }, + }; + } + } + + const sequencer = new (Sequencer.from({ + modules: { + Foo: CloseableModule, + Bar: CloseableModule, + D: DependencyFactoryModule, + }, + }))(); + sequencer.create(() => container.createChildContainer()); + sequencer.configure({ + Foo: {}, + Bar: {}, + D: {}, + }); + + sequencer.resolve("D"); + + await sequencer.close(); + + expect(spyFn).toHaveBeenCalledTimes(3); + }); +}); diff --git a/packages/stack/test/graphql/graphql.test.ts b/packages/stack/test/graphql/graphql.test.ts index fa1c18603..c80a33095 100644 --- a/packages/stack/test/graphql/graphql.test.ts +++ b/packages/stack/test/graphql/graphql.test.ts @@ -114,7 +114,7 @@ describe("graphql client test", () => { }, 20_000); afterAll(async () => { - server.sequencer.resolveOrFail("GraphqlServer", GraphqlServer).close(); + await server.sequencer.close(); }, 20_000); it("should retrieve state", async () => {