From 472f555244018f046e5fb4892567b534be080745 Mon Sep 17 00:00:00 2001 From: Jared Prather Date: Mon, 26 Aug 2024 11:14:49 -0500 Subject: [PATCH] feat: Adds error handling around actor deactivation Signed-off-by: Jared Prather --- src/actors/runtime/ActorManager.ts | 36 ++++++++++++------- src/actors/runtime/ActorRuntime.ts | 4 +-- src/implementation/Server/HTTPServer/actor.ts | 17 +++++++-- test/unit/actor/actorRuntime.test.ts | 19 ++++++++++ 4 files changed, 59 insertions(+), 17 deletions(-) diff --git a/src/actors/runtime/ActorManager.ts b/src/actors/runtime/ActorManager.ts index 0ffd3df5..732ac246 100644 --- a/src/actors/runtime/ActorManager.ts +++ b/src/actors/runtime/ActorManager.ts @@ -12,6 +12,7 @@ limitations under the License. */ import DaprClient from "../../implementation/Client/DaprClient"; +import { Logger } from "../../logger/Logger"; import Class from "../../types/Class"; import ActorId from "../ActorId"; import AbstractActor from "./AbstractActor"; @@ -23,11 +24,17 @@ import BufferSerializer from "./BufferSerializer"; * The Actor Manager manages actor objects of a specific actor type */ const REMINDER_METHOD_NAME = "receiveReminder"; // the callback method name for the reminder +export enum DeactivateResult { + Success, + ActorDoesNotExist, + Error, +} export default class ActorManager { readonly actorCls: Class; readonly daprClient: DaprClient; readonly serializer: BufferSerializer = new BufferSerializer(); + readonly logger: Logger; actors: Map; @@ -39,6 +46,7 @@ export default class ActorManager { this.daprClient = daprClient; this.actorCls = actorCls; + this.logger = new Logger("Actors", "ActorManager", daprClient.options.logger); this.actors = new Map(); // @todo: we need to make sure race condition cannot happen when accessing the active actors @@ -71,20 +79,22 @@ export default class ActorManager { this.actors.set(actorId.getId(), actor); } - async deactivateActor(actorId: ActorId): Promise { - if (!this.actors.has(actorId.getId())) { - throw new Error( - JSON.stringify({ - error: "ACTOR_NOT_ACTIVATED", - errorMsg: `The actor ${actorId.getId()} was not activated`, - }), - ); + async deactivateActor(actorId: ActorId): Promise { + if (this.actors.has(actorId.getId())) { + try { + const actor = await this.getActiveActor(actorId); + await actor.onDeactivateInternal(); + return DeactivateResult.Success; + } catch (error) { + this.logger.error("Error encountered deactivating actor"); + } finally { + this.actors.delete(actorId.getId()); + } + return DeactivateResult.Error; + } else { + this.logger.warn(`The actor ${actorId.getId()} was not activated`); + return DeactivateResult.ActorDoesNotExist; } - - const actor = await this.getActiveActor(actorId); - await actor.onDeactivateInternal(); - - this.actors.delete(actorId.getId()); } async getActiveActor(actorId: ActorId): Promise { diff --git a/src/actors/runtime/ActorRuntime.ts b/src/actors/runtime/ActorRuntime.ts index f3974c53..bde63d00 100644 --- a/src/actors/runtime/ActorRuntime.ts +++ b/src/actors/runtime/ActorRuntime.ts @@ -17,7 +17,7 @@ import { ActorRuntimeOptions } from "../../types/actors/ActorRuntimeOptions"; import Class from "../../types/Class"; import ActorId from "../ActorId"; import AbstractActor from "./AbstractActor"; -import ActorManager from "./ActorManager"; +import ActorManager, { DeactivateResult } from "./ActorManager"; /** * Creates instances of "Actor" and activates and deactivates "Actor" @@ -141,7 +141,7 @@ export default class ActorRuntime { return await manager.fireTimer(actorIdObj, name, requestBody); } - async deactivate(actorTypeName: string, actorId: string): Promise { + async deactivate(actorTypeName: string, actorId: string): Promise { const actorIdObj = new ActorId(actorId); const manager = this.getActorManager(actorTypeName); return await manager.deactivateActor(actorIdObj); diff --git a/src/implementation/Server/HTTPServer/actor.ts b/src/implementation/Server/HTTPServer/actor.ts index a960e2d2..b1a2589c 100644 --- a/src/implementation/Server/HTTPServer/actor.ts +++ b/src/implementation/Server/HTTPServer/actor.ts @@ -23,6 +23,7 @@ import { DaprClient } from "../../.."; import { Logger } from "../../../logger/Logger"; import { getRegisteredActorResponse } from "../../../utils/Actors.util"; import HttpStatusCode from "../../../enum/HttpStatusCode.enum"; +import { DeactivateResult } from "../../../actors/runtime/ActorManager"; // https://docs.dapr.io/reference/api/bindings_api/ export default class HTTPServerActor implements IServerActor { @@ -89,8 +90,20 @@ export default class HTTPServerActor implements IServerActor { private async handlerDeactivate(req: IRequest, res: IResponse): Promise { const { actorTypeName, actorId } = req.params; const result = await ActorRuntime.getInstance(this.client.daprClient).deactivate(actorTypeName, actorId); - res.statusCode = HttpStatusCode.OK; - return this.handleResult(res, result); + + switch (result) { + case DeactivateResult.Success: + res.statusCode = HttpStatusCode.OK; + return this.handleResult(res, result); + case DeactivateResult.Error: + res.statusCode = HttpStatusCode.INTERNAL_SERVER_ERROR; + return this.handleResult(res, result); + case DeactivateResult.ActorDoesNotExist: + res.statusCode = HttpStatusCode.NOT_FOUND; + return this.handleResult(res, result); + default: + throw new Error("Unsupported result type received"); + } } private async handlerMethod(req: IRequest, res: IResponse): Promise { diff --git a/test/unit/actor/actorRuntime.test.ts b/test/unit/actor/actorRuntime.test.ts index d8726dc2..18b50e4f 100644 --- a/test/unit/actor/actorRuntime.test.ts +++ b/test/unit/actor/actorRuntime.test.ts @@ -110,6 +110,25 @@ describe("ActorRuntime", () => { expect(res.toString()).toEqual(`Actor said: "Hello World"`); }); + it("should be able to deactivate a actor", async () => { + const actorId = randomUUID(); + + await runtime.registerActor(DemoActorSayImpl); + await runtime.invoke(DemoActorSayImpl.name, actorId, "sayString", Buffer.from("Hello World")); + const result = await runtime.deactivate(DemoActorSayImpl.name, actorId); + + expect(result).toEqual(DeactivateResult.Success); + }); + + it("should not error when calling deactivate on an actor that does not exist", async () => { + const actorId = randomUUID(); + + await runtime.registerActor(DemoActorSayImpl); + const result = await runtime.deactivate(DemoActorSayImpl.name, actorId); + + expect(result).toEqual(DeactivateResult.ActorDoesNotExist); + }); + it("should receive an error if the actor method does not exist", async () => { const actorId = randomUUID();