Skip to content

Commit 32d06a7

Browse files
authored
Tentative fix for timers deserializing error (#1512)
* Tentative fix for deserializing error * Added unit tests to prove out timer deserialization for all supported formats Signed-off-by: Whit Waldo <[email protected]>
1 parent 6f07643 commit 32d06a7

File tree

3 files changed

+214
-2
lines changed

3 files changed

+214
-2
lines changed

src/Dapr.Actors/Runtime/ActorManager.cs

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ async Task<byte[]> RequestFunc(Actor actor, CancellationToken ct)
223223
internal async Task FireTimerAsync(ActorId actorId, Stream requestBodyStream, CancellationToken cancellationToken = default)
224224
{
225225
#pragma warning disable 0618
226-
var timerData = await JsonSerializer.DeserializeAsync<TimerInfo>(requestBodyStream);
226+
var timerData = await DeserializeAsync(requestBodyStream);
227227
#pragma warning restore 0618
228228

229229
// Create a Func to be invoked by common method.
@@ -243,6 +243,62 @@ async Task<byte[]> RequestFunc(Actor actor, CancellationToken ct)
243243
await this.DispatchInternalAsync(actorId, this.timerMethodContext, RequestFunc, cancellationToken);
244244
}
245245

246+
#pragma warning disable 0618
247+
internal static async Task<TimerInfo> DeserializeAsync(Stream stream)
248+
{
249+
var json = await JsonSerializer.DeserializeAsync<JsonElement>(stream);
250+
if (json.ValueKind == JsonValueKind.Null)
251+
{
252+
return null;
253+
}
254+
255+
var setAnyProperties = false; // Used to determine if anything was actually deserialized
256+
var dueTime = TimeSpan.Zero;
257+
var callback = "";
258+
var period = TimeSpan.Zero;
259+
var data = Array.Empty<byte>();
260+
TimeSpan? ttl = null;
261+
if (json.TryGetProperty("callback", out var callbackProperty))
262+
{
263+
setAnyProperties = true;
264+
callback = callbackProperty.GetString();
265+
}
266+
if (json.TryGetProperty("dueTime", out var dueTimeProperty))
267+
{
268+
setAnyProperties = true;
269+
var dueTimeString = dueTimeProperty.GetString();
270+
dueTime = ConverterUtils.ConvertTimeSpanFromDaprFormat(dueTimeString);
271+
}
272+
273+
if (json.TryGetProperty("period", out var periodProperty))
274+
{
275+
setAnyProperties = true;
276+
var periodString = periodProperty.GetString();
277+
(period, _) = ConverterUtils.ConvertTimeSpanValueFromISO8601Format(periodString);
278+
}
279+
280+
if (json.TryGetProperty("data", out var dataProperty) && dataProperty.ValueKind != JsonValueKind.Null)
281+
{
282+
setAnyProperties = true;
283+
data = dataProperty.GetBytesFromBase64();
284+
}
285+
286+
if (json.TryGetProperty("ttl", out var ttlProperty))
287+
{
288+
setAnyProperties = true;
289+
var ttlString = ttlProperty.GetString();
290+
ttl = ConverterUtils.ConvertTimeSpanFromDaprFormat(ttlString);
291+
}
292+
293+
if (!setAnyProperties)
294+
{
295+
return null; //No properties were ever deserialized, so return null instead of default values
296+
}
297+
298+
return new TimerInfo(callback, data, dueTime, period, ttl);
299+
}
300+
#pragma warning restore 0618
301+
246302
internal async Task ActivateActorAsync(ActorId actorId)
247303
{
248304
// An actor is activated by "Dapr" runtime when a call is to be made for an actor.

src/Dapr.Actors/Runtime/ConverterUtils.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ public static string ConvertTimeSpanValueInISO8601Format(TimeSpan value, int? re
103103
builder.Append($"{value.Days}D");
104104
}
105105

106-
builder.Append("T");
106+
builder.Append('T');
107107

108108
if(value.Hours > 0)
109109
{

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

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
// ------------------------------------------------------------------------
1313

1414
using System;
15+
using System.IO;
16+
using System.Text;
1517
using System.Threading;
1618
using System.Threading.Tasks;
1719
using Dapr.Actors.Client;
@@ -175,6 +177,160 @@ await Assert.ThrowsAsync<InvalidTimeZoneException>(async () =>
175177
Assert.Equal(1, activator.DeleteCallCount);
176178
}
177179

180+
[Fact]
181+
public async Task DeserializeTimer_Period_Iso8601_Time()
182+
{
183+
const string timerJson = "{\"callback\": \"TimerCallback\", \"period\": \"0h0m7s10ms\"}";
184+
await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(timerJson));
185+
var result = await ActorManager.DeserializeAsync(stream);
186+
187+
Assert.Equal("TimerCallback", result.Callback);
188+
Assert.Equal(Array.Empty<byte>(), result.Data);
189+
Assert.Null(result.Ttl);
190+
Assert.Equal(TimeSpan.Zero, result.DueTime);
191+
Assert.Equal(TimeSpan.FromSeconds(7).Add(TimeSpan.FromMilliseconds(10)), result.Period);
192+
}
193+
194+
[Fact]
195+
public async Task DeserializeTimer_Period_DaprFormat_Every()
196+
{
197+
const string timerJson = "{\"callback\": \"TimerCallback\", \"period\": \"@every 15s\"}";
198+
await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(timerJson));
199+
var result = await ActorManager.DeserializeAsync(stream);
200+
201+
Assert.Equal("TimerCallback", result.Callback);
202+
Assert.Equal(Array.Empty<byte>(), result.Data);
203+
Assert.Null(result.Ttl);
204+
Assert.Equal(TimeSpan.Zero, result.DueTime);
205+
Assert.Equal(TimeSpan.FromSeconds(15), result.Period);
206+
}
207+
208+
[Fact]
209+
public async Task DeserializeTimer_Period_DaprFormat_Every2()
210+
{
211+
const string timerJson = "{\"callback\": \"TimerCallback\", \"period\": \"@every 3h2m15s\"}";
212+
await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(timerJson));
213+
var result = await ActorManager.DeserializeAsync(stream);
214+
215+
Assert.Equal("TimerCallback", result.Callback);
216+
Assert.Equal(Array.Empty<byte>(), result.Data);
217+
Assert.Null(result.Ttl);
218+
Assert.Equal(TimeSpan.Zero, result.DueTime);
219+
Assert.Equal(TimeSpan.FromHours(3).Add(TimeSpan.FromMinutes(2)).Add(TimeSpan.FromSeconds(15)), result.Period);
220+
}
221+
222+
[Fact]
223+
public async Task DeserializeTimer_Period_DaprFormat_Monthly()
224+
{
225+
const string timerJson = "{\"callback\": \"TimerCallback\", \"period\": \"@monthly\"}";
226+
await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(timerJson));
227+
var result = await ActorManager.DeserializeAsync(stream);
228+
229+
Assert.Equal("TimerCallback", result.Callback);
230+
Assert.Equal(Array.Empty<byte>(), result.Data);
231+
Assert.Null(result.Ttl);
232+
Assert.Equal(TimeSpan.Zero, result.DueTime);
233+
Assert.Equal(TimeSpan.FromDays(30), result.Period);
234+
}
235+
236+
[Fact]
237+
public async Task DeserializeTimer_Period_DaprFormat_Weekly()
238+
{
239+
const string timerJson = "{\"callback\": \"TimerCallback\", \"period\": \"@weekly\"}";
240+
await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(timerJson));
241+
var result = await ActorManager.DeserializeAsync(stream);
242+
243+
Assert.Equal("TimerCallback", result.Callback);
244+
Assert.Equal(Array.Empty<byte>(), result.Data);
245+
Assert.Null(result.Ttl);
246+
Assert.Equal(TimeSpan.Zero, result.DueTime);
247+
Assert.Equal(TimeSpan.FromDays(7), result.Period);
248+
}
249+
250+
[Fact]
251+
public async Task DeserializeTimer_Period_DaprFormat_Daily()
252+
{
253+
const string timerJson = "{\"callback\": \"TimerCallback\", \"period\": \"@daily\"}";
254+
await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(timerJson));
255+
var result = await ActorManager.DeserializeAsync(stream);
256+
257+
Assert.Equal("TimerCallback", result.Callback);
258+
Assert.Equal(Array.Empty<byte>(), result.Data);
259+
Assert.Null(result.Ttl);
260+
Assert.Equal(TimeSpan.Zero, result.DueTime);
261+
Assert.Equal(TimeSpan.FromDays(1), result.Period);
262+
}
263+
264+
[Fact]
265+
public async Task DeserializeTimer_Period_DaprFormat_Hourly()
266+
{
267+
const string timerJson = "{\"callback\": \"TimerCallback\", \"period\": \"@hourly\"}";
268+
await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(timerJson));
269+
var result = await ActorManager.DeserializeAsync(stream);
270+
271+
Assert.Equal("TimerCallback", result.Callback);
272+
Assert.Equal(Array.Empty<byte>(), result.Data);
273+
Assert.Null(result.Ttl);
274+
Assert.Equal(TimeSpan.Zero, result.DueTime);
275+
Assert.Equal(TimeSpan.FromHours(1), result.Period);
276+
}
277+
278+
[Fact]
279+
public async Task DeserializeTimer_DueTime_DaprFormat_Hourly()
280+
{
281+
const string timerJson = "{\"callback\": \"TimerCallback\", \"dueTime\": \"@hourly\"}";
282+
await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(timerJson));
283+
var result = await ActorManager.DeserializeAsync(stream);
284+
285+
Assert.Equal("TimerCallback", result.Callback);
286+
Assert.Equal(Array.Empty<byte>(), result.Data);
287+
Assert.Null(result.Ttl);
288+
Assert.Equal(TimeSpan.FromHours(1), result.DueTime);
289+
Assert.Equal(TimeSpan.Zero, result.Period);
290+
}
291+
292+
[Fact]
293+
public async Task DeserializeTimer_DueTime_Iso8601Times()
294+
{
295+
const string timerJson = "{\"callback\": \"TimerCallback\", \"dueTime\": \"0h0m7s10ms\"}";
296+
await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(timerJson));
297+
var result = await ActorManager.DeserializeAsync(stream);
298+
299+
Assert.Equal("TimerCallback", result.Callback);
300+
Assert.Equal(Array.Empty<byte>(), result.Data);
301+
Assert.Null(result.Ttl);
302+
Assert.Equal(TimeSpan.Zero, result.Period);
303+
Assert.Equal(TimeSpan.FromSeconds(7).Add(TimeSpan.FromMilliseconds(10)), result.DueTime);
304+
}
305+
306+
[Fact]
307+
public async Task DeserializeTimer_Ttl_DaprFormat_Hourly()
308+
{
309+
const string timerJson = "{\"callback\": \"TimerCallback\", \"ttl\": \"@hourly\"}";
310+
await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(timerJson));
311+
var result = await ActorManager.DeserializeAsync(stream);
312+
313+
Assert.Equal("TimerCallback", result.Callback);
314+
Assert.Equal(Array.Empty<byte>(), result.Data);
315+
Assert.Equal(TimeSpan.Zero, result.DueTime);
316+
Assert.Equal(TimeSpan.Zero, result.Period);
317+
Assert.Equal(TimeSpan.FromHours(1), result.Ttl);
318+
}
319+
320+
[Fact]
321+
public async Task DeserializeTimer_Ttl_Iso8601Times()
322+
{
323+
const string timerJson = "{\"callback\": \"TimerCallback\", \"ttl\": \"0h0m7s10ms\"}";
324+
await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(timerJson));
325+
var result = await ActorManager.DeserializeAsync(stream);
326+
327+
Assert.Equal("TimerCallback", result.Callback);
328+
Assert.Equal(Array.Empty<byte>(), result.Data);
329+
Assert.Equal(TimeSpan.Zero, result.DueTime);
330+
Assert.Equal(TimeSpan.Zero, result.Period);
331+
Assert.Equal(TimeSpan.FromSeconds(7).Add(TimeSpan.FromMilliseconds(10)), result.Ttl);
332+
}
333+
178334
private interface ITestActor : IActor { }
179335

180336
private class TestActor : Actor, ITestActor, IDisposable

0 commit comments

Comments
 (0)