Skip to content
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
40 changes: 40 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<Project>
<PropertyGroup>
<!-- Performance optimizations -->
<TieredCompilation>true</TieredCompilation>
<TieredPGO>true</TieredPGO>
<UseSystemResourceKeys>true</UseSystemResourceKeys>
<OptimizationPreference>Speed</OptimizationPreference>

<!-- Memory optimizations -->
<ServerGarbageCollection>true</ServerGarbageCollection>
<ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
<RetainVMGarbageCollection>true</RetainVMGarbageCollection>

<!-- Build optimizations -->
<DebugType Condition="'$(Configuration)' == 'Release'">none</DebugType>
<DebugSymbols Condition="'$(Configuration)' == 'Release'">false</DebugSymbols>
<Optimize Condition="'$(Configuration)' == 'Release'">true</Optimize>

<!-- Trimming and AOT preparation -->
<PublishTrimmed Condition="'$(Configuration)' == 'Release'">true</PublishTrimmed>
<TrimMode Condition="'$(Configuration)' == 'Release'">link</TrimMode>
<PublishReadyToRun Condition="'$(Configuration)' == 'Release'">true</PublishReadyToRun>

<!-- Assembly optimizations -->
<IlcOptimizationPreference>Speed</IlcOptimizationPreference>
<IlcFoldIdenticalMethodBodies>true</IlcFoldIdenticalMethodBodies>
</PropertyGroup>

<!-- Release-specific optimizations -->
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<DefineConstants>$(DefineConstants);RELEASE_OPTIMIZATIONS</DefineConstants>
<WarningsAsErrors />
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>

<!-- Package references that should be consistent across all projects -->
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.9" />
</ItemGroup>
</Project>
31 changes: 26 additions & 5 deletions OpenWorker.AuthServer/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,19 +1,40 @@
FROM mcr.microsoft.com/dotnet/runtime:9.0 AS base
FROM mcr.microsoft.com/dotnet/runtime:9.0-alpine AS base
USER $APP_UID
WORKDIR /app
# Install culture support for better performance
RUN apk add --no-cache icu-libs
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false

FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
FROM mcr.microsoft.com/dotnet/sdk:9.0-alpine AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src

# Copy project files and restore dependencies with better caching
COPY ["Directory.Build.props", "./"]
COPY ["OpenWorker.AuthServer/OpenWorker.AuthServer.csproj", "OpenWorker.AuthServer/"]
RUN dotnet restore "OpenWorker.AuthServer/OpenWorker.AuthServer.csproj"
COPY ["OpenWorker.AuthServer.Gameplay/OpenWorker.AuthServer.Gameplay.csproj", "OpenWorker.AuthServer.Gameplay/"]
COPY ["OpenWorker.DependencyInjection/OpenWorker.DependencyInjection.csproj", "OpenWorker.DependencyInjection/"]
COPY ["OpenWorker.Hotspot/OpenWorker.Hotspot.csproj", "OpenWorker.Hotspot/"]
COPY ["OpenWorker.Domain/OpenWorker.Domain.csproj", "OpenWorker.Domain/"]
COPY ["OpenWorker.Persistence/OpenWorker.Persistence.csproj", "OpenWorker.Persistence/"]
COPY ["OpenWorker.Batch/OpenWorker.Batch.csproj", "OpenWorker.Batch/"]
COPY ["OpenWorker.Extensions/OpenWorker.Extensions.csproj", "OpenWorker.Extensions/"]

RUN dotnet restore "OpenWorker.AuthServer/OpenWorker.AuthServer.csproj" --runtime alpine-x64

COPY . .
WORKDIR "/src/OpenWorker.AuthServer"
RUN dotnet build "OpenWorker.AuthServer.csproj" -c $BUILD_CONFIGURATION -o /app/build
RUN dotnet build "OpenWorker.AuthServer.csproj" -c $BUILD_CONFIGURATION -o /app/build --no-restore

FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "OpenWorker.AuthServer.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
RUN dotnet publish "OpenWorker.AuthServer.csproj" -c $BUILD_CONFIGURATION -o /app/publish \
--no-restore \
--runtime alpine-x64 \
--self-contained false \
/p:PublishTrimmed=true \
/p:PublishReadyToRun=true \
/p:PublishSingleFile=false

FROM base AS final
WORKDIR /app
Expand Down
65 changes: 65 additions & 0 deletions OpenWorker.Extensions/PerformanceExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System.Runtime;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace OpenWorker.Extensions;

public static class PerformanceExtensions
{
/// <summary>
/// Adds performance optimizations to the service collection
/// </summary>
public static IServiceCollection AddPerformanceOptimizations(this IServiceCollection services)
{
// Configure GC settings for server workloads
GCSettings.LatencyMode = GCLatencyMode.Batch;

// Add performance monitoring
services.AddHostedService<PerformanceMonitoringService>();

return services;
}

/// <summary>
/// Configures runtime optimizations
/// </summary>
public static void ConfigureRuntimeOptimizations()
{
// Enable server GC if not already configured
if (!GCSettings.IsServerGC)
{
// This can only be set via configuration or environment variables
Environment.SetEnvironmentVariable("DOTNET_gcServer", "1");
}

// Configure thread pool for high-throughput scenarios
ThreadPool.SetMinThreads(
workerThreads: Environment.ProcessorCount * 4,
completionPortThreads: Environment.ProcessorCount * 2
);

// Set process priority for better responsiveness
try
{
Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;
}
catch
{
// Ignore if we don't have permissions
}
}

/// <summary>
/// Optimizes memory allocation patterns
/// </summary>
public static void OptimizeMemoryAllocation()
{
// Pre-allocate some memory to reduce initial allocation overhead
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

// Configure large object heap compaction
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
}
}
119 changes: 119 additions & 0 deletions OpenWorker.Extensions/PerformanceMonitoringService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
using System.Diagnostics;
using System.Runtime;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace OpenWorker.Extensions;

public class PerformanceMonitoringService : BackgroundService
{
private readonly ILogger<PerformanceMonitoringService> _logger;
private readonly PerformanceCounter _cpuCounter;
private readonly Process _currentProcess;

public PerformanceMonitoringService(ILogger<PerformanceMonitoringService> logger)
{
_logger = logger;
_currentProcess = Process.GetCurrentProcess();

// Initialize CPU counter if available
try
{
_cpuCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total");
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Could not initialize CPU performance counter");
}
}

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
LogPerformanceMetrics();
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
}
catch (OperationCanceledException)
{
break;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in performance monitoring");
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
}
}
}

private void LogPerformanceMetrics()
{
try
{
// Memory metrics
var gcMemory = GC.GetTotalMemory(false);
var workingSet = _currentProcess.WorkingSet64;
var privateMemory = _currentProcess.PrivateMemorySize64;

// GC metrics
var gen0Collections = GC.CollectionCount(0);
var gen1Collections = GC.CollectionCount(1);
var gen2Collections = GC.CollectionCount(2);

// CPU metrics
var cpuTime = _currentProcess.TotalProcessorTime;
var cpuUsage = _cpuCounter?.NextValue() ?? 0;

// Thread metrics
var threadCount = _currentProcess.Threads.Count;

_logger.LogInformation(
"Performance Metrics - " +
"GC Memory: {GCMemory:N0} bytes, " +
"Working Set: {WorkingSet:N0} bytes, " +
"Private Memory: {PrivateMemory:N0} bytes, " +
"GC Collections (Gen0/Gen1/Gen2): {Gen0}/{Gen1}/{Gen2}, " +
"CPU Usage: {CpuUsage:F1}%, " +
"CPU Time: {CpuTime}, " +
"Thread Count: {ThreadCount}",
gcMemory,
workingSet,
privateMemory,
gen0Collections,
gen1Collections,
gen2Collections,
cpuUsage,
cpuTime,
threadCount);

// Log warnings for concerning metrics
if (gcMemory > 500_000_000) // 500MB
{
_logger.LogWarning("High GC memory usage detected: {GCMemory:N0} bytes", gcMemory);
}

if (gen2Collections > 10)
{
_logger.LogWarning("High Gen2 GC collection count: {Gen2Collections}", gen2Collections);
}

if (threadCount > 100)
{
_logger.LogWarning("High thread count detected: {ThreadCount}", threadCount);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error collecting performance metrics");
}
}

public override void Dispose()
{
_cpuCounter?.Dispose();
_currentProcess?.Dispose();
base.Dispose();
}
}
10 changes: 9 additions & 1 deletion OpenWorker.Hotspot/ServerService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Buffers;
using System.Buffers;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
Expand Down Expand Up @@ -78,6 +78,14 @@ private async Task SessionLoopAsync(TcpClient socket, CancellationToken cancella
while (cancellationToken.IsCancellationRequested is false)
{
var header = new MessageHeader(reader);

// Validate content length to prevent excessive memory allocation
if (header.ContentLength > short.MaxValue || header.ContentLength < 0)
{
logger.LogWarning("Invalid content length: {ContentLength}", header.ContentLength);
break;
}

var buffer = memory.Memory[..header.ContentLength];

await stream.ReadExactlyAsync(buffer, cancellationToken).ConfigureAwait(false);
Expand Down
Loading
Loading