Skip to content

Commit 851f8c7

Browse files
committed
grpc/orleans tests WIP
1 parent 680c3d0 commit 851f8c7

File tree

10 files changed

+102
-28
lines changed

10 files changed

+102
-28
lines changed

dotnet/src/Microsoft.AutoGen/Microsoft.AutoGen.Runtime.Grpc/AgentStateGrain.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// AgentStateGrain.cs
33

4+
using System.Data;
45
using Microsoft.AutoGen.Abstractions;
56

67
namespace Microsoft.AutoGen.Runtime.Grpc;
78

8-
internal sealed class AgentStateGrain([PersistentState("state", "AgentStateStore")] IPersistentState<AgentState> state) : Grain
9+
internal sealed class AgentStateGrain([PersistentState("state", "AgentStateStore")] IPersistentState<AgentState> state) : Grain, IAgentGrain
910
{
1011
/// <inheritdoc />
1112
public async ValueTask<string> WriteStateAsync(AgentState newState, string eTag/*, CancellationToken cancellationToken = default*/)
@@ -22,7 +23,7 @@ public async ValueTask<string> WriteStateAsync(AgentState newState, string eTag/
2223
else
2324
{
2425
//TODO - this is probably not the correct behavior to just throw - I presume we want to somehow let the caller know that the state has changed and they need to re-read it
25-
throw new ArgumentException(
26+
throw new DBConcurrencyException(
2627
"The provided ETag does not match the current ETag. The state has been modified by another request.");
2728
}
2829
return state.Etag;
@@ -34,3 +35,9 @@ public ValueTask<AgentState> ReadStateAsync(/*CancellationToken cancellationToke
3435
return ValueTask.FromResult(state.State);
3536
}
3637
}
38+
39+
internal interface IAgentGrain : IGrainWithStringKey
40+
{
41+
ValueTask<AgentState> ReadStateAsync();
42+
ValueTask<string> WriteStateAsync(AgentState state, string eTag);
43+
}

dotnet/src/Microsoft.AutoGen/Microsoft.AutoGen.Runtime.Grpc/GrpcGateway.cs

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ public sealed class GrpcGateway : BackgroundService, IGateway
1414
private static readonly TimeSpan s_agentResponseTimeout = TimeSpan.FromSeconds(30);
1515
private readonly ILogger<GrpcGateway> _logger;
1616
private readonly IClusterClient _clusterClient;
17-
private readonly ConcurrentDictionary<string, AgentState> _agentState = new();
1817
private readonly IRegistryGrain _gatewayRegistry;
1918
private readonly IGateway _reference;
2019

@@ -60,12 +59,18 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
6059
}
6160
}
6261

63-
internal Task ConnectToWorkerProcess(IAsyncStreamReader<Message> requestStream, IServerStreamWriter<Message> responseStream, ServerCallContext context)
62+
internal async Task ConnectToWorkerProcess(IAsyncStreamReader<Message> requestStream, IServerStreamWriter<Message> responseStream, ServerCallContext context)
6463
{
6564
_logger.LogInformation("Received new connection from {Peer}.", context.Peer);
6665
var workerProcess = new GrpcWorkerConnection(this, requestStream, responseStream, context);
6766
_workers[workerProcess] = workerProcess;
68-
return workerProcess.Completion;
67+
var completion = new TaskCompletionSource<Task>();
68+
var _ = Task.Run(() =>
69+
{
70+
completion.SetResult(workerProcess.Connect());
71+
});
72+
73+
await completion.Task;
6974
}
7075

7176
public async ValueTask BroadcastEvent(CloudEvent evt)
@@ -172,18 +177,16 @@ private static async Task InvokeRequestDelegate(GrpcWorkerConnection connection,
172177

173178
public async ValueTask StoreAsync(AgentState value)
174179
{
175-
var agentId = value.AgentId ?? throw new ArgumentNullException(nameof(value.AgentId));
176-
_agentState[agentId.Key] = value;
180+
var agentState = _clusterClient.GetGrain<IAgentGrain>($"{value.AgentId.Type}:{value.AgentId.Key}");
181+
await agentState.WriteStateAsync(value, value.ETag);
177182
}
178183

179184
public async ValueTask<AgentState> ReadAsync(AgentId agentId)
180185
{
181-
if (_agentState.TryGetValue(agentId.Key, out var state))
182-
{
183-
return state;
184-
}
185-
return new AgentState { AgentId = agentId };
186+
var agentState = _clusterClient.GetGrain<IAgentGrain>($"{agentId.Type}:{agentId.Key}");
187+
return await agentState.ReadStateAsync();
186188
}
189+
187190
internal void OnRemoveWorkerProcess(GrpcWorkerConnection workerProcess)
188191
{
189192
_workers.TryRemove(workerProcess, out _);
@@ -208,7 +211,7 @@ internal void OnRemoveWorkerProcess(GrpcWorkerConnection workerProcess)
208211
public async ValueTask<RpcResponse> InvokeRequest(RpcRequest request, CancellationToken cancellationToken = default)
209212
{
210213
var agentId = (request.Target.Type, request.Target.Key);
211-
if (!_agentDirectory.TryGetValue(agentId, out var connection) || connection.Completion.IsCompleted)
214+
if (!_agentDirectory.TryGetValue(agentId, out var connection) || connection.Completion?.IsCompleted == true)
212215
{
213216
// Activate the agent on a compatible worker process.
214217
if (_supportedAgentTypes.TryGetValue(request.Target.Type, out var workers))

dotnet/src/Microsoft.AutoGen/Microsoft.AutoGen.Runtime.Grpc/GrpcWorkerConnection.cs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@ namespace Microsoft.AutoGen.Runtime.Grpc;
1010
internal sealed class GrpcWorkerConnection : IAsyncDisposable
1111
{
1212
private static long s_nextConnectionId;
13-
private readonly Task _readTask;
14-
private readonly Task _writeTask;
13+
private Task? _readTask;
14+
private Task? _writeTask;
1515
private readonly string _connectionId = Interlocked.Increment(ref s_nextConnectionId).ToString();
1616
private readonly object _lock = new();
1717
private readonly HashSet<string> _supportedTypes = [];
1818
private readonly GrpcGateway _gateway;
1919
private readonly CancellationTokenSource _shutdownCancellationToken = new();
20+
public Task? Completion { get; private set; }
2021

2122
public GrpcWorkerConnection(GrpcGateway agentWorker, IAsyncStreamReader<Message> requestStream, IServerStreamWriter<Message> responseStream, ServerCallContext context)
2223
{
@@ -25,7 +26,10 @@ public GrpcWorkerConnection(GrpcGateway agentWorker, IAsyncStreamReader<Message>
2526
ResponseStream = responseStream;
2627
ServerCallContext = context;
2728
_outboundMessages = Channel.CreateUnbounded<Message>(new UnboundedChannelOptions { AllowSynchronousContinuations = true, SingleReader = true, SingleWriter = false });
29+
}
2830

31+
public Task Connect()
32+
{
2933
var didSuppress = false;
3034
if (!ExecutionContext.IsFlowSuppressed())
3135
{
@@ -46,7 +50,7 @@ public GrpcWorkerConnection(GrpcGateway agentWorker, IAsyncStreamReader<Message>
4650
}
4751
}
4852

49-
Completion = Task.WhenAll(_readTask, _writeTask);
53+
return Completion = Task.WhenAll(_readTask, _writeTask);
5054
}
5155

5256
public IAsyncStreamReader<Message> RequestStream { get; }
@@ -76,8 +80,6 @@ public async Task SendMessage(Message message)
7680
await _outboundMessages.Writer.WriteAsync(message).ConfigureAwait(false);
7781
}
7882

79-
public Task Completion { get; }
80-
8183
public async Task RunReadPump()
8284
{
8385
await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding);
@@ -122,7 +124,10 @@ public async Task RunWritePump()
122124
public async ValueTask DisposeAsync()
123125
{
124126
_shutdownCancellationToken.Cancel();
125-
await Completion.ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing);
127+
if (Completion is not null)
128+
{
129+
await Completion.ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing);
130+
}
126131
}
127132

128133
public override string ToString() => $"Connection-{_connectionId}";

dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/GrpcGatewayServiceTests.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,20 @@ public async Task Test_OpenChannel()
2929
var requestStream = new TestAsyncStreamReader<Message>(callContext);
3030
var responseStream = new TestServerStreamWriter<Message>(callContext);
3131

32+
await service.RegisterAgent(new RegisterAgentTypeRequest { Type = nameof(PBAgent), RequestId = $"{Guid.NewGuid()}", Events = { "", "" } }, callContext);
33+
await service.RegisterAgent(new RegisterAgentTypeRequest { Type = nameof(GMAgent), RequestId = $"{Guid.NewGuid()}", Events = { "", "" } }, callContext);
34+
3235
await service.OpenChannel(requestStream, responseStream, callContext);
3336

34-
requestStream.AddMessage(new Message { });
37+
var bgEvent = new CloudEvent
38+
{
39+
Id = "1",
40+
Source = "gh/repo/1",
41+
Type = "test",
42+
43+
};
44+
45+
requestStream.AddMessage(new Message { CloudEvent = bgEvent });
3546

3647
requestStream.Complete();
3748

@@ -62,7 +73,7 @@ public async Task Test_GetState()
6273
var service = new GrpcGatewayService(gateway);
6374
var callContext = TestServerCallContext.Create();
6475

65-
var response = await service.GetState(new AgentId { }, callContext);
76+
var response = await service.GetState(new AgentId { Key = "", Type = "" }, callContext);
6677

6778
response.Should().NotBeNull();
6879
}

dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/GrpcGatewayTests.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ public async Task TestBroadcastEvent()
2929

3030
// 1. Register Agent
3131
// 2. Broadcast Event
32-
// 3.
32+
// 3.
33+
3334
await gateway.BroadcastEvent(evt);
3435
//var registry = fixture.Registry;
3536
//var subscriptions = fixture.Subscriptions;

dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Orleans/SiloBuilderConfigurator.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ public void Configure(ISiloBuilder siloBuilder)
1212
{
1313
siloBuilder.ConfigureServices(services =>
1414
{
15-
services.AddSerializer(a=> a.AddProtobufSerializer());
15+
services.AddSerializer(a => a.AddProtobufSerializer());
1616
});
17+
siloBuilder.AddMemoryStreams("StreamProvider")
18+
.AddMemoryGrainStorage("PubSubStore")
19+
.AddMemoryGrainStorage("AgentStateStore");
1720
}
1821
}

dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Orleans/Surrogates/AgentStateSurrogate.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// AgentStateSurrogate.cs
33

44
using Google.Protobuf;
5-
using Google.Protobuf.WellKnownTypes;
65
using Microsoft.AutoGen.Abstractions;
76

87
namespace Microsoft.AutoGen.Runtime.Grpc.Tests.Helpers.Orleans.Surrogates;
@@ -21,7 +20,7 @@ public struct AgentStateSurrogate
2120
[Id(4)]
2221
public string Etag;
2322
[Id(5)]
24-
public Any ProtoData;
23+
public ByteString ProtoData;
2524
}
2625

2726
[RegisterConverter]
@@ -35,7 +34,7 @@ public AgentState ConvertFromSurrogate(
3534
TextData = surrogate.TextData,
3635
BinaryData = surrogate.BinaryData,
3736
AgentId = surrogate.AgentId,
38-
ProtoData = surrogate.ProtoData,
37+
// ProtoData = surrogate.ProtoData,
3938
ETag = surrogate.Etag
4039
};
4140

@@ -47,7 +46,7 @@ public AgentStateSurrogate ConvertToSurrogate(
4746
BinaryData = value.BinaryData,
4847
TextData = value.TextData,
4948
Etag = value.ETag,
50-
ProtoData = value.ProtoData
49+
ProtoData = value.ProtoData.Value
5150
};
5251
}
5352

dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Microsoft.AutoGen.Runtime.Grpc.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
</ItemGroup>
1414

1515
<ItemGroup>
16+
<ProjectReference Include="..\..\src\Microsoft.AutoGen\Microsoft.AutoGen.Core\Microsoft.AutoGen.Core.csproj" />
1617
<ProjectReference Include="..\..\src\Microsoft.AutoGen\Microsoft.AutoGen.Runtime.Grpc\Microsoft.AutoGen.Runtime.Grpc.csproj" />
1718
<ProjectReference Include="..\Microsoft.Autogen.Tests.Shared\Microsoft.Autogen.Tests.Shared.csproj" />
1819
</ItemGroup>
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// TestAgent.cs
3+
4+
using System.Collections.Concurrent;
5+
using Microsoft.AutoGen.Core;
6+
using Microsoft.Extensions.DependencyInjection;
7+
using Microsoft.Extensions.Logging;
8+
using Tests.Events;
9+
10+
namespace Microsoft.AutoGen.Runtime.Grpc.Tests;
11+
12+
public class PBAgent([FromKeyedServices("EventTypes")] EventTypes eventTypes, ILogger<Agent>? logger = null)
13+
: Agent(eventTypes, logger)
14+
, IHandle<NewMessageReceived>
15+
, IHandle<GoodBye>
16+
{
17+
public async Task Handle(NewMessageReceived item, CancellationToken cancellationToken = default)
18+
{
19+
ReceivedMessages[AgentId.Key] = item.Message;
20+
var hello = new Hello { Message = item.Message };
21+
await PublishEventAsync(hello);
22+
}
23+
public Task Handle(GoodBye item, CancellationToken cancellationToken)
24+
{
25+
_logger.LogInformation($"Received GoodBye message {item.Message}");
26+
return Task.CompletedTask;
27+
}
28+
29+
public static ConcurrentDictionary<string, object> ReceivedMessages { get; private set; } = new();
30+
}
31+
32+
public class GMAgent([FromKeyedServices("EventTypes")] EventTypes eventTypes, ILogger<Agent>? logger = null)
33+
: Agent(eventTypes, logger)
34+
, IHandle<Hello>
35+
{
36+
public async Task Handle(Hello item, CancellationToken cancellationToken)
37+
{
38+
_logger.LogInformation($"Received Hello message {item.Message}");
39+
ReceivedMessages[AgentId.Key] = item.Message;
40+
await PublishEventAsync(new GoodBye { Message = "" });
41+
}
42+
43+
public static ConcurrentDictionary<string, object> ReceivedMessages { get; private set; } = new();
44+
}

dotnet/test/Microsoft.Autogen.Tests.Shared/Protos/messages.proto

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ message TextMessage {
77
string message = 1;
88
string source = 2;
99
}
10-
message Input {
10+
message Hello {
1111
string message = 1;
1212
}
1313
message InputProcessed {

0 commit comments

Comments
 (0)