Skip to content

Commit 472f555

Browse files
committed
feat: Adds error handling around actor deactivation
Signed-off-by: Jared Prather <jared.prather@chartspan.com>
1 parent 76866c8 commit 472f555

File tree

4 files changed

+59
-17
lines changed

4 files changed

+59
-17
lines changed

src/actors/runtime/ActorManager.ts

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ limitations under the License.
1212
*/
1313

1414
import DaprClient from "../../implementation/Client/DaprClient";
15+
import { Logger } from "../../logger/Logger";
1516
import Class from "../../types/Class";
1617
import ActorId from "../ActorId";
1718
import AbstractActor from "./AbstractActor";
@@ -23,11 +24,17 @@ import BufferSerializer from "./BufferSerializer";
2324
* The Actor Manager manages actor objects of a specific actor type
2425
*/
2526
const REMINDER_METHOD_NAME = "receiveReminder"; // the callback method name for the reminder
27+
export enum DeactivateResult {
28+
Success,
29+
ActorDoesNotExist,
30+
Error,
31+
}
2632

2733
export default class ActorManager<T extends AbstractActor> {
2834
readonly actorCls: Class<T>;
2935
readonly daprClient: DaprClient;
3036
readonly serializer: BufferSerializer = new BufferSerializer();
37+
readonly logger: Logger;
3138

3239
actors: Map<string, T>;
3340

@@ -39,6 +46,7 @@ export default class ActorManager<T extends AbstractActor> {
3946
this.daprClient = daprClient;
4047
this.actorCls = actorCls;
4148

49+
this.logger = new Logger("Actors", "ActorManager", daprClient.options.logger);
4250
this.actors = new Map<string, T>();
4351

4452
// @todo: we need to make sure race condition cannot happen when accessing the active actors
@@ -71,20 +79,22 @@ export default class ActorManager<T extends AbstractActor> {
7179
this.actors.set(actorId.getId(), actor);
7280
}
7381

74-
async deactivateActor(actorId: ActorId): Promise<void> {
75-
if (!this.actors.has(actorId.getId())) {
76-
throw new Error(
77-
JSON.stringify({
78-
error: "ACTOR_NOT_ACTIVATED",
79-
errorMsg: `The actor ${actorId.getId()} was not activated`,
80-
}),
81-
);
82+
async deactivateActor(actorId: ActorId): Promise<DeactivateResult> {
83+
if (this.actors.has(actorId.getId())) {
84+
try {
85+
const actor = await this.getActiveActor(actorId);
86+
await actor.onDeactivateInternal();
87+
return DeactivateResult.Success;
88+
} catch (error) {
89+
this.logger.error("Error encountered deactivating actor");
90+
} finally {
91+
this.actors.delete(actorId.getId());
92+
}
93+
return DeactivateResult.Error;
94+
} else {
95+
this.logger.warn(`The actor ${actorId.getId()} was not activated`);
96+
return DeactivateResult.ActorDoesNotExist;
8297
}
83-
84-
const actor = await this.getActiveActor(actorId);
85-
await actor.onDeactivateInternal();
86-
87-
this.actors.delete(actorId.getId());
8898
}
8999

90100
async getActiveActor(actorId: ActorId): Promise<T> {

src/actors/runtime/ActorRuntime.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { ActorRuntimeOptions } from "../../types/actors/ActorRuntimeOptions";
1717
import Class from "../../types/Class";
1818
import ActorId from "../ActorId";
1919
import AbstractActor from "./AbstractActor";
20-
import ActorManager from "./ActorManager";
20+
import ActorManager, { DeactivateResult } from "./ActorManager";
2121

2222
/**
2323
* Creates instances of "Actor" and activates and deactivates "Actor"
@@ -141,7 +141,7 @@ export default class ActorRuntime {
141141
return await manager.fireTimer(actorIdObj, name, requestBody);
142142
}
143143

144-
async deactivate(actorTypeName: string, actorId: string): Promise<void> {
144+
async deactivate(actorTypeName: string, actorId: string): Promise<DeactivateResult> {
145145
const actorIdObj = new ActorId(actorId);
146146
const manager = this.getActorManager(actorTypeName);
147147
return await manager.deactivateActor(actorIdObj);

src/implementation/Server/HTTPServer/actor.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { DaprClient } from "../../..";
2323
import { Logger } from "../../../logger/Logger";
2424
import { getRegisteredActorResponse } from "../../../utils/Actors.util";
2525
import HttpStatusCode from "../../../enum/HttpStatusCode.enum";
26+
import { DeactivateResult } from "../../../actors/runtime/ActorManager";
2627

2728
// https://docs.dapr.io/reference/api/bindings_api/
2829
export default class HTTPServerActor implements IServerActor {
@@ -89,8 +90,20 @@ export default class HTTPServerActor implements IServerActor {
8990
private async handlerDeactivate(req: IRequest, res: IResponse): Promise<IResponse> {
9091
const { actorTypeName, actorId } = req.params;
9192
const result = await ActorRuntime.getInstance(this.client.daprClient).deactivate(actorTypeName, actorId);
92-
res.statusCode = HttpStatusCode.OK;
93-
return this.handleResult(res, result);
93+
94+
switch (result) {
95+
case DeactivateResult.Success:
96+
res.statusCode = HttpStatusCode.OK;
97+
return this.handleResult(res, result);
98+
case DeactivateResult.Error:
99+
res.statusCode = HttpStatusCode.INTERNAL_SERVER_ERROR;
100+
return this.handleResult(res, result);
101+
case DeactivateResult.ActorDoesNotExist:
102+
res.statusCode = HttpStatusCode.NOT_FOUND;
103+
return this.handleResult(res, result);
104+
default:
105+
throw new Error("Unsupported result type received");
106+
}
94107
}
95108

96109
private async handlerMethod(req: IRequest, res: IResponse): Promise<IResponse> {

test/unit/actor/actorRuntime.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,25 @@ describe("ActorRuntime", () => {
110110
expect(res.toString()).toEqual(`Actor said: "Hello World"`);
111111
});
112112

113+
it("should be able to deactivate a actor", async () => {
114+
const actorId = randomUUID();
115+
116+
await runtime.registerActor(DemoActorSayImpl);
117+
await runtime.invoke(DemoActorSayImpl.name, actorId, "sayString", Buffer.from("Hello World"));
118+
const result = await runtime.deactivate(DemoActorSayImpl.name, actorId);
119+
120+
expect(result).toEqual(DeactivateResult.Success);
121+
});
122+
123+
it("should not error when calling deactivate on an actor that does not exist", async () => {
124+
const actorId = randomUUID();
125+
126+
await runtime.registerActor(DemoActorSayImpl);
127+
const result = await runtime.deactivate(DemoActorSayImpl.name, actorId);
128+
129+
expect(result).toEqual(DeactivateResult.ActorDoesNotExist);
130+
});
131+
113132
it("should receive an error if the actor method does not exist", async () => {
114133
const actorId = randomUUID();
115134

0 commit comments

Comments
 (0)