Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade GPS Tracker to dotnet 9 and add Aspire orchestration #7016

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions orleans/GPSTracker/GPSTracker.AppHost/GPSTracker.AppHost.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">

<Sdk Name="Aspire.AppHost.Sdk" Version="9.0.0" />

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsAspireHost>true</IsAspireHost>
<UserSecretsId>afde51c8-abcf-4e8d-97a3-fd9301b96ffd</UserSecretsId>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.0.0" />
<PackageReference Include="Aspire.Hosting.Azure.Storage" Version="9.0.0" />
<PackageReference Include="Aspire.Hosting.Orleans" Version="9.0.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\GPSTracker.FakeDeviceGateway\GPSTracker.FakeDeviceGateway.csproj" />
<ProjectReference Include="..\GPSTracker.Service\GPSTracker.Service.csproj" />
</ItemGroup>

</Project>
19 changes: 19 additions & 0 deletions orleans/GPSTracker/GPSTracker.AppHost/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
var builder = DistributedApplication.CreateBuilder(args);

// https://learn.microsoft.com/en-us/dotnet/aspire/frameworks/orleans?tabs=dotnet-cli
var storage = builder.AddAzureStorage("storage")
.RunAsEmulator();
var clusteringTable = storage.AddTables("clustering");
var orleans = builder.AddOrleans("default")
.WithClustering(clusteringTable);

var service = builder.AddProject<Projects.GPSTracker_Service>("gpstracker-service")
.WithReference(orleans)
.WithReplicas(3);

var deviceGateway = builder.AddProject<Projects.GPSTracker_FakeDeviceGateway>("device-gateway")
.WithReference(orleans.AsClient())
.WithExternalHttpEndpoints()
.WaitFor(service);

builder.Build().Run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:17244;http://localhost:15133",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21285",
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22279"
}
},
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:15133",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19294",
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20112"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
9 changes: 9 additions & 0 deletions orleans/GPSTracker/GPSTracker.AppHost/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Aspire.Hosting.Dcp": "Warning"
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Orleans.Sdk" Version="8.0.0" />
<PackageReference Include="Microsoft.Orleans.Sdk" Version="9.0.1" />
</ItemGroup>
</Project>
2 changes: 1 addition & 1 deletion orleans/GPSTracker/GPSTracker.Common/LoadDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ private static void UpdateDevicePosition(Model model, double delta)

public static double NextDouble(double min, double max) => Random.NextDouble() * (max - min) + min;

private class Model
private sealed class Model
{
public Stopwatch TimeSinceLastUpdate { get; } = Stopwatch.StartNew();
public int DeviceId { get; set; }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\GPSTracker.Common\GPSTracker.Common.csproj" />
<ProjectReference Include="..\GPSTracker.ServiceDefaults\GPSTracker.ServiceDefaults.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Aspire.Azure.Data.Tables" Version="9.0.0" />
<PackageReference Include="Microsoft.Orleans.Clustering.AzureStorage" Version="9.0.1" />
</ItemGroup>
</Project>
9 changes: 5 additions & 4 deletions orleans/GPSTracker/GPSTracker.FakeDeviceGateway/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

using IHost host = Host.CreateDefaultBuilder(args)
.UseOrleansClient((ctx, clientBuilder) => clientBuilder.UseLocalhostClustering())
.UseConsoleLifetime()
.Build();
var builder = Host.CreateApplicationBuilder(args);
builder.AddServiceDefaults();
builder.AddKeyedAzureTableClient("clustering");
builder.UseOrleansClient();

using var host = builder.Build();
await host.StartAsync();

IHostApplicationLifetime lifetime = host.Services.GetRequiredService<IHostApplicationLifetime>();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<OutputType>Exe</OutputType>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\GPSTracker.Common\GPSTracker.Common.csproj" />
<ProjectReference Include="..\GPSTracker.ServiceDefaults\GPSTracker.ServiceDefaults.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Orleans.Server" Version="8.0.0" />
<PackageReference Include="OpenTelemetry.Exporter.Zipkin" Version="1.7.0" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.7.0" />
<PackageReference Include="OpenTelemetry.Exporter.Prometheus.AspNetCore" Version="1.4.0-rc.4" />
<PackageReference Include="Aspire.Azure.Data.Tables" Version="9.0.0" />
<PackageReference Include="Microsoft.Orleans.Clustering.AzureStorage" Version="9.0.1" />
<PackageReference Include="Microsoft.Orleans.Server" Version="9.0.1" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace GPSTracker.GrainImplementation;
[Reentrant]
public class DeviceGrain : Grain, IDeviceGrain
{
private DeviceMessage _lastMessage = null!;
private DeviceMessage? _lastMessage;

private readonly IPushNotifierGrain _pushNotifier;

Expand Down
12 changes: 3 additions & 9 deletions orleans/GPSTracker/GPSTracker.Service/Grains/HubListGrain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,12 @@

namespace GPSTracker.GrainImplementation;

public class HubListGrain : Grain, IHubListGrain
public class HubListGrain(IClusterMembershipService clusterMembershipService) : Grain, IHubListGrain
{
private readonly IClusterMembershipService _clusterMembership;
private readonly Dictionary<SiloAddress, IRemoteLocationHub> _hubs = new();
private readonly Dictionary<SiloAddress, IRemoteLocationHub> _hubs = [];
private MembershipVersion _cacheMembershipVersion;
private List<(SiloAddress Host, IRemoteLocationHub Hub)>? _cache;

public HubListGrain(IClusterMembershipService clusterMembershipService)
{
_clusterMembership = clusterMembershipService;
}

public ValueTask AddHub(SiloAddress host, IRemoteLocationHub hubReference)
{
// Invalidate the cache.
Expand All @@ -29,7 +23,7 @@ public ValueTask AddHub(SiloAddress host, IRemoteLocationHub hubReference)
private List<(SiloAddress Host, IRemoteLocationHub Hub)> GetCachedHubs()
{
// Returns a cached list of hubs if the cache is valid, otherwise builds a list of hubs.
ClusterMembershipSnapshot clusterMembers = _clusterMembership.CurrentSnapshot;
ClusterMembershipSnapshot clusterMembers = clusterMembershipService.CurrentSnapshot;
if (_cache is { } && clusterMembers.Version == _cacheMembershipVersion)
{
return _cache;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Orleans.Runtime;
using Orleans.Runtime;

namespace GPSTracker.GrainImplementation;

Expand All @@ -8,5 +8,6 @@ namespace GPSTracker.GrainImplementation;
public interface IHubListGrain : IGrainWithGuidKey
{
ValueTask AddHub(SiloAddress host, IRemoteLocationHub hubReference);

ValueTask<List<(SiloAddress Host, IRemoteLocationHub Hub)>> GetHubs();
}
44 changes: 22 additions & 22 deletions orleans/GPSTracker/GPSTracker.Service/Grains/PushNotifierGrain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,44 +7,46 @@ namespace GPSTracker.GrainImplementation;

[Reentrant]
[StatelessWorker(maxLocalWorkers: 12)]
public class PushNotifierGrain : Grain, IPushNotifierGrain
public sealed class PushNotifierGrain(ILogger<PushNotifierGrain> logger) : Grain, IPushNotifierGrain, IDisposable
{
private readonly Queue<VelocityMessage> _messageQueue = new();
private readonly ILogger<PushNotifierGrain> _logger;
private List<(SiloAddress Host, IRemoteLocationHub Hub)> _hubs = new();
public PushNotifierGrain(ILogger<PushNotifierGrain> logger) => _logger = logger;
private Task _flushTask = Task.CompletedTask;
private IGrainTimer? _flushTimer;
private IGrainTimer? _refreshTimer;

public override async Task OnActivateAsync(CancellationToken cancellationToken)
{
// Set up a timer to regularly flush the message queue
RegisterTimer(
_ =>
{
Flush();
return Task.CompletedTask;
},
null,
TimeSpan.FromMilliseconds(15),
TimeSpan.FromMilliseconds(15));
_flushTimer = this.RegisterGrainTimer(
ct => Flush(),
dueTime: TimeSpan.FromMilliseconds(15),
period: TimeSpan.FromMilliseconds(15));

// Set up a timer to regularly refresh the hubs, to respond to azure infrastructure changes
await RefreshHubs();
RegisterTimer(
asyncCallback: async _ => await RefreshHubs(),
state: null,

_refreshTimer = this.RegisterGrainTimer(
async _ => await RefreshHubs(),
dueTime: TimeSpan.FromSeconds(60),
period: TimeSpan.FromSeconds(60));

await base.OnActivateAsync(cancellationToken);
}


public override async Task OnDeactivateAsync(DeactivationReason deactivationReason, CancellationToken cancellationToken)
{
await Flush();
await base.OnDeactivateAsync(deactivationReason, cancellationToken);
}

public void Dispose()
{
_flushTimer?.Dispose();
_refreshTimer?.Dispose();
}

private async ValueTask RefreshHubs()
{
// Discover the current infrastructure
Expand Down Expand Up @@ -77,16 +79,14 @@ async Task FlushInternal()
{
// Send all messages to all SignalR hubs
var messagesToSend = new List<VelocityMessage>(Math.Min(_messageQueue.Count, MaxMessagesPerBatch));
while (messagesToSend.Count < MaxMessagesPerBatch && _messageQueue.TryDequeue(out VelocityMessage? msg)) messagesToSend.Add(msg);

var tasks = new List<Task>(_hubs.Count);
var batch = new VelocityBatch(messagesToSend);

foreach ((SiloAddress Host, IRemoteLocationHub Hub) hub in _hubs)
while (messagesToSend.Count < MaxMessagesPerBatch && _messageQueue.TryDequeue(out VelocityMessage? msg))
{
tasks.Add(BroadcastUpdates(hub.Host, hub.Hub, batch, _logger));
messagesToSend.Add(msg);
}

var batch = new VelocityBatch(messagesToSend);
var tasks = _hubs.Select(hub => BroadcastUpdates(hub.Host, hub.Hub, batch, logger));

await Task.WhenAll(tasks);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,10 @@ namespace GPSTracker;
/// <summary>
/// Broadcasts location messages to clients which are connected to the local SignalR hub.
/// </summary>
internal sealed class RemoteLocationHub : IRemoteLocationHub
internal sealed class RemoteLocationHub(IHubContext<LocationHub> hub) : IRemoteLocationHub
{
private readonly IHubContext<LocationHub> _hub;

public RemoteLocationHub(IHubContext<LocationHub> hub) => _hub = hub;

// Send a message to every client which is connected to the hub
public ValueTask BroadcastUpdates(VelocityBatch messages) =>
new(_hub.Clients.All.SendAsync(
new(hub.Clients.All.SendAsync(
"locationUpdates", messages, CancellationToken.None));
}
32 changes: 10 additions & 22 deletions orleans/GPSTracker/GPSTracker.Service/HubListUpdater.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,19 @@ namespace GPSTracker;
/// <summary>
/// Periodically updates the <see cref="IHubListGrain"/> implementation with a reference to the local <see cref="RemoteLocationHub"/>.
/// </summary>
[Reentrant]
internal sealed class HubListUpdater : BackgroundService
internal sealed class HubListUpdater(
IGrainFactory grainFactory,
ILogger<HubListUpdater> logger,
ILocalSiloDetails localSiloDetails,
IHubContext<LocationHub> hubContext) : BackgroundService
{
private readonly IGrainFactory _grainFactory;
private readonly ILogger<HubListUpdater> _logger;
private readonly ILocalSiloDetails _localSiloDetails;
private readonly RemoteLocationHub _locationBroadcaster;

public HubListUpdater(
IGrainFactory grainFactory,
ILogger<HubListUpdater> logger,
ILocalSiloDetails localSiloDetails,
IHubContext<LocationHub> hubContext)
{
_grainFactory = grainFactory;
_logger = logger;
_localSiloDetails = localSiloDetails;
_locationBroadcaster = new RemoteLocationHub(hubContext);
}
private readonly RemoteLocationHub _locationBroadcaster = new RemoteLocationHub(hubContext);

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
IHubListGrain hubListGrain = _grainFactory.GetGrain<IHubListGrain>(Guid.Empty);
SiloAddress localSiloAddress = _localSiloDetails.SiloAddress;
IRemoteLocationHub selfReference = _grainFactory.CreateObjectReference<IRemoteLocationHub>(_locationBroadcaster);
IHubListGrain hubListGrain = grainFactory.GetGrain<IHubListGrain>(Guid.Empty);
SiloAddress localSiloAddress = localSiloDetails.SiloAddress;
IRemoteLocationHub selfReference = grainFactory.CreateObjectReference<IRemoteLocationHub>(_locationBroadcaster);

// This runs in a loop because the HubListGrain does not use any form of persistence, so if the
// host which it is activated on stops, then it will lose any internal state.
Expand All @@ -45,7 +33,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
}
catch (Exception exception) when (!stoppingToken.IsCancellationRequested)
{
_logger.LogError(exception, "Error polling location hub list");
logger.LogError(exception, "Error polling location hub list");
}

if (!stoppingToken.IsCancellationRequested)
Expand Down
Loading
Loading