Skip to content

Commit 19fd403

Browse files
authored
Fix: Actor reminders should return null if not registered
Added more checks to determine if a reminder was returned or not having discovered that the runtime returns a 200 even if there's no reminder registered. (#1476)
1 parent 94dcdfd commit 19fd403

File tree

3 files changed

+114
-83
lines changed

3 files changed

+114
-83
lines changed

src/Dapr.Actors/Runtime/DefaultActorTimerManager.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,24 +90,26 @@ public override async Task UnregisterTimerAsync(ActorTimerToken timer)
9090
await this.interactor.UnregisterTimerAsync(timer.ActorType, timer.ActorId.ToString(), timer.Name);
9191
}
9292

93-
private async ValueTask<string> SerializeReminderAsync(ActorReminder reminder)
93+
private static async ValueTask<string> SerializeReminderAsync(ActorReminder reminder)
9494
{
9595
var info = new ReminderInfo(reminder.State, reminder.DueTime, reminder.Period, reminder.Repetitions,
9696
reminder.Ttl);
9797
return await info.SerializeAsync();
9898
}
9999

100-
private async ValueTask<ActorReminder> DeserializeReminderAsync(Stream stream, ActorReminderToken token)
100+
private static async ValueTask<ActorReminder> DeserializeReminderAsync(Stream stream, ActorReminderToken token)
101101
{
102102
if (stream == null)
103103
{
104104
throw new ArgumentNullException(nameof(stream));
105105
}
106+
106107
var info = await ReminderInfo.DeserializeAsync(stream);
107-
if(info == null)
108+
if (info == null)
108109
{
109110
return null;
110111
}
112+
111113
var reminder = new ActorReminder(token.ActorType, token.ActorId, token.Name, info.Data, info.DueTime,
112114
info.Period);
113115
return reminder;

src/Dapr.Actors/Runtime/ReminderInfo.cs

Lines changed: 90 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -11,99 +11,109 @@
1111
// limitations under the License.
1212
// ------------------------------------------------------------------------
1313

14-
namespace Dapr.Actors.Runtime
14+
#nullable enable
15+
namespace Dapr.Actors.Runtime;
16+
17+
using System;
18+
using System.IO;
19+
using System.Text;
20+
using System.Text.Json;
21+
using System.Threading.Tasks;
22+
23+
// represents the wire format used by Dapr to store reminder info with the runtime
24+
internal class ReminderInfo
1525
{
16-
using System;
17-
using System.IO;
18-
using System.Text;
19-
using System.Text.Json;
20-
using System.Threading.Tasks;
21-
22-
// represents the wire format used by Dapr to store reminder info with the runtime
23-
internal class ReminderInfo
26+
public ReminderInfo(
27+
byte[] data,
28+
TimeSpan dueTime,
29+
TimeSpan period,
30+
int? repetitions = null,
31+
TimeSpan? ttl = null)
2432
{
25-
public ReminderInfo(
26-
byte[] data,
27-
TimeSpan dueTime,
28-
TimeSpan period,
29-
int? repetitions = null,
30-
TimeSpan? ttl = null)
33+
this.Data = data;
34+
this.DueTime = dueTime;
35+
this.Period = period;
36+
this.Ttl = ttl;
37+
this.Repetitions = repetitions;
38+
}
39+
40+
public TimeSpan DueTime { get; private set; }
41+
42+
public TimeSpan Period { get; private set; }
43+
44+
public byte[] Data { get; private set; }
45+
46+
public TimeSpan? Ttl { get; private set; }
47+
48+
public int? Repetitions { get; private set; }
49+
50+
internal static async Task<ReminderInfo?> DeserializeAsync(Stream stream)
51+
{
52+
var json = await JsonSerializer.DeserializeAsync<JsonElement>(stream);
53+
if(json.ValueKind == JsonValueKind.Null)
3154
{
32-
this.Data = data;
33-
this.DueTime = dueTime;
34-
this.Period = period;
35-
this.Ttl = ttl;
36-
this.Repetitions = repetitions;
55+
return null;
3756
}
3857

39-
public TimeSpan DueTime { get; private set; }
58+
var setAnyProperties = false; //Used to determine if anything was actually deserialized
59+
var dueTime = TimeSpan.Zero;
60+
var period = TimeSpan.Zero;
61+
var data = Array.Empty<byte>();
62+
int? repetition = null;
63+
TimeSpan? ttl = null;
64+
if (json.TryGetProperty("dueTime", out var dueTimeProperty))
65+
{
66+
setAnyProperties = true;
67+
var dueTimeString = dueTimeProperty.GetString();
68+
dueTime = ConverterUtils.ConvertTimeSpanFromDaprFormat(dueTimeString);
69+
}
4070

41-
public TimeSpan Period { get; private set; }
71+
if (json.TryGetProperty("period", out var periodProperty))
72+
{
73+
setAnyProperties = true;
74+
var periodString = periodProperty.GetString();
75+
(period, repetition) = ConverterUtils.ConvertTimeSpanValueFromISO8601Format(periodString);
76+
}
4277

43-
public byte[] Data { get; private set; }
78+
if (json.TryGetProperty("data", out var dataProperty) && dataProperty.ValueKind != JsonValueKind.Null)
79+
{
80+
setAnyProperties = true;
81+
data = dataProperty.GetBytesFromBase64();
82+
}
4483

45-
public TimeSpan? Ttl { get; private set; }
46-
47-
public int? Repetitions { get; private set; }
84+
if (json.TryGetProperty("ttl", out var ttlProperty))
85+
{
86+
setAnyProperties = true;
87+
var ttlString = ttlProperty.GetString();
88+
ttl = ConverterUtils.ConvertTimeSpanFromDaprFormat(ttlString);
89+
}
4890

49-
internal static async Task<ReminderInfo> DeserializeAsync(Stream stream)
91+
if (!setAnyProperties)
5092
{
51-
var json = await JsonSerializer.DeserializeAsync<JsonElement>(stream);
52-
if(json.ValueKind == JsonValueKind.Null)
53-
{
54-
return null;
55-
}
56-
57-
var dueTime = default(TimeSpan);
58-
var period = default(TimeSpan);
59-
var data = default(byte[]);
60-
int? repetition = null;
61-
TimeSpan? ttl = null;
62-
if (json.TryGetProperty("dueTime", out var dueTimeProperty))
63-
{
64-
var dueTimeString = dueTimeProperty.GetString();
65-
dueTime = ConverterUtils.ConvertTimeSpanFromDaprFormat(dueTimeString);
66-
}
67-
68-
if (json.TryGetProperty("period", out var periodProperty))
69-
{
70-
var periodString = periodProperty.GetString();
71-
(period, repetition) = ConverterUtils.ConvertTimeSpanValueFromISO8601Format(periodString);
72-
}
73-
74-
if (json.TryGetProperty("data", out var dataProperty) && dataProperty.ValueKind != JsonValueKind.Null)
75-
{
76-
data = dataProperty.GetBytesFromBase64();
77-
}
78-
79-
if (json.TryGetProperty("ttl", out var ttlProperty))
80-
{
81-
var ttlString = ttlProperty.GetString();
82-
ttl = ConverterUtils.ConvertTimeSpanFromDaprFormat(ttlString);
83-
}
84-
85-
return new ReminderInfo(data, dueTime, period, repetition, ttl);
93+
return null; //No properties were ever deserialized, so return null instead of default values
8694
}
8795

88-
internal async ValueTask<string> SerializeAsync()
96+
return new ReminderInfo(data, dueTime, period, repetition, ttl);
97+
}
98+
99+
internal async ValueTask<string> SerializeAsync()
100+
{
101+
using var stream = new MemoryStream();
102+
await using Utf8JsonWriter writer = new Utf8JsonWriter(stream);
103+
104+
writer.WriteStartObject();
105+
writer.WriteString("dueTime", ConverterUtils.ConvertTimeSpanValueInDaprFormat(this.DueTime));
106+
writer.WriteString("period", ConverterUtils.ConvertTimeSpanValueInISO8601Format(
107+
this.Period, this.Repetitions));
108+
writer.WriteBase64String("data", this.Data);
109+
110+
if (Ttl != null)
89111
{
90-
using var stream = new MemoryStream();
91-
using Utf8JsonWriter writer = new Utf8JsonWriter(stream);
92-
93-
writer.WriteStartObject();
94-
writer.WriteString("dueTime", ConverterUtils.ConvertTimeSpanValueInDaprFormat(this.DueTime));
95-
writer.WriteString("period", ConverterUtils.ConvertTimeSpanValueInISO8601Format(
96-
this.Period, this.Repetitions));
97-
writer.WriteBase64String("data", this.Data);
98-
99-
if (Ttl != null)
100-
{
101-
writer.WriteString("ttl", ConverterUtils.ConvertTimeSpanValueInDaprFormat(Ttl));
102-
}
103-
104-
writer.WriteEndObject();
105-
await writer.FlushAsync();
106-
return Encoding.UTF8.GetString(stream.ToArray());
112+
writer.WriteString("ttl", ConverterUtils.ConvertTimeSpanValueInDaprFormat(Ttl));
107113
}
114+
115+
writer.WriteEndObject();
116+
await writer.FlushAsync();
117+
return Encoding.UTF8.GetString(stream.ToArray());
108118
}
109119
}

test/Dapr.Actors.Test/Runtime/DefaultActorTimerManagerTests.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,25 @@ public async Task GetReminderAsync_ReturnsNullWhenUnavailable()
109109
var reminderResult = await defaultActorTimerManager.GetReminderAsync(new ActorReminderToken(actorType, new ActorId(actorId), reminderName));
110110
Assert.Null(reminderResult);
111111
}
112+
113+
[Fact]
114+
public async Task GetReminderAsync_ReturnsNullWhenDeserialziationFails()
115+
{
116+
const string actorId = "123";
117+
const string actorType = "abc";
118+
const string reminderName = "reminderName";
119+
var interactor = new Mock<TestDaprInteractor>();
120+
var response = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("{}") };
121+
122+
interactor
123+
.Setup(d => d.GetReminderAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(),
124+
It.IsAny<CancellationToken>()))
125+
.ReturnsAsync(response);
126+
var defaultActorTimerManager = new DefaultActorTimerManager(interactor.Object);
127+
128+
var reminderResult = await defaultActorTimerManager.GetReminderAsync(new ActorReminderToken(actorType, new ActorId(actorId), reminderName));
129+
Assert.Null(reminderResult);
130+
}
112131

113132
[Fact]
114133
public async Task GetReminderAsync_ReturnsResultWhenAvailable()

0 commit comments

Comments
 (0)