Skip to content
5 changes: 2 additions & 3 deletions src/Dapr.Actors/DaprHttpInteractor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ HttpRequestMessage RequestFunc()
return this.SendAsync(RequestFunc, relativeUrl, cancellationToken);
}

public async Task<Stream> GetReminderAsync(string actorType, string actorId, string reminderName, CancellationToken cancellationToken = default)
public async Task<HttpResponseMessage> GetReminderAsync(string actorType, string actorId, string reminderName, CancellationToken cancellationToken = default)
{
var relativeUrl = string.Format(CultureInfo.InvariantCulture, Constants.ActorReminderRelativeUrlFormat, actorType, actorId, reminderName);

Expand All @@ -278,8 +278,7 @@ HttpRequestMessage RequestFunc()
return request;
}

var response = await this.SendAsync(RequestFunc, relativeUrl, cancellationToken);
return await response.Content.ReadAsStreamAsync();
return await this.SendAsync(RequestFunc, relativeUrl, cancellationToken);
}

public Task UnregisterReminderAsync(string actorType, string actorId, string reminderName, CancellationToken cancellationToken = default)
Expand Down
6 changes: 4 additions & 2 deletions src/Dapr.Actors/IDaprInteractor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
// limitations under the License.
// ------------------------------------------------------------------------

using System.Net.Http;

namespace Dapr.Actors
{
using System.IO;
Expand Down Expand Up @@ -81,8 +83,8 @@ internal interface IDaprInteractor
/// <param name="actorId">ActorId.</param>
/// <param name="reminderName">Name of reminder to unregister.</param>
/// <param name="cancellationToken">Cancels the operation.</param>
/// <returns>A <see cref="Task"/> representing the result of the asynchronous operation.</returns>
Task<Stream> GetReminderAsync(string actorType, string actorId, string reminderName, CancellationToken cancellationToken = default);
/// <returns>A <see cref="Task"/> containing the response of the asynchronous HTTP operation.</returns>
Task<HttpResponseMessage> GetReminderAsync(string actorType, string actorId, string reminderName, CancellationToken cancellationToken = default);

/// <summary>
/// Unregisters a reminder.
Expand Down
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
12 changes: 9 additions & 3 deletions src/Dapr.Actors/Runtime/DefaultActorTimerManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using System.Text.Json;
using System.Threading.Tasks;
using System.IO;
using Grpc.Core;

namespace Dapr.Actors.Runtime
{
Expand Down Expand Up @@ -45,9 +46,14 @@ public override async Task<IActorReminder> GetReminderAsync(ActorReminderToken t
throw new ArgumentNullException(nameof(token));
}

var responseStream = await this.interactor.GetReminderAsync(token.ActorType, token.ActorId.ToString(), token.Name);
var reminder = await DeserializeReminderAsync(responseStream, token);
return reminder;
var response = await this.interactor.GetReminderAsync(token.ActorType, token.ActorId.ToString(), token.Name);
if ((int)response.StatusCode == 500)
{
return null;
}

var responseStream = await response.Content.ReadAsStreamAsync();
return await DeserializeReminderAsync(responseStream, token);
}

public override async Task UnregisterReminderAsync(ActorReminderToken reminder)
Expand Down
97 changes: 96 additions & 1 deletion test/Dapr.Actors.Test/ActorUnitTestTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public async Task CanTestStartingAndStoppingTimer()
}

[Fact]
public async Task CanTestStartingAndStoppinReminder()
public async Task CanTestStartingAndStoppingReminder()
{
var reminders = new List<ActorReminder>();
IActorReminder getReminder = null;
Expand Down Expand Up @@ -120,6 +120,93 @@ public async Task CanTestStartingAndStoppinReminder()
Assert.Empty(reminders);
}

[Fact]
public async Task ReminderReturnsNullIfNotAvailable()
{
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.GetReminderAsync();
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 @@ -159,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
Loading
Loading