Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion daprdocs/content/en/js-sdk-docs/js-actors/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,8 @@ async function start()
await client.actor.timerCreate(ParkingSensorImpl.name, `actor-id`, `timer-id`, {
callback: "method-to-excute-on-actor",
dueTime: Temporal.Duration.from({ seconds: 2 }),
period: Temporal.Duration.from({ seconds: 1 })
period: Temporal.Duration.from({ seconds: 1 }),
ttl: Temporal.Duration.from({ seconds: 1 }),
});

// Delete the timer
Expand All @@ -184,6 +185,7 @@ async function start()
await client.actor.reminderCreate(DemoActorImpl.name, `actor-id`, `timer-id`, {
dueTime: Temporal.Duration.from({ seconds: 2 }),
period: Temporal.Duration.from({ seconds: 1 }),
ttl: Temporal.Duration.from({ seconds: 1 }),
data: 100
});

Expand Down
8 changes: 8 additions & 0 deletions src/actors/client/ActorClient/ActorClientGRPC.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,10 @@ export default class ActorClientGRPC implements IClientActor {
msgService.setDueTime(reminder.dueTime.toString());
}

if (reminder.ttl) {
msgService.setTtl(reminder.ttl.toString());
}

return new Promise((resolve, reject) => {
const client = this.client.getClient();
client.registerActorReminder(msgService, (err, _res) => {
Expand Down Expand Up @@ -191,6 +195,10 @@ export default class ActorClientGRPC implements IClientActor {
msgService.setDueTime(timer.dueTime.toString());
}

if (timer.ttl) {
msgService.setTtl(timer.ttl.toString());
}

return new Promise((resolve, reject) => {
const client = this.client.getClient();
client.registerActorTimer(msgService, (err, _res) => {
Expand Down
2 changes: 2 additions & 0 deletions src/actors/client/ActorClient/ActorClientHTTP.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export default class ActorClientHTTP implements IClientActor {
body: JSON.stringify({
period: reminder.period.toString().toLocaleLowerCase().replace('pt', ''),
dueTime: reminder?.dueTime?.toString()?.toLocaleLowerCase().replace('pt', ''),
ttl: reminder?.ttl?.toString()?.toLocaleLowerCase().replace('pt', ''),
data: reminder.data
}),
});
Expand All @@ -85,6 +86,7 @@ export default class ActorClientHTTP implements IClientActor {
body: JSON.stringify({
period: timer.period.toString().toLocaleLowerCase().replace('pt', ''),
dueTime: timer?.dueTime?.toString()?.toLocaleLowerCase().replace('pt', ''),
ttl: timer?.ttl?.toString()?.toLocaleLowerCase().replace('pt', ''),
data: timer.data,
callback: timer.callback
}),
Expand Down
7 changes: 5 additions & 2 deletions src/actors/runtime/AbstractActor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,16 @@ export default abstract class AbstractActor {
* @param reminderName name of the reminder
* @param state the state to be send along with the reminder trigger
* @param dueTime due time for the first trigger
* @param ttl time to duration after which the reminder will be expired and deleted
* @param period frequency for the triggers
* @param <Type> Type of the state object
* @return Async void response
*/
async registerActorReminder<_Type>(reminderName: string, dueTime: Temporal.Duration, period: Temporal.Duration, state?: any) {
async registerActorReminder<_Type>(reminderName: string, dueTime: Temporal.Duration, period: Temporal.Duration, ttl?: Temporal.Duration, state?: any) {
await this.actorClient.actor.registerActorReminder(this.actorType, this.id, reminderName, {
period,
dueTime,
ttl,
data: state
});
}
Expand All @@ -96,11 +98,12 @@ export default abstract class AbstractActor {
await this.actorClient.actor.unregisterActorReminder(this.actorType, this.id, reminderName);
}

async registerActorTimer(timerName: string, callback: string, dueTime: Temporal.Duration, period: Temporal.Duration, state?: any) {
async registerActorTimer(timerName: string, callback: string, dueTime: Temporal.Duration, period: Temporal.Duration, ttl?: Temporal.Duration, state?: any) {
// Register the timer in the sidecar
return await this.actorClient.actor.registerActorTimer(this.actorType, this.id, timerName, {
period,
dueTime,
ttl,
data: state,
callback
});
Expand Down
13 changes: 11 additions & 2 deletions src/actors/runtime/ActorReminderData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,20 @@ export default class ActorReminderData {
readonly reminderName: string;
readonly state: string | object | undefined;
readonly dueTime: number;
readonly ttl: number | undefined;
readonly period: number;

/**
* @param reminderName the name of the actor reminder
* @param state the state data passed to receiveReminder callback
* @param dueTime the amount of time to delay before invoking the reminder for the first time
* @param ttl time to duration after which the reminder will be expired and deleted
* @param period the time interval between reminder invocations after the first invocation
*/
constructor(reminderName: string, dueTime: number, period: number, state?: string | object) {
constructor(reminderName: string, dueTime: number, period: number, ttl?: number, state?: string | object) {
this.reminderName = reminderName;
this.dueTime = dueTime;
this.ttl = ttl;
this.period = period;
this.state = state;
}
Expand All @@ -47,6 +50,10 @@ export default class ActorReminderData {
return this.dueTime;
}

getTtl(): number | undefined {
return this.ttl;
}

getPeriod(): number {
return this.period;
}
Expand All @@ -58,6 +65,7 @@ export default class ActorReminderData {
return {
reminderName: this.reminderName,
dueTime: this.dueTime,
ttl: this.ttl,
period: this.period,
data: this.state
}
Expand All @@ -68,10 +76,11 @@ export default class ActorReminderData {

const data = obj?.data;
const dueTime = obj?.dueTime;
const ttl = obj?.ttl;
const period = obj?.period;

const deserializedData = serializer.deserialize(data);

return new ActorReminderData(reminderName, dueTime, period, deserializedData);
return new ActorReminderData(reminderName, dueTime, ttl, period, deserializedData);
}
}
1 change: 1 addition & 0 deletions src/types/ActorReminder.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ export type ActorReminderType = {
period: Temporal.Duration; // e.g. 0h0m9s0ms
dueTime?: Temporal.Duration; // e.g. 1m or 0h0m0s0ms defaults to 0s
data?: any; // the data to pass
ttl?: Temporal.Duration; // e.g. 1m
}
1 change: 1 addition & 0 deletions src/types/ActorTimer.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ export type ActorTimerType = {
period: Temporal.Duration; // e.g. 0h0m9s0ms
dueTime?: Temporal.Duration; // e.g. 1m or 0h0m0s0ms defaults to 0s
data?: any; // the data to pass
ttl?: Temporal.Duration; // e.g. 1m
callback: string; // which method to execute as callback method
}
3 changes: 2 additions & 1 deletion test/actor/DemoActorReminder2Impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ export default class DemoActorReminder2Impl extends AbstractActor implements Dem
counter = 0;

async init(): Promise<string> {
await super.registerActorReminder("my-reminder-name", Temporal.Duration.from({ seconds: 2 }), Temporal.Duration.from({ seconds: 1 }), 123);
await super.registerActorReminder("my-reminder-name", Temporal.Duration.from({ seconds: 2 }), Temporal.Duration.from({ seconds: 2 }),
Temporal.Duration.from({ seconds: 1 }), 123);
return "Actor Initialized";
}

Expand Down
3 changes: 2 additions & 1 deletion test/actor/DemoActorReminderImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ export default class DemoActorReminderImpl extends AbstractActor implements Demo
counter = 0;

async init(): Promise<string> {
await super.registerActorReminder("my-reminder-name", Temporal.Duration.from({ seconds: 2 }), Temporal.Duration.from({ seconds: 1 }), 123);
await super.registerActorReminder("my-reminder-name", Temporal.Duration.from({ seconds: 2 }), Temporal.Duration.from({ seconds: 3 }),
undefined, 123);
return "Actor Initialized";
}

Expand Down
48 changes: 48 additions & 0 deletions test/actor/DemoActorReminderTtlImpl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
Copyright 2022 The Dapr Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import { AbstractActor, Temporal } from "../../src";
import DemoActorReminderInterface from "./DemoActorReminderInterface";

export default class DemoActorReminderTtlImpl extends AbstractActor implements DemoActorReminderInterface {
counter = 0;

async init(): Promise<string> {
await super.registerActorReminder("my-reminder-name",
Temporal.Duration.from({ seconds: 2 }), //dueTime
Temporal.Duration.from({ seconds: 2 }), //period
Temporal.Duration.from({ seconds: 1 }), //ttl
123);
return "Actor Initialized";
}

async count(): Promise<void> {
this.counter++;
}

async getCounter(): Promise<number> {
return this.counter;
}

async removeReminder(): Promise<void> {
return this.unregisterActorReminder("my-reminder-name");
}

/**
* @override
* @param data
*/
async receiveReminder(data: string): Promise<void> {
this.counter += parseInt(data);
}
}
3 changes: 2 additions & 1 deletion test/actor/DemoActorTimerImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ export default class DemoActorTimerImpl extends AbstractActor implements DemoAct
counter = 0;

async init(): Promise<string> {
await super.registerActorTimer("my-timer-name", "countBy", Temporal.Duration.from({ seconds: 2 }), Temporal.Duration.from({ seconds: 1 }), 100);
await super.registerActorTimer("my-timer-name", "countBy", Temporal.Duration.from({ seconds: 2 }), Temporal.Duration.from({ seconds: 1 }),
undefined, 100);
return "Actor Initialized";
}

Expand Down
44 changes: 44 additions & 0 deletions test/actor/DemoActorTimerTtlImpl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
Copyright 2022 The Dapr Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import { AbstractActor, Temporal } from "../../src";
import DemoActorTimerInterface from "./DemoActorTimerInterface";

export default class DemoActorTimerTtlImpl extends AbstractActor implements DemoActorTimerInterface {
counter = 0;

async init(): Promise<string> {
await super.registerActorTimer("my-timer-name", "countBy",
Temporal.Duration.from({ seconds: 2 }), //dueTime
Temporal.Duration.from({ seconds: 2 }), //period
Temporal.Duration.from({ seconds: 3 }), //ttl
100);
return "Actor Initialized";
}

async count(): Promise<void> {
this.counter++;
}

async getCounter(): Promise<number> {
return this.counter;
}

async countBy(amount: string): Promise<void> {
this.counter += parseInt(amount);
}

async removeTimer(): Promise<void> {
return await this.unregisterActorTimer("my-timer-name");
}
}
75 changes: 74 additions & 1 deletion test/e2e/actors.http.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import DemoActorSayImpl from '../actor/DemoActorSayImpl';
import DemoActorSayInterface from '../actor/DemoActorSayInterface';
import DemoActorTimerImpl from '../actor/DemoActorTimerImpl';
import DemoActorTimerInterface from '../actor/DemoActorTimerInterface';
import DemoActorTimerTtlImpl from '../actor/DemoActorTimerTtlImpl';
import DemoActorReminderTtlImpl from '../actor/DemoActorReminderTtlImpl';

const serverHost = "127.0.0.1";
const serverPort = "50001";
Expand Down Expand Up @@ -61,6 +63,8 @@ describe('http/actors', () => {
await server.actor.registerActor(DemoActorReminder2Impl);
await server.actor.registerActor(DemoActorTimerImpl);
await server.actor.registerActor(DemoActorActivateImpl);
await server.actor.registerActor(DemoActorTimerTtlImpl);
await server.actor.registerActor(DemoActorReminderTtlImpl);

// Start server
await server.start(); // Start the general server, this can take a while
Expand Down Expand Up @@ -97,13 +101,15 @@ describe('http/actors', () => {
it('should register actors correctly', async () => {
const actors = await server.actor.getRegisteredActors();

expect(actors.length).toEqual(6);
expect(actors.length).toEqual(8);

expect(actors).toContain(DemoActorCounterImpl.name);
expect(actors).toContain(DemoActorSayImpl.name);
expect(actors).toContain(DemoActorReminderImpl.name);
expect(actors).toContain(DemoActorTimerImpl.name);
expect(actors).toContain(DemoActorActivateImpl.name);
expect(actors).toContain(DemoActorTimerTtlImpl.name);
expect(actors).toContain(DemoActorReminderTtlImpl.name);
});

it('should be able to invoke an actor through a text message', async () => {
Expand Down Expand Up @@ -171,6 +177,45 @@ describe('http/actors', () => {
const res4 = await actor.getCounter();
expect(res4).toEqual(300);
}, 10000);


it('should apply the ttl when it is set (expected execution time > 5s)', async () => {
const builder = new ActorProxyBuilder<DemoActorTimerInterface>(DemoActorTimerTtlImpl, client);
const actor = builder.build(ActorId.createRandomId());

// Activate our actor
await actor.init();

const res0 = await actor.getCounter();
expect(res0).toEqual(0);

// Now we wait for dueTime (2s)
await (new Promise(resolve => setTimeout(resolve, 2000)));

// After that the timer callback will be called
// In our case, the callback increments the count attribute
// the count attribute is +100 due to the passed state
const res1 = await actor.getCounter();
expect(res1).toEqual(100);

// Every 1 second the timer gets called again, so the count attribute should change
// we check this twice to ensure correct calling
await (new Promise(resolve => setTimeout(resolve, 1000)));
const res2 = await actor.getCounter();
expect(res2).toEqual(100);
await (new Promise(resolve => setTimeout(resolve, 1000)));
const res3 = await actor.getCounter();
expect(res3).toEqual(200);

// Stop the timer
await actor.removeTimer();

// We then expect the counter to stop increasing
await (new Promise(resolve => setTimeout(resolve, 1000)));
const res4 = await actor.getCounter();
expect(res4).toEqual(200);

}, 10000);
});

describe('reminders', () => {
Expand Down Expand Up @@ -228,5 +273,33 @@ describe('http/actors', () => {
// Unregister the reminder
await actor.removeReminder();
});

it('should apply the ttl when it is set to a reminder', async () => {
const builder = new ActorProxyBuilder<DemoActorReminderInterface>(DemoActorReminderTtlImpl, client);
const actor = builder.build(ActorId.createRandomId());

// Activate our actor
// this will initialize the reminder to be called
await actor.init();

const res0 = await actor.getCounter();
expect(res0).toEqual(0);

// Now we wait for dueTime (2s)
await NodeJSUtil.sleep(2000);

// After that the reminder callback will be called
// In our case, the callback increments the count attribute
const res1 = await actor.getCounter();
expect(res1).toEqual(123);

await actor.removeReminder();

await (new Promise(resolve => setTimeout(resolve, 2000)));

// Make sure the counter didn't change
const res2 = await actor.getCounter();
expect(res2).toEqual(123);
});
});
});