diff --git a/orleans/Chirper/Chirper.Client/Chirper.Client.csproj b/orleans/Chirper/Chirper.Client/Chirper.Client.csproj index 7a56fc8f4b3..abea8cad7fa 100644 --- a/orleans/Chirper/Chirper.Client/Chirper.Client.csproj +++ b/orleans/Chirper/Chirper.Client/Chirper.Client.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net9.0 enable enable @@ -16,8 +16,8 @@ - - + + diff --git a/orleans/Chirper/Chirper.Client/ShellHostedService.cs b/orleans/Chirper/Chirper.Client/ShellHostedService.cs index 5ae6626c2bd..880233a9891 100644 --- a/orleans/Chirper/Chirper.Client/ShellHostedService.cs +++ b/orleans/Chirper/Chirper.Client/ShellHostedService.cs @@ -7,21 +7,12 @@ namespace Chirper.Client; -public sealed partial class ShellHostedService : BackgroundService +public sealed partial class ShellHostedService(IClusterClient client, IHostApplicationLifetime applicationLifetime) : BackgroundService { - private readonly IClusterClient _client; - private readonly IHostApplicationLifetime _applicationLifetime; - private ChirperConsoleViewer? _viewer; private IChirperViewer? _viewerRef; private IChirperAccount? _account; - public ShellHostedService(IClusterClient client, IHostApplicationLifetime applicationLifetime) - { - _client = client; - _applicationLifetime = applicationLifetime; - } - protected override async Task ExecuteAsync(CancellationToken stoppingToken) { ShowHelp(true); @@ -35,7 +26,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) } else if (command is null or "/quit") { - _applicationLifetime.StopApplication(); + applicationLifetime.StopApplication(); return; } else if (command.StartsWith("/user ")) @@ -44,7 +35,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) { await Unobserve(); var username = match.Groups["username"].Value; - _account = _client.GetGrain(username); + _account = client.GetGrain(username); AnsiConsole.MarkupLine("[bold grey][[[/][bold lime]✓[/][bold grey]]][/] The current user is now [navy]{0}[/]", username); } @@ -121,7 +112,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) if (_viewerRef is null) { _viewer = new ChirperConsoleViewer(_account.GetPrimaryKeyString()); - _viewerRef = _client.CreateObjectReference(_viewer); + _viewerRef = client.CreateObjectReference(_viewer); } await _account.SubscribeAsync(_viewerRef); diff --git a/orleans/Chirper/Chirper.Grains.Interfaces/Chirper.Grains.Interfaces.csproj b/orleans/Chirper/Chirper.Grains.Interfaces/Chirper.Grains.Interfaces.csproj index 1e2bd3c7486..b04dad1cea5 100644 --- a/orleans/Chirper/Chirper.Grains.Interfaces/Chirper.Grains.Interfaces.csproj +++ b/orleans/Chirper/Chirper.Grains.Interfaces/Chirper.Grains.Interfaces.csproj @@ -1,13 +1,13 @@ - net8.0 + net9.0 enable enable - + diff --git a/orleans/Chirper/Chirper.Grains.Interfaces/EnumerableExtensions.cs b/orleans/Chirper/Chirper.Grains.Interfaces/EnumerableExtensions.cs index a8fe5b930bc..2475fcf48f9 100644 --- a/orleans/Chirper/Chirper.Grains.Interfaces/EnumerableExtensions.cs +++ b/orleans/Chirper/Chirper.Grains.Interfaces/EnumerableExtensions.cs @@ -1,7 +1,7 @@ -namespace System.Collections.Generic; +namespace System.Collections.Generic; /// -/// Helper extensions for enumerables. +/// Helper extensions for . /// public static class EnumerableExtensions { diff --git a/orleans/Chirper/Chirper.Grains.Interfaces/Models/ChirperMessage.cs b/orleans/Chirper/Chirper.Grains.Interfaces/Models/ChirperMessage.cs index 4d8f79745b8..344dd95a165 100644 --- a/orleans/Chirper/Chirper.Grains.Interfaces/Models/ChirperMessage.cs +++ b/orleans/Chirper/Chirper.Grains.Interfaces/Models/ChirperMessage.cs @@ -1,9 +1,9 @@ -namespace Chirper.Grains.Models; +namespace Chirper.Grains.Models; /// /// Data object representing one Chirp message entry /// -[GenerateSerializer] +[GenerateSerializer, Immutable] public record class ChirperMessage( /// /// The message content for this chirp message entry. diff --git a/orleans/Chirper/Chirper.Grains/Chirper.Grains.csproj b/orleans/Chirper/Chirper.Grains/Chirper.Grains.csproj index ea74d6737f7..a3f6b16ea00 100644 --- a/orleans/Chirper/Chirper.Grains/Chirper.Grains.csproj +++ b/orleans/Chirper/Chirper.Grains/Chirper.Grains.csproj @@ -1,13 +1,13 @@ - net8.0 + net9.0 enable enable - + diff --git a/orleans/Chirper/Chirper.Grains/ChirperAccount.cs b/orleans/Chirper/Chirper.Grains/ChirperAccount.cs index 5d72315b932..c869f134662 100644 --- a/orleans/Chirper/Chirper.Grains/ChirperAccount.cs +++ b/orleans/Chirper/Chirper.Grains/ChirperAccount.cs @@ -1,4 +1,4 @@ -using System.Collections.Immutable; +using System.Collections.Immutable; using Chirper.Grains.Models; using Microsoft.Extensions.Logging; using Orleans.Concurrency; @@ -7,8 +7,12 @@ namespace Chirper.Grains; [Reentrant] -public sealed class ChirperAccount : Grain, IChirperAccount +public sealed class ChirperAccount( + [PersistentState(stateName: "account", storageName: "AccountState")] IPersistentState state, + ILogger logger) : Grain, IChirperAccount { + private static string GrainType => nameof(ChirperAccount); + /// /// Size for the recently received message cache. /// @@ -29,28 +33,17 @@ public sealed class ChirperAccount : Grain, IChirperAccount /// This list is not part of state and will not survive grain deactivation. /// private readonly HashSet _viewers = new(); - private readonly ILogger _logger; - private readonly IPersistentState _state; /// /// Allows state writing to happen in the background. /// private Task? _outstandingWriteStateOperation; - public ChirperAccount( - [PersistentState(stateName: "account", storageName: "AccountState")] IPersistentState state, - ILogger logger) - { - _state = state; - _logger = logger; - } - - private static string GrainType => nameof(ChirperAccount); private string GrainKey => this.GetPrimaryKeyString(); public override Task OnActivateAsync(CancellationToken _) { - _logger.LogInformation("{GrainType} {GrainKey} activated.", GrainType, GrainKey); + logger.LogInformation("{GrainType} {GrainKey} activated.", GrainType, GrainKey); return Task.CompletedTask; } @@ -59,41 +52,41 @@ public async ValueTask PublishMessageAsync(string message) { var chirp = CreateNewChirpMessage(message); - _logger.LogInformation("{GrainType} {GrainKey} publishing new chirp message '{Chirp}'.", + logger.LogInformation("{GrainType} {GrainKey} publishing new chirp message '{Chirp}'.", GrainType, GrainKey, chirp); - _state.State.MyPublishedMessages.Enqueue(chirp); + state.State.MyPublishedMessages.Enqueue(chirp); - while (_state.State.MyPublishedMessages.Count > PublishedMessagesCacheSize) + while (state.State.MyPublishedMessages.Count > PublishedMessagesCacheSize) { - _state.State.MyPublishedMessages.Dequeue(); + state.State.MyPublishedMessages.Dequeue(); } await WriteStateAsync(); // notify viewers of new message - _logger.LogInformation("{GrainType} {GrainKey} sending new chirp message to {ViewerCount} viewers.", + logger.LogInformation("{GrainType} {GrainKey} sending new chirp message to {ViewerCount} viewers.", GrainType, GrainKey, _viewers.Count); _viewers.ForEach(_ => _.NewChirp(chirp)); // notify followers of a new message - _logger.LogInformation("{GrainType} {GrainKey} sending new chirp message to {FollowerCount} followers.", - GrainType, GrainKey, _state.State.Followers.Count); + logger.LogInformation("{GrainType} {GrainKey} sending new chirp message to {FollowerCount} followers.", + GrainType, GrainKey, state.State.Followers.Count); - await Task.WhenAll(_state.State.Followers.Values.Select(_ => _.NewChirpAsync(chirp)).ToArray()); + await Task.WhenAll(state.State.Followers.Values.Select(_ => _.NewChirpAsync(chirp)).ToArray()); } public ValueTask> GetReceivedMessagesAsync(int number, int start) { if (start < 0) start = 0; - if (start + number > _state.State.RecentReceivedMessages.Count) + if (start + number > state.State.RecentReceivedMessages.Count) { - number = _state.State.RecentReceivedMessages.Count - start; + number = state.State.RecentReceivedMessages.Count - start; } return ValueTask.FromResult( - _state.State.RecentReceivedMessages + state.State.RecentReceivedMessages .Skip(start) .Take(number) .ToImmutableList()); @@ -101,7 +94,7 @@ public ValueTask> GetReceivedMessagesAsync(int num public async ValueTask FollowUserIdAsync(string username) { - _logger.LogInformation( + logger.LogInformation( "{GrainType} {UserName} > FollowUserName({TargetUserName}).", GrainType, GrainKey, @@ -111,7 +104,7 @@ public async ValueTask FollowUserIdAsync(string username) await userToFollow.AddFollowerAsync(GrainKey, this.AsReference()); - _state.State.Subscriptions[username] = userToFollow; + state.State.Subscriptions[username] = userToFollow; await WriteStateAsync(); @@ -121,7 +114,7 @@ public async ValueTask FollowUserIdAsync(string username) public async ValueTask UnfollowUserIdAsync(string username) { - _logger.LogInformation( + logger.LogInformation( "{GrainType} {GrainKey} > UnfollowUserName({TargetUserName}).", GrainType, GrainKey, @@ -132,7 +125,7 @@ await GrainFactory.GetGrain(username) .RemoveFollowerAsync(GrainKey); // remove this publisher from the subscriptions list - _state.State.Subscriptions.Remove(username); + state.State.Subscriptions.Remove(username); // save now await WriteStateAsync(); @@ -142,10 +135,10 @@ await GrainFactory.GetGrain(username) } public ValueTask> GetFollowingListAsync() => - ValueTask.FromResult(_state.State.Subscriptions.Keys.ToImmutableList()); + ValueTask.FromResult(state.State.Subscriptions.Keys.ToImmutableList()); public ValueTask> GetFollowersListAsync() => - ValueTask.FromResult(_state.State.Followers.Keys.ToImmutableList()); + ValueTask.FromResult(state.State.Followers.Keys.ToImmutableList()); public ValueTask SubscribeAsync(IChirperViewer viewer) { @@ -162,12 +155,12 @@ public ValueTask UnsubscribeAsync(IChirperViewer viewer) public ValueTask> GetPublishedMessagesAsync(int number, int start) { if (start < 0) start = 0; - if (start + number > _state.State.MyPublishedMessages.Count) + if (start + number > state.State.MyPublishedMessages.Count) { - number = _state.State.MyPublishedMessages.Count - start; + number = state.State.MyPublishedMessages.Count - start; } return ValueTask.FromResult( - _state.State.MyPublishedMessages + state.State.MyPublishedMessages .Skip(start) .Take(number) .ToImmutableList()); @@ -175,37 +168,37 @@ public ValueTask> GetPublishedMessagesAsync(int nu public async ValueTask AddFollowerAsync(string username, IChirperSubscriber follower) { - _state.State.Followers[username] = follower; + state.State.Followers[username] = follower; await WriteStateAsync(); _viewers.ForEach(cv => cv.NewFollower(username)); } public ValueTask RemoveFollowerAsync(string username) { - _state.State.Followers.Remove(username); + state.State.Followers.Remove(username); return WriteStateAsync(); } public async Task NewChirpAsync(ChirperMessage chirp) { - _logger.LogInformation( + logger.LogInformation( "{GrainType} {GrainKey} received chirp message = {Chirp}", GrainType, GrainKey, chirp); - _state.State.RecentReceivedMessages.Enqueue(chirp); + state.State.RecentReceivedMessages.Enqueue(chirp); // only relevant when not using fixed queue - while (_state.State.RecentReceivedMessages.Count > ReceivedMessagesCacheSize) // to keep not more than the max number of messages + while (state.State.RecentReceivedMessages.Count > ReceivedMessagesCacheSize) // to keep not more than the max number of messages { - _state.State.RecentReceivedMessages.Dequeue(); + state.State.RecentReceivedMessages.Dequeue(); } await WriteStateAsync(); // notify any viewers that a new chirp has been received - _logger.LogInformation( + logger.LogInformation( "{GrainType} {GrainKey} sending received chirp message to {ViewerCount} viewers", GrainType, GrainKey, @@ -246,7 +239,7 @@ private async ValueTask WriteStateAsync() if (_outstandingWriteStateOperation is null) { // If after the initial write is completed, no other request initiated a new write operation, do it now. - currentWriteStateOperation = _state.WriteStateAsync(); + currentWriteStateOperation = state.WriteStateAsync(); _outstandingWriteStateOperation = currentWriteStateOperation; } else diff --git a/orleans/Chirper/Chirper.Grains/ChirperAccountState.cs b/orleans/Chirper/Chirper.Grains/ChirperAccountState.cs index a7fa6859b8a..6ec9694dd27 100644 --- a/orleans/Chirper/Chirper.Grains/ChirperAccountState.cs +++ b/orleans/Chirper/Chirper.Grains/ChirperAccountState.cs @@ -1,4 +1,4 @@ -using Chirper.Grains.Models; +using Chirper.Grains.Models; namespace Chirper.Grains; diff --git a/orleans/Chirper/Chirper.Server/Chirper.Server.csproj b/orleans/Chirper/Chirper.Server/Chirper.Server.csproj index ed23905ba9d..a4481270818 100644 --- a/orleans/Chirper/Chirper.Server/Chirper.Server.csproj +++ b/orleans/Chirper/Chirper.Server/Chirper.Server.csproj @@ -2,14 +2,14 @@ Exe - net8.0 + net9.0 enable enable - - + + diff --git a/orleans/Chirper/Chirper.sln b/orleans/Chirper/Chirper.sln index 4d921fc05f4..ff35074f486 100644 --- a/orleans/Chirper/Chirper.sln +++ b/orleans/Chirper/Chirper.sln @@ -9,6 +9,9 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Chirper.Server", "Chirper.Server\Chirper.Server.csproj", "{2C967379-D025-4EBB-96C0-1A4C6CBE4B4B}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Chirper.Client", "Chirper.Client\Chirper.Client.csproj", "{3BE53D35-938D-47D8-B135-501786B145DC}" + ProjectSection(ProjectDependencies) = postProject + {2C967379-D025-4EBB-96C0-1A4C6CBE4B4B} = {2C967379-D025-4EBB-96C0-1A4C6CBE4B4B} + EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1223A865-0D72-4BF7-8FCD-016A48F3EC68}" ProjectSection(SolutionItems) = preProject diff --git a/orleans/Chirper/Chirper.slnLaunch b/orleans/Chirper/Chirper.slnLaunch new file mode 100644 index 00000000000..1c5cd154cd3 --- /dev/null +++ b/orleans/Chirper/Chirper.slnLaunch @@ -0,0 +1,15 @@ +[ + { + "Name": "Server + Client", + "Projects": [ + { + "Path": "Chirper.Client\\Chirper.Client.csproj", + "Action": "Start" + }, + { + "Path": "Chirper.Server\\Chirper.Server.csproj", + "Action": "Start" + } + ] + } +] \ No newline at end of file diff --git a/orleans/Chirper/README.md b/orleans/Chirper/README.md index 82ed63e882f..0c0e892d838 100644 --- a/orleans/Chirper/README.md +++ b/orleans/Chirper/README.md @@ -43,7 +43,7 @@ There is also an `IChirperViewer` observer interface for applications to subscri ## Sample prerequisites -This sample is written in C# and targets .NET 8.0. It requires the [.NET 8.0 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) or later. +This sample is written in C# and targets .NET 9.0. It requires the [.NET 9.0 SDK](https://dotnet.microsoft.com/download/dotnet/9.0) or later. ## Building the sample