Skip to content

Commit

Permalink
Implemented TryGetReminder method using a ConditionalValue since `o…
Browse files Browse the repository at this point in the history
…ut` parameters in async methods are not supported per https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/async#return-types

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
  • Loading branch information
WhitWaldo committed Feb 22, 2025
1 parent ab77d25 commit 03ea0da
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 0 deletions.
11 changes: 11 additions & 0 deletions src/Dapr.Actors/Runtime/Actor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,17 @@ protected async Task<IActorReminder> GetReminderAsync(string reminderName)
return await this.Host.TimerManager.GetReminderAsync(new ActorReminderToken(this.actorTypeName, this.Id, reminderName));
}

/// <summary>
/// Attempts to get a reminder previously registered using <see cref="Dapr.Actors.Runtime.Actor.RegisterReminderAsync(ActorReminderOptions)"/>.
/// </summary>
/// <param name="reminderName">The name of the reminder to attempt to get.</param>
/// <returns>A <see cref="ConditionalValue{TValue}"/> that reflects whether the reminder could be returned or not in the <c>HasValue</c> property, and if it does, the value in the <c>HasValue</c> property.</returns>
protected async Task<ConditionalValue<IActorReminder>> TryGetReminderAsync(string reminderName)
{
var reminder = await this.GetReminderAsync(reminderName);
return reminder == null ? new ConditionalValue<IActorReminder>() : new ConditionalValue<IActorReminder>(true, reminder);
}

/// <summary>
/// Unregisters a reminder previously registered using <see cref="Dapr.Actors.Runtime.Actor.RegisterReminderAsync(ActorReminderOptions)" />.
/// </summary>
Expand Down
79 changes: 79 additions & 0 deletions test/Dapr.Actors.Test/ActorUnitTestTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,77 @@ public async Task ReminderReturnsNullIfNotAvailable()
Assert.Null(retrievedReminder);
}

[Fact]
public async Task TryGetReminderReturnsTrueIfAvailable()
{
var reminders = new List<ActorReminder>();
IActorReminder getReminder = null;

var timerManager = new Mock<ActorTimerManager>(MockBehavior.Strict);
timerManager
.Setup(tm => tm.RegisterReminderAsync(It.IsAny<ActorReminder>()))
.Callback<ActorReminder>(reminder => reminders.Add(reminder))
.Returns(Task.CompletedTask);
timerManager
.Setup(tm => tm.UnregisterReminderAsync(It.IsAny<ActorReminderToken>()))
.Callback<ActorReminderToken>(reminder => reminders.RemoveAll(t => t.Name == reminder.Name))
.Returns(Task.CompletedTask);
timerManager
.Setup(tm => tm.GetReminderAsync(It.IsAny<ActorReminderToken>()))
.Returns(() => Task.FromResult(getReminder));

var host = ActorHost.CreateForTest<CoolTestActor>(new ActorTestOptions(){ TimerManager = timerManager.Object, });
var actor = new CoolTestActor(host);

// Start the reminder
var message = new Message()
{
Text = "Remind me to tape the hockey game tonite.",
};
await actor.StartReminderAsync(message);

var reminder = Assert.Single(reminders);
Assert.Equal("record", reminder.Name);
Assert.Equal(TimeSpan.FromSeconds(5), reminder.Period);
Assert.Equal(TimeSpan.Zero, reminder.DueTime);

var state = JsonSerializer.Deserialize<Message>(reminder.State);
Assert.Equal(message.Text, state.Text);

// Simulate invoking the reminder interface
for (var i = 0; i < 10; i++)
{
await actor.ReceiveReminderAsync(reminder.Name, reminder.State, reminder.DueTime, reminder.Period);
}

getReminder = reminder;
var reminderFromGet = await actor.TryGetReminderAsync();
Assert.True(reminderFromGet.HasValue);
Assert.Equal(reminder, reminderFromGet.Value);

// Stop the reminder
await actor.StopReminderAsync();
Assert.Empty(reminders);
}

[Fact]
public async Task TryGetReminderReturnsFalseIfNotAvailable()
{
var timerManager = new Mock<ActorTimerManager>(MockBehavior.Strict);
timerManager
.Setup(tm => tm.GetReminderAsync(It.IsAny<ActorReminderToken>()))
.Returns(() => Task.FromResult<IActorReminder>(null));

var host = ActorHost.CreateForTest<CoolTestActor>(new ActorTestOptions() { TimerManager = timerManager.Object, });
var actor = new CoolTestActor(host);

//There is no starting reminder, so this should always return null
var retrievedReminder = await actor.TryGetReminderAsync();

Assert.False(retrievedReminder.HasValue);
Assert.Null(retrievedReminder.Value);
}

public interface ICoolTestActor : IActor
{
}
Expand Down Expand Up @@ -175,6 +246,14 @@ public async Task<IActorReminder> GetReminderAsync()
return await this.GetReminderAsync("record");
}

public async Task<ConditionalValue<IActorReminder>> TryGetReminderAsync()
{
var reminder = await this.GetReminderAsync("record");
return reminder == null
? new ConditionalValue<IActorReminder>()
: new ConditionalValue<IActorReminder>(true, reminder);
}

public async Task StopReminderAsync()
{
await this.UnregisterReminderAsync("record");
Expand Down

0 comments on commit 03ea0da

Please sign in to comment.