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
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,41 @@ protected <T> Mono<Void> registerReminder(
T state,
Duration dueTime,
Duration period) {
return this.registerReminder(
reminderName,
state,
ActorReminderOptions.builder().setDueTime(dueTime).setPeriod(period).build());
}

/**
* Registers a reminder for this Actor.
*
* <p>Unlike {@link #registerReminder(String, Object, Duration, Duration)}, every option is optional and the
* reminder can be limited by a number of repetitions and/or a TTL.</p>
*
* @param reminderName Name of the reminder.
* @param state State to be send along with reminder triggers.
* @param options Schedule of the reminder (due time, period, repetitions and TTL).
* @param <T> Type of the state object.
* @return Asynchronous void response.
*/
protected <T> Mono<Void> registerReminder(
String reminderName,
T state,
ActorReminderOptions options) {
if (options == null) {
throw new IllegalArgumentException("Reminder options are required.");
}

try {
byte[] data = this.actorRuntimeContext.getObjectSerializer().serialize(state);
ActorReminderParams params = new ActorReminderParams(data, dueTime, period);
ActorReminderParams params = new ActorReminderParams(
data,
options.getDueTime(),
options.getPeriod(),
options.getRepetitions(),
options.getTtl(),
null);
return this.actorRuntimeContext.getDaprClient().registerReminder(
this.actorRuntimeContext.getActorTypeInformation().getName(),
this.id.toString(),
Expand Down Expand Up @@ -151,6 +183,31 @@ protected <T> Mono<String> registerActorTimer(
T state,
Duration dueTime,
Duration period) {
return this.registerActorTimer(
timerName,
callback,
state,
ActorTimerOptions.builder().setDueTime(dueTime).setPeriod(period).build());
}

/**
* Registers a Timer for the actor. A timer name is autogenerated by the runtime to keep track of it.
*
* <p>Unlike {@link #registerActorTimer(String, String, Object, Duration, Duration)}, every option is optional and
* the timer can be limited by a number of repetitions and/or a TTL.</p>
*
* @param timerName Name of the timer, unique per Actor (auto-generated if null).
* @param callback Name of the method to be called.
* @param state State to be passed it to the method when timer triggers.
* @param options Schedule of the timer (due time, period, repetitions and TTL).
* @param <T> Type for the state to be passed in to timer.
* @return Asynchronous result with timer's name.
*/
protected <T> Mono<String> registerActorTimer(
String timerName,
String callback,
T state,
ActorTimerOptions options) {
try {
if ((callback == null) || callback.isEmpty()) {
throw new IllegalArgumentException("Timer requires a callback function.");
Expand All @@ -161,8 +218,18 @@ protected <T> Mono<String> registerActorTimer(
name = String.format("%s_Timer_%s", this.id.toString(), UUID.randomUUID().toString());
}

if (options == null) {
throw new IllegalArgumentException("Timer options are required.");
}

byte[] data = this.actorRuntimeContext.getObjectSerializer().serialize(state);
ActorTimerParams actorTimer = new ActorTimerParams(callback, data, dueTime, period);
ActorTimerParams actorTimer = new ActorTimerParams(
callback,
data,
options.getDueTime(),
options.getPeriod(),
options.getRepetitions(),
options.getTtl());

return this.actorRuntimeContext.getDaprClient().registerTimer(
this.actorRuntimeContext.getActorTypeInformation().getName(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,7 @@ private byte[] serialize(ActorTimerParams timer) throws IOException {
try (ByteArrayOutputStream writer = new ByteArrayOutputStream()) {
JsonGenerator generator = JSON_FACTORY.createGenerator(writer);
generator.writeStartObject();
generator.writeStringField("dueTime", DurationUtils.convertDurationToDaprFormat(timer.getDueTime()));
generator.writeStringField("period", DurationUtils.convertDurationToDaprFormat(timer.getPeriod()));
writeSchedule(generator, timer.getDueTime(), timer.getPeriod(), timer.getRepetitions(), timer.getTtl());
generator.writeStringField("callback", timer.getCallback());
if (timer.getData() != null) {
generator.writeBinaryField("data", timer.getData());
Expand All @@ -108,8 +107,8 @@ private byte[] serialize(ActorReminderParams reminder) throws IOException {
JsonGenerator generator = JSON_FACTORY.createGenerator(writer);
generator.setCodec(OBJECT_MAPPER);
generator.writeStartObject();
generator.writeStringField("dueTime", DurationUtils.convertDurationToDaprFormat(reminder.getDueTime()));
generator.writeStringField("period", DurationUtils.convertDurationToDaprFormat(reminder.getPeriod()));
writeSchedule(
generator, reminder.getDueTime(), reminder.getPeriod(), reminder.getRepetitions(), reminder.getTtl());
if (reminder.getData() != null) {
generator.writeBinaryField("data", reminder.getData());
}
Expand Down Expand Up @@ -236,9 +235,11 @@ private ActorTimerParams deserializeActorTimer(byte[] value) throws IOException
String callback = node.get("callback").asText();
Duration dueTime = extractDurationOrNull(node, "dueTime");
Duration period = extractDurationOrNull(node, "period");
Integer repetitions = extractRepetitionsOrNull(node);
Duration ttl = extractDurationOrNull(node, "ttl");
byte[] data = node.get("data") != null ? node.get("data").binaryValue() : null;

return new ActorTimerParams(callback, data, dueTime, period);
return new ActorTimerParams(callback, data, dueTime, period, repetitions, ttl);
}

/**
Expand All @@ -256,6 +257,8 @@ private ActorReminderParams deserializeActorReminder(byte[] value) throws IOExce
JsonNode node = OBJECT_MAPPER.readTree(value);
Duration dueTime = extractDurationOrNull(node, "dueTime");
Duration period = extractDurationOrNull(node, "period");
Integer repetitions = extractRepetitionsOrNull(node);
Duration ttl = extractDurationOrNull(node, "ttl");
byte[] data = node.get("data") != null ? node.get("data").binaryValue() : null;

// Handle failure policy if present
Expand All @@ -266,7 +269,7 @@ private ActorReminderParams deserializeActorReminder(byte[] value) throws IOExce
}


return new ActorReminderParams(data, dueTime, period, failurePolicy);
return new ActorReminderParams(data, dueTime, period, repetitions, ttl, failurePolicy);
}

/**
Expand All @@ -284,4 +287,46 @@ private static Duration extractDurationOrNull(JsonNode node, String name) {

return DurationUtils.convertDurationFromDaprFormat(valueNode.asText());
}

/**
* Extracts the repetitions from the period (e.g. R5/PT10S) or null.
*
* @param node Node that contains the period attribute.
* @return Repetitions or null.
*/
private static Integer extractRepetitionsOrNull(JsonNode node) {
JsonNode valueNode = node.get("period");
if (valueNode == null) {
return null;
}

return ActorScheduleUtils.parseRepetitions(valueNode.asText());
}

/**
* Writes the schedule fields of a timer or reminder, omitting the ones that are not set.
*
* @param generator JSON generator.
* @param dueTime Due time or null.
* @param period Period or null.
* @param repetitions Repetitions or null.
* @param ttl TTL or null.
* @throws IOException If cannot generate JSON.
*/
private static void writeSchedule(
JsonGenerator generator,
Duration dueTime,
Duration period,
Integer repetitions,
Duration ttl) throws IOException {
if (dueTime != null) {
generator.writeStringField("dueTime", ActorScheduleUtils.formatDuration(dueTime));
}
if (period != null) {
generator.writeStringField("period", ActorScheduleUtils.formatPeriod(period, repetitions));
}
if (ttl != null) {
generator.writeStringField("ttl", ActorScheduleUtils.formatDuration(ttl));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/*
* Copyright 2026 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.
*/

package io.dapr.actors.runtime;

import java.time.Duration;

/**
* Options to register an actor reminder.
*
* <p>All fields are optional: without a due time the reminder fires immediately, without a period it fires only
* once, without repetitions it fires until unregistered (or until the TTL expires) and without a TTL it never
* expires. A TTL requires a positive period.</p>
*/
public final class ActorReminderOptions {

private final Duration dueTime;

private final Duration period;

private final Integer repetitions;

private final Duration ttl;

private ActorReminderOptions(Builder builder) {
this.dueTime = builder.dueTime;
this.period = builder.period;
this.repetitions = builder.repetitions;
this.ttl = builder.ttl;
}

/**
* Creates a new builder.
*
* @return Builder for reminder options.
*/
public static Builder builder() {
return new Builder();
}

/**
* Gets the time the reminder is due for the 1st time.
*
* @return Due time or null.
*/
public Duration getDueTime() {
return dueTime;
}

/**
* Gets the interval between triggers.
*
* @return Period or null.
*/
public Duration getPeriod() {
return period;
}

/**
* Gets the number of times the reminder is triggered.
*
* @return Repetitions or null.
*/
public Integer getRepetitions() {
return repetitions;
}

/**
* Gets the time after which the reminder expires.
*
* @return TTL or null.
*/
public Duration getTtl() {
return ttl;
}

/**
* Builder for {@link ActorReminderOptions}.
*/
public static final class Builder {

private Duration dueTime;

private Duration period;

private Integer repetitions;

private Duration ttl;

private Builder() {
}

/**
* Sets the time the reminder is due for the 1st time.
*
* @param dueTime Due time.
* @return This builder.
*/
public Builder setDueTime(Duration dueTime) {
this.dueTime = dueTime;
return this;
}

/**
* Sets the interval between triggers.
*
* @param period Period.
* @return This builder.
*/
public Builder setPeriod(Duration period) {
this.period = period;
return this;
}

/**
* Sets the number of times the reminder is triggered, requires a period in whole seconds.
*
* @param repetitions Repetitions.
* @return This builder.
*/
public Builder setRepetitions(Integer repetitions) {
this.repetitions = repetitions;
return this;
}

/**
* Sets the time after which the reminder expires, requires a positive period.
*
* @param ttl TTL.
* @return This builder.
*/
public Builder setTtl(Duration ttl) {
this.ttl = ttl;
return this;
}

/**
* Builds the reminder options.
*
* @return Reminder options.
*/
public ActorReminderOptions build() {
return new ActorReminderOptions(this);
}
}
}
Loading
Loading