Skip to content

Commit

Permalink
Actor State TTL (#1164)
Browse files Browse the repository at this point in the history
* Actor state TTL support

Signed-off-by: joshvanl <[email protected]>
  • Loading branch information
JoshVanL authored Jan 8, 2024
1 parent 7616bfa commit 233c620
Show file tree
Hide file tree
Showing 31 changed files with 1,082 additions and 80 deletions.
4 changes: 2 additions & 2 deletions examples/Actor/ActorClient/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
// Copyright 2021 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.
Expand Down Expand Up @@ -46,7 +46,7 @@ public static async Task Main(string[] args)
var proxy = ActorProxy.Create<IDemoActor>(actorId, "DemoActor");

Console.WriteLine("Making call using actor proxy to save data.");
await proxy.SaveData(data);
await proxy.SaveData(data, TimeSpan.FromMinutes(10));
Console.WriteLine("Making call using actor proxy to get data.");
var receivedData = await proxy.GetData();
Console.WriteLine($"Received data is {receivedData}.");
Expand Down
10 changes: 5 additions & 5 deletions examples/Actor/DemoActor/DemoActor.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
// Copyright 2021 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.
Expand Down Expand Up @@ -41,12 +41,12 @@ public DemoActor(ActorHost host, BankService bank)
this.bank = bank;
}

public async Task SaveData(MyData data)
public async Task SaveData(MyData data, TimeSpan ttl)
{
Console.WriteLine($"This is Actor id {this.Id} with data {data}.");

// Set State using StateManager, state is saved after the method execution.
await this.StateManager.SetStateAsync<MyData>(StateName, data);
await this.StateManager.SetStateAsync<MyData>(StateName, data, ttl);
}

public Task<MyData> GetData()
Expand Down Expand Up @@ -109,7 +109,7 @@ public async Task ReceiveReminderAsync(string reminderName, byte[] state, TimeSp
// This method is invoked when an actor reminder is fired.
var actorState = await this.StateManager.GetStateAsync<MyData>(StateName);
actorState.PropertyB = $"Reminder triggered at '{DateTime.Now:yyyy-MM-ddTHH:mm:ss}'";
await this.StateManager.SetStateAsync<MyData>(StateName, actorState);
await this.StateManager.SetStateAsync<MyData>(StateName, actorState, ttl: TimeSpan.FromMinutes(5));
}

class TimerParams
Expand Down Expand Up @@ -173,7 +173,7 @@ public async Task TimerCallback(byte[] data)
{
var state = await this.StateManager.GetStateAsync<MyData>(StateName);
state.PropertyA = $"Timer triggered at '{DateTime.Now:yyyyy-MM-ddTHH:mm:s}'";
await this.StateManager.SetStateAsync<MyData>(StateName, state);
await this.StateManager.SetStateAsync<MyData>(StateName, state, ttl: TimeSpan.FromMinutes(5));
var timerParams = JsonSerializer.Deserialize<TimerParams>(data);
Console.WriteLine("Timer parameter1: " + timerParams.IntParam);
Console.WriteLine("Timer parameter2: " + timerParams.StringParam);
Expand Down
5 changes: 3 additions & 2 deletions examples/Actor/IDemoActor/IDemoActor.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
// Copyright 2021 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.
Expand Down Expand Up @@ -27,8 +27,9 @@ public interface IDemoActor : IActor
/// Method to save data.
/// </summary>
/// <param name="data">DAta to save.</param>
/// <param name="ttl">TTL of state key.</param>
/// <returns>A task that represents the asynchronous save operation.</returns>
Task SaveData(MyData data);
Task SaveData(MyData data, TimeSpan ttl);

/// <summary>
/// Method to get data.
Expand Down
50 changes: 50 additions & 0 deletions src/Dapr.Actors/Communication/ActorStateResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// ------------------------------------------------------------------------
// Copyright 2023 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.
// ------------------------------------------------------------------------

namespace Dapr.Actors.Communication
{
using System;

/// <summary>
/// Represents a response from fetching an actor state key.
/// </summary>
public class ActorStateResponse<T>
{
/// <summary>
/// Initializes a new instance of the <see cref="ActorStateResponse{T}"/> class.
/// </summary>
/// <param name="value">The response value.</param>
/// <param name="ttlExpireTime">The time to live expiration time.</param>
public ActorStateResponse(T value, DateTimeOffset? ttlExpireTime)
{
this.Value = value;
this.TTLExpireTime = ttlExpireTime;
}

/// <summary>
/// Gets the response value as a string.
/// </summary>
/// <value>
/// The response value as a string.
/// </value>
public T Value { get; }

/// <summary>
/// Gets the time to live expiration time.
/// </summary>
/// <value>
/// The time to live expiration time.
/// </value>
public DateTimeOffset? TTLExpireTime { get; }
}
}
3 changes: 2 additions & 1 deletion src/Dapr.Actors/Constants.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
// Copyright 2021 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.
Expand All @@ -22,6 +22,7 @@ internal static class Constants
public const string RequestHeaderName = "X-DaprRequestHeader";
public const string ErrorResponseHeaderName = "X-DaprErrorResponseHeader";
public const string ReentrancyRequestHeaderName = "Dapr-Reentrancy-Id";
public const string TTLResponseHeaderName = "Metadata.ttlExpireTime";
public const string Dapr = "dapr";
public const string Config = "config";
public const string State = "state";
Expand Down
15 changes: 13 additions & 2 deletions src/Dapr.Actors/DaprHttpInteractor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public DaprHttpInteractor(
this.httpClient.Timeout = requestTimeout ?? this.httpClient.Timeout;
}

public async Task<string> GetStateAsync(string actorType, string actorId, string keyName, CancellationToken cancellationToken = default)
public async Task<ActorStateResponse<string>> GetStateAsync(string actorType, string actorId, string keyName, CancellationToken cancellationToken = default)
{
var relativeUrl = string.Format(CultureInfo.InvariantCulture, Constants.ActorStateKeyRelativeUrlFormat, actorType, actorId, keyName);

Expand All @@ -72,7 +72,18 @@ HttpRequestMessage RequestFunc()

using var response = await this.SendAsync(RequestFunc, relativeUrl, cancellationToken);
var stringResponse = await response.Content.ReadAsStringAsync();
return stringResponse;

DateTimeOffset? ttlExpireTime = null;
if (response.Headers.TryGetValues(Constants.TTLResponseHeaderName, out IEnumerable<string> headerValues))
{
var ttlExpireTimeString = headerValues.First();
if (!string.IsNullOrEmpty(ttlExpireTimeString))
{
ttlExpireTime = DateTime.Parse(ttlExpireTimeString, CultureInfo.InvariantCulture);
}
}

return new ActorStateResponse<string>(stringResponse, ttlExpireTime);
}

public Task SaveStateTransactionallyAsync(string actorType, string actorId, string data, CancellationToken cancellationToken = default)
Expand Down
4 changes: 2 additions & 2 deletions src/Dapr.Actors/IDaprInteractor.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
// Copyright 2021 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.
Expand Down Expand Up @@ -52,7 +52,7 @@ internal interface IDaprInteractor
/// <param name="keyName">Name of key to get value for.</param>
/// <param name="cancellationToken">Cancels the operation.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
Task<string> GetStateAsync(string actorType, string actorId, string keyName, CancellationToken cancellationToken = default);
Task<ActorStateResponse<string>> GetStateAsync(string actorType, string actorId, string keyName, CancellationToken cancellationToken = default);

/// <summary>
/// Invokes Actor method.
Expand Down
17 changes: 15 additions & 2 deletions src/Dapr.Actors/Runtime/ActorStateChange.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
// Copyright 2021 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.
Expand Down Expand Up @@ -27,14 +27,16 @@ public sealed class ActorStateChange
/// <param name="type">The type of value associated with given actor state name.</param>
/// <param name="value">The value associated with given actor state name.</param>
/// <param name="changeKind">The kind of state change for given actor state name.</param>
public ActorStateChange(string stateName, Type type, object value, StateChangeKind changeKind)
/// <param name="ttlExpireTime">The time to live for the state.</param>
public ActorStateChange(string stateName, Type type, object value, StateChangeKind changeKind, DateTimeOffset? ttlExpireTime)
{
ArgumentVerifier.ThrowIfNull(stateName, nameof(stateName));

this.StateName = stateName;
this.Type = type;
this.Value = value;
this.ChangeKind = changeKind;
this.TTLExpireTime = ttlExpireTime;
}

/// <summary>
Expand Down Expand Up @@ -68,5 +70,16 @@ public ActorStateChange(string stateName, Type type, object value, StateChangeKi
/// The kind of state change for given actor state name.
/// </value>
public StateChangeKind ChangeKind { get; }

/// <summary>
/// Gets the time to live for the state.
/// </summary>
/// <value>
/// The time to live for the state.
/// </value>
/// <remarks>
/// If null, the state will not expire.
/// </remarks>
public DateTimeOffset? TTLExpireTime { get; }
}
}
Loading

0 comments on commit 233c620

Please sign in to comment.