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 Orleans Chirper sample to dotnet 9 #7015

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
6 changes: 3 additions & 3 deletions orleans/Chirper/Chirper.Client/Chirper.Client.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
Expand All @@ -16,8 +16,8 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Spectre.Console" Version="0.48.0" />
<PackageReference Include="Spectre.Console.ImageSharp" Version="0.48.0" />
<PackageReference Include="Spectre.Console" Version="0.49.1" />
<PackageReference Include="Spectre.Console.ImageSharp" Version="0.49.1" />
</ItemGroup>

<ItemGroup>
Expand Down
17 changes: 4 additions & 13 deletions orleans/Chirper/Chirper.Client/ShellHostedService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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 "))
Expand All @@ -44,7 +35,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await Unobserve();
var username = match.Groups["username"].Value;
_account = _client.GetGrain<IChirperAccount>(username);
_account = client.GetGrain<IChirperAccount>(username);

AnsiConsole.MarkupLine("[bold grey][[[/][bold lime]✓[/][bold grey]]][/] The current user is now [navy]{0}[/]", username);
}
Expand Down Expand Up @@ -121,7 +112,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
if (_viewerRef is null)
{
_viewer = new ChirperConsoleViewer(_account.GetPrimaryKeyString());
_viewerRef = _client.CreateObjectReference<IChirperViewer>(_viewer);
_viewerRef = client.CreateObjectReference<IChirperViewer>(_viewer);
}

await _account.SubscribeAsync(_viewerRef);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<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>
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
namespace System.Collections.Generic;
namespace System.Collections.Generic;

/// <summary>
/// Helper extensions for enumerables.
/// Helper extensions for <see cref="IEnumerable{T}">.
/// </summary>
public static class EnumerableExtensions
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
namespace Chirper.Grains.Models;
namespace Chirper.Grains.Models;

/// <summary>
/// Data object representing one Chirp message entry
/// </summary>
[GenerateSerializer]
[GenerateSerializer, Immutable]
public record class ChirperMessage(
/// <summary>
/// The message content for this chirp message entry.
Expand Down
4 changes: 2 additions & 2 deletions orleans/Chirper/Chirper.Grains/Chirper.Grains.csproj
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<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.Runtime" Version="8.0.0" />
<PackageReference Include="Microsoft.Orleans.Runtime" Version="9.0.1" />
</ItemGroup>

<ItemGroup>
Expand Down
77 changes: 35 additions & 42 deletions orleans/Chirper/Chirper.Grains/ChirperAccount.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Collections.Immutable;
using System.Collections.Immutable;
using Chirper.Grains.Models;
using Microsoft.Extensions.Logging;
using Orleans.Concurrency;
Expand All @@ -7,8 +7,12 @@
namespace Chirper.Grains;

[Reentrant]
public sealed class ChirperAccount : Grain, IChirperAccount
public sealed class ChirperAccount(
[PersistentState(stateName: "account", storageName: "AccountState")] IPersistentState<ChirperAccountState> state,
ILogger<ChirperAccount> logger) : Grain, IChirperAccount
{
private static string GrainType => nameof(ChirperAccount);

/// <summary>
/// Size for the recently received message cache.
/// </summary>
Expand All @@ -29,28 +33,17 @@ public sealed class ChirperAccount : Grain, IChirperAccount
/// This list is not part of state and will not survive grain deactivation.
/// </summary>
private readonly HashSet<IChirperViewer> _viewers = new();
private readonly ILogger<ChirperAccount> _logger;
private readonly IPersistentState<ChirperAccountState> _state;

/// <summary>
/// Allows state writing to happen in the background.
/// </summary>
private Task? _outstandingWriteStateOperation;

public ChirperAccount(
[PersistentState(stateName: "account", storageName: "AccountState")] IPersistentState<ChirperAccountState> state,
ILogger<ChirperAccount> 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;
}
Expand All @@ -59,49 +52,49 @@ 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<ImmutableList<ChirperMessage>> 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());
}

public async ValueTask FollowUserIdAsync(string username)
{
_logger.LogInformation(
logger.LogInformation(
"{GrainType} {UserName} > FollowUserName({TargetUserName}).",
GrainType,
GrainKey,
Expand All @@ -111,7 +104,7 @@ public async ValueTask FollowUserIdAsync(string username)

await userToFollow.AddFollowerAsync(GrainKey, this.AsReference<IChirperSubscriber>());

_state.State.Subscriptions[username] = userToFollow;
state.State.Subscriptions[username] = userToFollow;

await WriteStateAsync();

Expand All @@ -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,
Expand All @@ -132,7 +125,7 @@ await GrainFactory.GetGrain<IChirperPublisher>(username)
.RemoveFollowerAsync(GrainKey);

// remove this publisher from the subscriptions list
_state.State.Subscriptions.Remove(username);
state.State.Subscriptions.Remove(username);

// save now
await WriteStateAsync();
Expand All @@ -142,10 +135,10 @@ await GrainFactory.GetGrain<IChirperPublisher>(username)
}

public ValueTask<ImmutableList<string>> GetFollowingListAsync() =>
ValueTask.FromResult(_state.State.Subscriptions.Keys.ToImmutableList());
ValueTask.FromResult(state.State.Subscriptions.Keys.ToImmutableList());

public ValueTask<ImmutableList<string>> GetFollowersListAsync() =>
ValueTask.FromResult(_state.State.Followers.Keys.ToImmutableList());
ValueTask.FromResult(state.State.Followers.Keys.ToImmutableList());

public ValueTask SubscribeAsync(IChirperViewer viewer)
{
Expand All @@ -162,50 +155,50 @@ public ValueTask UnsubscribeAsync(IChirperViewer viewer)
public ValueTask<ImmutableList<ChirperMessage>> 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());
}

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,
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion orleans/Chirper/Chirper.Grains/ChirperAccountState.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Chirper.Grains.Models;
using Chirper.Grains.Models;

namespace Chirper.Grains;

Expand Down
6 changes: 3 additions & 3 deletions orleans/Chirper/Chirper.Server/Chirper.Server.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Orleans.Server" Version="8.0.0" />
<PackageReference Include="OrleansDashboard" Version="8.0.0" />
<PackageReference Include="Microsoft.Orleans.Server" Version="9.0.1" />
<PackageReference Include="OrleansDashboard" Version="8.2.0" />
</ItemGroup>

<ItemGroup>
Expand Down
3 changes: 3 additions & 0 deletions orleans/Chirper/Chirper.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 15 additions & 0 deletions orleans/Chirper/Chirper.slnLaunch
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[
{
"Name": "Server + Client",
"Projects": [
{
"Path": "Chirper.Client\\Chirper.Client.csproj",
"Action": "Start"
},
{
"Path": "Chirper.Server\\Chirper.Server.csproj",
"Action": "Start"
}
]
}
]
Loading
Loading