Skip to content

Commit

Permalink
feat: upgrade everything to .net 8.0 lts (#136)
Browse files Browse the repository at this point in the history
* feat: upgrade everything to .net 8.0 lts

* fix: upgrade to .net 8.0 causes changes to public api

* chore: more logging to diagnose the flaky test

* chore: removed obsolete code. added more logging to diagnose query channel test failure

* chore: scan dotnet code using sonarscanner tool

* fix: forgot to add the env vars to the workflow

* chore: attempt at fixing the sonar host url

* fix: no url when ending sonarscanner

* chore: simplified the use of buffers, the flow control channel and concurrent workflow

* chore: more logging, unique message names per test to minimize test collisions
  • Loading branch information
yreynhout authored Nov 29, 2023
1 parent 6f5209d commit e36b7a5
Show file tree
Hide file tree
Showing 34 changed files with 445 additions and 825 deletions.
8 changes: 7 additions & 1 deletion .config/dotnet-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"dotnet-outdated-tool": {
"version": "4.1.0",
"version": "4.6.0",
"commands": [
"dotnet-outdated"
]
Expand All @@ -13,6 +13,12 @@
"commands": [
"peek-at-changelog"
]
},
"dotnet-sonarscanner": {
"version": "5.15.0",
"commands": [
"dotnet-sonarscanner"
]
}
}
}
8 changes: 8 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,21 @@ jobs:
run: make ci
env:
AXONIQ_LICENSE: ${{ secrets.AXONIQ_LICENSE }}
SONAR_PROJECT_KEY: AxonIQ_axonserver-connector-dotnet
SONAR_ORGANIZATION: axoniq
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: https://sonarcloud.io
# run the CD build (on master only)
- name: make cd
if: github.ref == 'refs/heads/master'
run: make cd
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NUGET_APIKEY: ${{ secrets.NUGET_APIKEY }}
SONAR_PROJECT_KEY: AxonIQ_axonserver-connector-dotnet
SONAR_ORGANIZATION: axoniq
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: https://sonarcloud.io
- name: publish test results
uses: EnricoMi/publish-unit-test-result-action/composite@v2
if: always()
Expand Down
2 changes: 1 addition & 1 deletion global.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"sdk": {
"version": "6.0.417"
"version": "8.0.100"
}
}
22 changes: 19 additions & 3 deletions makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ LICENSE = $(shell cat axoniq.license)
ci:
dotnet tool restore
dotnet restore
ifneq ("$(SONAR_TOKEN)","")
dotnet sonarscanner begin -k:"$(SONAR_PROJECT_KEY)" -o:"$(SONAR_ORGANIZATION)" -d:sonar.token="$(SONAR_TOKEN)" -d:sonar.host.url="$(SONAR_HOST_URL)"
endif
dotnet build --configuration Release --no-restore
ifneq ("$(SONAR_TOKEN)","")
dotnet sonarscanner end -d:sonar.token="$(SONAR_TOKEN)"
endif
dotnet test --configuration Release --no-build --no-restore test/AxonIQ.AxonServer.Connector.Tests --logger "trx;logfilename=connector_tests.trx"
dotnet test --configuration Release --no-build --no-restore test/AxonIQ.AxonServerIntegrationTests --filter "Surface=AdminChannel" --logger "trx;logfilename=server_integration_tests_admin_channel.trx"
dotnet test --configuration Release --no-build --no-restore test/AxonIQ.AxonServerIntegrationTests --filter "Surface=ControlChannel" --logger "trx;logfilename=server_integration_tests_control_channel.trx"
Expand All @@ -15,14 +21,24 @@ ci:
cd:
dotnet tool restore
dotnet restore
ifneq ("$(SONAR_TOKEN)","")
dotnet sonarscanner begin -k:"$(SONAR_PROJECT_KEY)" -o:"$(SONAR_ORGANIZATION)" -d:sonar.token="$(SONAR_TOKEN)" -d:sonar.host.url="$(SONAR_HOST_URL)"
endif
dotnet build --configuration Release --no-restore
ifneq ("$(SONAR_TOKEN)","")
dotnet sonarscanner end -d:sonar.token="$(SONAR_TOKEN)"
endif
dotnet pack --configuration Release --no-build --no-restore --include-symbols --include-source src/AxonIQ.AxonServer.Connector/AxonIQ.AxonServer.Connector.csproj -o .artifacts/
dotnet pack --configuration Release --no-build --no-restore --include-symbols --include-source src/AxonIQ.AxonServer.Embedded/AxonIQ.AxonServer.Embedded.csproj -o .artifacts/
ifneq ("$(NUGET_APIKEY)","")
dotnet nuget push .artifacts/*.nupkg --api-key $(NUGET_APIKEY) --source https://api.nuget.org/v3/index.json --skip-duplicate --no-symbols
endif
ifneq ("$(GITHUB_TOKEN)","")
dotnet nuget push .artifacts/*.nupkg --api-key $(GITHUB_TOKEN) --source https://nuget.pkg.github.com/AxonIQ/index.json --skip-duplicate --no-symbols

endif

install-license:
dotnet user-secrets set "axoniq.license" "${LICENSE}" -p test/AxonIQ.AxonClusterIntegrationTests/AxonIQ.AxonClusterIntegrationTests.csproj

remove-dangling-containers:
docker rm -f $(docker ps -a --filter "name=axonserver-" -q)
docker rm -f $$(docker ps -a --filter "name=axonserver-" -q)
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>AxonIQ.AxonServer.Connector</RootNamespace>
Expand All @@ -27,17 +27,17 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.VisualStudio.Threading" Version="17.8.14" />
<PackageReference Include="MinVer" Version="4.3.0" PrivateAssets="all" />
<PackageReference Include="Google.Protobuf" Version="3.24.4" />
<PackageReference Include="Grpc.Net.Client" Version="2.58.0" />
<PackageReference Include="Google.Protobuf" Version="3.25.1" />
<PackageReference Include="Grpc.Net.Client" Version="2.59.0" />
<PackageReference Include="Grpc.Tools" Version="2.59.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="OpenTelemetry" Version="1.6.0" />
<PackageReference Include="OpenTelemetry.Api" Version="1.6.0" />
<PackageReference Include="PooledAwait" Version="1.0.49" />
</ItemGroup>

<ItemGroup>
Expand Down
35 changes: 21 additions & 14 deletions src/AxonIQ.AxonServer.Connector/BufferedQueryResponseChannel.cs
Original file line number Diff line number Diff line change
@@ -1,32 +1,39 @@
using System.Threading.Channels;
using Io.Axoniq.Axonserver.Grpc;
using Io.Axoniq.Axonserver.Grpc.Query;
using Microsoft.Extensions.Logging;

namespace AxonIQ.AxonServer.Connector;

internal class BufferedQueryResponseChannel : IQueryResponseChannel
internal class BufferedQueryResponseChannel(ChannelId id, Channel<QueryReply> channel, ILogger logger) : IQueryResponseChannel
{
private readonly Channel<QueryReply> _channel;
private long _completed = Completed.No;

public BufferedQueryResponseChannel(Channel<QueryReply> channel)
{
_channel = channel;
}

public ValueTask SendAsync(QueryResponse response, CancellationToken cancellationToken)
{
return _channel.Writer.WriteAsync(new QueryReply.Send(response), cancellationToken);
logger.LogDebug("Sending query response on channel {ChannelId}: {Response}", id.ToString(), response);
return channel.Writer.WriteAsync(new QueryReply.Send(id, response), cancellationToken);
}

public async ValueTask CompleteAsync(CancellationToken cancellationToken)
public ValueTask CompleteAsync(CancellationToken cancellationToken)
{
await _channel.Writer.WriteAsync(new QueryReply.Complete(), cancellationToken);
_channel.Writer.Complete();
if(Interlocked.CompareExchange(ref _completed, Completed.Yes, Completed.No) == Completed.No)
{
logger.LogDebug("Completing query response channel {ChannelId}", id.ToString());
return channel.Writer.WriteAsync(new QueryReply.Complete(id), cancellationToken);
}
logger.LogDebug("Query response channel {ChannelId} already completed", id.ToString());
return ValueTask.CompletedTask;
}

public async ValueTask CompleteWithErrorAsync(ErrorMessage error, CancellationToken cancellationToken)
public ValueTask CompleteWithErrorAsync(ErrorMessage error, CancellationToken cancellationToken)
{
await _channel.Writer.WriteAsync(new QueryReply.CompleteWithError(error), cancellationToken);
_channel.Writer.Complete();
if (Interlocked.CompareExchange(ref _completed, Completed.Yes, Completed.No) == Completed.No)
{
logger.LogDebug("Completing query response channel {ChannelId} with {Error}", id.ToString(), error);
return channel.Writer.WriteAsync(new QueryReply.CompleteWithError(id, error), cancellationToken);
}
logger.LogDebug("Query response channel {ChannelId} already completed", id.ToString());
return ValueTask.CompletedTask;
}
}
54 changes: 47 additions & 7 deletions src/AxonIQ.AxonServer.Connector/ChannelExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,45 +1,85 @@
using System.Threading.Channels;
using Microsoft.Extensions.Logging;

namespace AxonIQ.AxonServer.Connector;

internal static class ChannelExtensions
{
public static async Task PipeTo<T>(this Channel<T> source, Channel<T> destination, CancellationToken cancellationToken = default)
public static async Task PipeTo<T>(this Channel<T> source, Channel<T> destination, ILogger logger, CancellationToken cancellationToken = default)
{
var count = 0L;
try
{
while (await source.Reader.WaitToReadAsync(cancellationToken))
{
while (source.Reader.TryRead(out var item))
{
count++;
await destination.Writer.WriteAsync(item, cancellationToken);
}
}

logger.LogDebug("Piping {Count} messages from source to destination completed gracefully", count);
}
catch (ChannelClosedException)
{
// ignore
logger.LogDebug("Channel was closed while piping messages from source to destination");
}
catch (OperationCanceledException exception) when (exception.CancellationToken == cancellationToken)
{
// ignore
logger.LogDebug("Operation was cancelled while piping messages from source to destination");
}
catch (Exception exception)
{
logger.LogCritical(exception, "Unexpected exception while piping messages from source to destination");
}
}

public static Task PipeFromAll<T>(this Channel<T> destination, IReadOnlyCollection<Channel<T>> sources, CancellationToken ct)
public static Task PipeFromAll<T>(this Channel<T> destination, IReadOnlyCollection<Channel<T>> sources, ILogger logger, CancellationToken ct)
{
return sources.Count switch
{
0 => Task.Run(() => destination.Writer.Complete(), ct),
0 => Task.Run(() =>
{
logger.LogDebug("No sources to pipe to destination from");
if(!destination.Writer.TryComplete())
{
logger.LogDebug("The destination channel was already completed while piping messages from source to destination");
}
}, ct),
1 => Task.Run(async () =>
{
await sources.Single().PipeTo(destination, ct);
destination.Writer.Complete();
logger.LogDebug("One source to pipe to destination from");
try
{
await sources.Single().PipeTo(destination, logger, ct);
}
catch (Exception exception)
{
logger.LogCritical(exception, "Unexpected exception while piping messages from source to destination");
}
if(!destination.Writer.TryComplete())
{
logger.LogDebug("The destination channel was already completed while piping messages from source to destination");
}
}, ct),
_ => Task.Run(async () =>
{
await Task.WhenAll(sources.Select(channel => channel.PipeTo(destination, ct)));
destination.Writer.Complete();
logger.LogDebug("Many sources to pipe to destination from");
try
{
await Task.WhenAll(sources.Select(channel => channel.PipeTo(destination, logger, ct)));
}
catch (Exception exception)
{
logger.LogCritical(exception, "Unexpected exception while piping messages from source to destination");
}
if(!destination.Writer.TryComplete())
{
logger.LogDebug("The destination channel was already completed while piping messages from source to destination");
}
}, ct)
};
}
Expand Down
30 changes: 30 additions & 0 deletions src/AxonIQ.AxonServer.Connector/ChannelId.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
namespace AxonIQ.AxonServer.Connector;

internal readonly struct ChannelId : IEquatable<ChannelId>
{
public static ChannelId New() => new(Guid.NewGuid().ToString("D"));

private readonly string _value;

public ChannelId(string value)
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}

if (value == "")
{
throw new ArgumentException("The channel identifier can not be empty.", nameof(value));
}

_value = value;
}

public bool Equals(ChannelId other) => other._value.Equals(_value);
public override bool Equals(object? obj) => obj is ChannelId other && other.Equals(this);
public override int GetHashCode() => HashCode.Combine(_value);
public override string ToString() => _value;
public static bool operator ==(ChannelId left, ChannelId right) => left.Equals(right);
public static bool operator !=(ChannelId left, ChannelId right) => !left.Equals(right);
}
Loading

0 comments on commit e36b7a5

Please sign in to comment.