From 8fc4a007a94505d895ddfb40062635ec9648e087 Mon Sep 17 00:00:00 2001 From: InCerry Date: Thu, 22 Jan 2026 20:51:53 +0800 Subject: [PATCH] update target frameworks to support .NET 7.0, 8.0, 9.0, and 10.0; adjust build configurations --- .github/workflows/build.yml | 10 +- .github/workflows/buildandtest.yml | 12 +- .github/workflows/release_stable.yml | 53 +++- .github/workflows/release_unstable.yml | 55 ++++- AGENTS.md | 231 ++++++++++++++++++ .../Benchmark.AspNetCore.BenchClient.csproj | 5 +- .../Program.cs | 77 ++++-- .../Benchmark.AspNetCore.csproj | 4 +- benchmarks/Benchmark/Benchmark.csproj | 4 +- samples/Sample/Sample.csproj | 4 +- .../Dispose.Scope.AspNetCore.csproj | 41 +++- .../Dispose.Scope.Tests.csproj | 3 +- 12 files changed, 436 insertions(+), 63 deletions(-) create mode 100644 AGENTS.md diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fefe846..6e84d8e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,11 +16,15 @@ jobs: os: [ windows-latest ] steps: - - uses: actions/checkout@v2 - - name: Setup .NET SDK + - uses: actions/checkout@v4 + - name: Setup .NET SDK 6.0.x - 10.0.x uses: actions/setup-dotnet@v3 with: dotnet-version: | + 10.0.x + 9.0.x + 8.0.x + 7.0.x 6.0.x - name: Show dotnet Version @@ -30,4 +34,4 @@ jobs: - name: Build with dotnet run: | - dotnet build --configuration Release D:\a\Dispose.Scope\Dispose.Scope\DisposeScope.sln \ No newline at end of file + dotnet build --configuration Release DisposeScope.sln -p:CheckEolTargetFramework=false diff --git a/.github/workflows/buildandtest.yml b/.github/workflows/buildandtest.yml index d8957a8..f6341d4 100644 --- a/.github/workflows/buildandtest.yml +++ b/.github/workflows/buildandtest.yml @@ -16,11 +16,15 @@ jobs: os: [ ubuntu-latest ] steps: - - uses: actions/checkout@v2 - - name: Setup .NET SDK + - uses: actions/checkout@v4 + - name: Setup .NET SDK 6.0.x - 10.0.x uses: actions/setup-dotnet@v3 with: dotnet-version: | + 10.0.x + 9.0.x + 8.0.x + 7.0.x 6.0.x - name: Show dotnet Version @@ -30,8 +34,8 @@ jobs: - name: Build with dotnet run: | - dotnet build --configuration Release /home/runner/work/Dispose.Scope/Dispose.Scope/DisposeScope.sln + dotnet build --configuration Release DisposeScope.sln -p:CheckEolTargetFramework=false - name: Run tests run: | - dotnet test /home/runner/work/Dispose.Scope/Dispose.Scope/tests/Dispose.Scope.Tests/Dispose.Scope.Tests.csproj + dotnet test tests/Dispose.Scope.Tests/Dispose.Scope.Tests.csproj -p:CheckEolTargetFramework=false diff --git a/.github/workflows/release_stable.yml b/.github/workflows/release_stable.yml index 4ead113..9be1d8d 100644 --- a/.github/workflows/release_stable.yml +++ b/.github/workflows/release_stable.yml @@ -12,23 +12,27 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 - - name: Setup .NET SDK + - uses: actions/checkout@v4 + - name: Setup .NET SDK 6.0.x - 10.0.x uses: actions/setup-dotnet@v3 with: dotnet-version: | + 10.0.x + 9.0.x + 8.0.x + 7.0.x 6.0.x - name: Build with dotnet - run: dotnet build --configuration Release /home/runner/work/Dispose.Scope/Dispose.Scope/DisposeScope.sln + run: dotnet build --configuration Release DisposeScope.sln -p:CheckEolTargetFramework=false - name: Pack with dotnet env: VERSION: ${{ github.ref_name }} - run: dotnet pack /home/runner/work/Dispose.Scope/Dispose.Scope/DisposeScope.sln --version-suffix $VERSION -o /home/runner/work/nugetpkgs -c Release --no-build + run: dotnet pack DisposeScope.sln --version-suffix $VERSION -o ./nugetpkgs -c Release --no-build - name: Upload artifact - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v4 with: name: nugetpkgs - path: /home/runner/work/nugetpkgs + path: ./nugetpkgs release_nuget: name: Release to Nuget @@ -37,14 +41,39 @@ jobs: steps: - name: Download build artifacts - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v4 with: name: nugetpkgs - - name: list nugetpkgs - run: ls nugetpkgs + path: . + - name: Debug downloaded artifacts + shell: bash + run: | + set -e + echo "PWD: $(pwd)" + echo "Top-level:" + ls -la + echo "NUPKG files:" + ls -la *.nupkg || true - name: Release + shell: bash run: | - for file in nugetpkgs/*.nupkg - do - dotnet nuget push $file -k ${{ secrets.NUGET_API_KEY }} --skip-duplicate -s https://www.nuget.org/api/v2/package + set -e + shopt -s nullglob + + # All non-library projects should have IsPackable=false, so we can push everything we packed. + files=( *.nupkg ) + if [ ${#files[@]} -eq 0 ]; then + echo "No .nupkg files found in workspace root." + exit 1 + fi + + for file in "${files[@]}"; do + case "$file" in + *.snupkg|*symbols*.nupkg) + echo "Skipping: $file" + continue + ;; + esac + echo "Pushing: $file" + dotnet nuget push "$file" -k "${{ secrets.NUGET_API_KEY }}" --skip-duplicate -s https://www.nuget.org/api/v2/package done diff --git a/.github/workflows/release_unstable.yml b/.github/workflows/release_unstable.yml index cc597ea..bc2aa53 100644 --- a/.github/workflows/release_unstable.yml +++ b/.github/workflows/release_unstable.yml @@ -13,23 +13,27 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 - - name: Setup .NET SDK + - uses: actions/checkout@v4 + - name: Setup .NET SDK 6.0.x - 10.0.x uses: actions/setup-dotnet@v3 with: dotnet-version: | + 10.0.x + 9.0.x + 8.0.x + 7.0.x 6.0.x - name: Build with dotnet - run: dotnet build --configuration Release /home/runner/work/Dispose.Scope/Dispose.Scope/DisposeScope.sln + run: dotnet build --configuration Release DisposeScope.sln -p:CheckEolTargetFramework=false - name: Pack with dotnet env: VERSION: ${{ github.ref_name }} - run: dotnet pack /home/runner/work/Dispose.Scope/Dispose.Scope/DisposeScope.sln --version-suffix $VERSION -o /home/runner/work/nugetpkgs -c Release --no-build + run: dotnet pack DisposeScope.sln --version-suffix $VERSION -o ./nugetpkgs -c Release --no-build - name: Upload artifact - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v4 with: name: nugetpkgs - path: /home/runner/work/nugetpkgs + path: ./nugetpkgs release_nuget: name: Release to Nuget @@ -38,14 +42,39 @@ jobs: steps: - name: Download build artifacts - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v4 with: name: nugetpkgs - - name: list nugetpkgs - run: ls nugetpkgs + path: . + - name: Debug downloaded artifacts + shell: bash + run: | + set -e + echo "PWD: $(pwd)" + echo "Top-level:" + ls -la + echo "NUPKG files:" + ls -la *.nupkg || true - name: Release + shell: bash run: | - for file in nugetpkgs/*.nupkg - do - dotnet nuget push $file -k ${{ secrets.NUGET_API_KEY }} --skip-duplicate -s https://www.nuget.org/api/v2/package - done \ No newline at end of file + set -e + shopt -s nullglob + + # All non-library projects should have IsPackable=false, so we can push everything we packed. + files=( *.nupkg ) + if [ ${#files[@]} -eq 0 ]; then + echo "No .nupkg files found in workspace root." + exit 1 + fi + + for file in "${files[@]}"; do + case "$file" in + *.snupkg|*symbols*.nupkg) + echo "Skipping: $file" + continue + ;; + esac + echo "Pushing: $file" + dotnet nuget push "$file" -k "${{ secrets.NUGET_API_KEY }}" --skip-duplicate -s https://www.nuget.org/api/v2/package + done diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..94bc6f9 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,231 @@ +# AGENTS.md + +This document describes how agentic coding tools should work in this repository. It summarizes how to build, test, and the coding conventions already used across the codebase. Follow these rules unless a change explicitly requires deviation. + +--- + +## Project Overview + +- Language/runtime: C# multi-targeted (netstandard2.0 + .NET 6/7/8/9, optional .NET 10) +- Solution file: `DisposeScope.sln` +- Core library: `Dispose.Scope` +- Tests: `tests/Dispose.Scope.Tests` +- Benchmarks: `benchmarks/Benchmark*` (BenchmarkDotNet) +- CI: GitHub Actions using `dotnet build` + `dotnet test` + +This is a low-level performance‑sensitive library focused on allocation control and pooling. Favor clarity, determinism, and minimal allocations. + +--- + +## Build, Test, and Lint Commands + +### Restore + +```bash +dotnet restore DisposeScope.sln +``` + +### Build (Release) + +```bash +dotnet build DisposeScope.sln -c Release +``` + +### Build (Debug) + +```bash +dotnet build DisposeScope.sln +``` + +### Run All Tests + +```bash +dotnet test +``` + +### Run Tests for a Single Project + +```bash +dotnet test tests/Dispose.Scope.Tests/Dispose.Scope.Tests.csproj +``` + +### Run a Single Test or Test Class + +```bash +dotnet test tests/Dispose.Scope.Tests/Dispose.Scope.Tests.csproj --filter FullyQualifiedName~ClassName +``` + +Examples: + +```bash +# single test class +dotnet test --filter FullyQualifiedName~DisposeScopeTests + +# single test method +dotnet test --filter FullyQualifiedName~DisposeScopeTests.BeginScope_ReusesPools +``` + +### Benchmarks + +Benchmarks are **not** part of CI and should be run manually. + +```bash +dotnet run -c Release --project benchmarks/Benchmark/Benchmark.csproj +``` + +Run benchmarks only in Release mode and on an idle machine. + +### Linting / Formatting + +- No standalone linter is configured +- Rely on compiler warnings and `.editorconfig` generated by SDK +- Do not auto‑reformat code unless changing the touched lines + +--- + +## Code Style Guidelines + +### General Principles + +- Optimize for readability first, allocations second +- Avoid unnecessary abstractions +- Prefer explicit code over clever code +- Public APIs are stable: breaking changes require strong justification + +--- + +### Formatting + +- 4‑space indentation +- One type per file +- Braces on new lines (Allman style) +- Keep methods short and single‑purpose +- Blank line between logical sections + +Example: + +```csharp +public void DoWork() +{ + if (condition) + { + Execute(); + } +} +``` + +--- + +### Imports (`using`) + +- Place `using` statements at the top of the file +- Group system namespaces first, then third‑party, then local +- Remove unused `using` directives + +Order: + +```csharp +using System; +using System.Collections.Generic; + +using Collections.Pooled; + +using Dispose.Scope; +``` + +Global usings may be introduced only if they clearly reduce repetition. + +--- + +### Naming Conventions + +- Namespaces: `Dispose.Scope.*` +- Types: `PascalCase` +- Methods: `PascalCase` +- Public members: `PascalCase` +- Private fields: `_camelCase` +- Locals and parameters: `camelCase` + +Avoid abbreviations unless they are well‑established (e.g., `vm`, `id`). + +--- + +### Types and Nullability + +- Nullable reference types are enabled +- Use `string?` / `T?` when null is valid +- Do not suppress nullability warnings without explanation +- Prefer explicit types over `var` when the type is not obvious + +Acceptable: + +```csharp +var list = new PooledList(); // obvious +``` + +Avoid: + +```csharp +var x = GetSomething(); // unclear +``` + +--- + +### Error Handling + +- Fail fast for programmer errors +- Use `ArgumentException` / `ArgumentNullException` for invalid inputs +- Do not swallow exceptions +- Avoid catching `Exception` unless rethrowing with context + +This is a library: do not log to console or swallow failures. + +--- + +### Performance and Allocation Rules + +- Avoid LINQ in hot paths unless measured +- Dispose pooled collections deterministically +- Prefer `using var` over explicit `Dispose()` calls +- Do not allocate in loops unless unavoidable +- Bench before and after performance‑critical changes + +DisposeScope usage: + +```csharp +using (_ = DisposeScope.BeginScope()) +{ + // pooled allocations +} +``` + +--- + +### Tests + +- Tests should be deterministic and allocation‑aware +- Prefer clear Arrange / Act / Assert sections +- Avoid time‑based assertions +- Do not rely on execution order + +--- + +## CI and Repository Rules + +- CI uses `.github/workflows/buildandtest.yml` +- CI builds with multiple SDKs (6/7/8/9/10) but the library remains `netstandard2.0` for broad compatibility +- Linux CI environment +- All tests must pass before merging + +There are no Cursor (`.cursor/`) or Copilot instruction files present in this repository at this time. + +--- + +## When Modifying Code + +- Touch only relevant files +- Preserve existing public API behavior +- Add tests for behavioral changes +- Do not reformat unrelated code + +If unsure, ask before making structural or breaking changes. diff --git a/benchmarks/Benchmark.AspNetCore.BenchClient/Benchmark.AspNetCore.BenchClient.csproj b/benchmarks/Benchmark.AspNetCore.BenchClient/Benchmark.AspNetCore.BenchClient.csproj index aca94f1..324bdfb 100644 --- a/benchmarks/Benchmark.AspNetCore.BenchClient/Benchmark.AspNetCore.BenchClient.csproj +++ b/benchmarks/Benchmark.AspNetCore.BenchClient/Benchmark.AspNetCore.BenchClient.csproj @@ -1,7 +1,10 @@ - net6.0 + net7.0;net8.0;net9.0 + false + true + $(TargetFrameworks);net10.0 enable false diff --git a/benchmarks/Benchmark.AspNetCore.BenchClient/Program.cs b/benchmarks/Benchmark.AspNetCore.BenchClient/Program.cs index 307f07d..58e95ea 100644 --- a/benchmarks/Benchmark.AspNetCore.BenchClient/Program.cs +++ b/benchmarks/Benchmark.AspNetCore.BenchClient/Program.cs @@ -1,4 +1,4 @@ -//Stress Test, Use httpclient 50 connection and 50 threads request http://localhost:8080/ +//Stress Test, Use httpclient 50 connection and 50 threads request http://localhost:8080/ using System; using System.Collections.Concurrent; @@ -13,18 +13,29 @@ const int port = 5120; -// if not windows os, return -if(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) == false) +// If not Windows, exit (PerformanceCounter is Windows-only). +if (!OperatingSystem.IsWindows()) { - Console.WriteLine("This program is only for windows os"); + Console.WriteLine("This program is only for Windows."); return; } -Process.GetCurrentProcess().ProcessorAffinity = (IntPtr) 0x3FC; +Process.GetCurrentProcess().ProcessorAffinity = (IntPtr)0x3FC; + +#if NET10_0 +const string serverTfm = "net10.0"; +#elif NET9_0 +const string serverTfm = "net9.0"; +#elif NET8_0 +const string serverTfm = "net8.0"; +#else +const string serverTfm = "net7.0"; +#endif + var usePooled = "1"; var startInfo = new ProcessStartInfo { - FileName = @"..\Benchmark.Aspnetcore\bin\Release\net6.0\Benchmark.Aspnetcore.exe", + FileName = $@"..\Benchmark.AspNetCore\bin\Release\{serverTfm}\Benchmark.AspNetCore.exe", RedirectStandardOutput = true, EnvironmentVariables = { @@ -35,9 +46,11 @@ {"Use_Pooled",usePooled} } }; -var process = Process.Start(startInfo); -var memoryUsage = new PerformanceCounter("Process", "Working Set - Private", process.ProcessName); -var cpuUsage = new PerformanceCounter("Process", "% Processor Time", process.ProcessName); + +using Process process = Process.Start(startInfo) ?? throw new InvalidOperationException("Failed to start Benchmark.AspNetCore."); +using var memoryUsage = new PerformanceCounter("Process", "Working Set - Private", process.ProcessName); +using var cpuUsage = new PerformanceCounter("Process", "% Processor Time", process.ProcessName); + var cpuUsageList = new List(); var memoryUsageList = new List(); var cancelSource = new CancellationTokenSource(); @@ -68,23 +81,32 @@ }); Console.WriteLine("Warm up done"); -new Thread(() => +var monitorThread = new Thread(() => { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + // Keep the platform guard inside the thread for analyzers. + if (!OperatingSystem.IsWindows()) { - while (token.IsCancellationRequested == false) - { - memoryUsageList.Add(memoryUsage.NextValue()); - cpuUsageList.Add(cpuUsage.NextValue()); - Thread.Sleep(50); - } + return; } -}).Start(); + + while (!token.IsCancellationRequested) + { + memoryUsageList.Add(memoryUsage.NextValue()); + cpuUsageList.Add(cpuUsage.NextValue()); + Thread.Sleep(50); + } +}) +{ + IsBackground = true +}; +monitorThread.Start(); + // wait for monitor start Thread.Sleep(1000); Console.WriteLine("Start stress test"); var count = 10000; + // test var totalTime = Stopwatch.StartNew(); var singleRequestTimes = new ConcurrentBag(); @@ -96,6 +118,10 @@ _ = await response.Content.ReadAsStringAsync(); singleRequestTimes.Add(sw.ElapsedMilliseconds); }); + +cancelSource.Cancel(); +monitorThread.Join(); + var totalTimeMs = totalTime.ElapsedMilliseconds; Console.WriteLine(totalTimeMs); @@ -103,15 +129,20 @@ Console.WriteLine(singleRequestTimes.Average()); Console.WriteLine(singleRequestTimes.Max()); Console.WriteLine(count / (totalTimeMs / 1000.0)); + // calculate p95 p99 var singleRequestTimesSortMin = singleRequestTimes.OrderBy(x => x).ToList(); var p95 = singleRequestTimesSortMin[(int)Math.Ceiling(singleRequestTimesSortMin.Count * 0.95) - 1]; var p99 = singleRequestTimesSortMin[(int)Math.Ceiling(singleRequestTimesSortMin.Count * 0.99) - 1]; Console.WriteLine(p95); Console.WriteLine(p99); -Console.WriteLine(cpuUsageList.Where(c => c > 0).Average()); -Console.WriteLine(cpuUsageList.Where(c => c > 0).Max()); -Console.WriteLine(memoryUsageList.Where(c => c > 0).Average()); -Console.WriteLine(memoryUsageList.Where(c => c > 0).Max()); + +var cpu = cpuUsageList.Where(c => c > 0).ToArray(); +Console.WriteLine(cpu.Length == 0 ? 0 : cpu.Average()); +Console.WriteLine(cpu.Length == 0 ? 0 : cpu.Max()); + +var memory = memoryUsageList.Where(c => c > 0).ToArray(); +Console.WriteLine(memory.Length == 0 ? 0 : memory.Average()); +Console.WriteLine(memory.Length == 0 ? 0 : memory.Max()); + Console.ReadLine(); -cancelSource.Cancel(); \ No newline at end of file diff --git a/benchmarks/Benchmark.AspNetCore/Benchmark.AspNetCore.csproj b/benchmarks/Benchmark.AspNetCore/Benchmark.AspNetCore.csproj index 3e62f1e..4d8ca45 100644 --- a/benchmarks/Benchmark.AspNetCore/Benchmark.AspNetCore.csproj +++ b/benchmarks/Benchmark.AspNetCore/Benchmark.AspNetCore.csproj @@ -1,7 +1,9 @@ - net6.0 + net7.0;net8.0;net9.0 + false + $(TargetFrameworks);net10.0 enable enable diff --git a/benchmarks/Benchmark/Benchmark.csproj b/benchmarks/Benchmark/Benchmark.csproj index 53a422c..eea124a 100644 --- a/benchmarks/Benchmark/Benchmark.csproj +++ b/benchmarks/Benchmark/Benchmark.csproj @@ -2,7 +2,9 @@ Exe - net6.0 + net7.0;net8.0;net9.0 + false + $(TargetFrameworks);net10.0 enable enable diff --git a/samples/Sample/Sample.csproj b/samples/Sample/Sample.csproj index eee0efa..6725bc4 100644 --- a/samples/Sample/Sample.csproj +++ b/samples/Sample/Sample.csproj @@ -2,7 +2,9 @@ Exe - net6.0 + net7.0;net8.0;net9.0 + false + $(TargetFrameworks);net10.0 enable enable diff --git a/src/Dispose.Scope.AspNetCore/Dispose.Scope.AspNetCore.csproj b/src/Dispose.Scope.AspNetCore/Dispose.Scope.AspNetCore.csproj index 738113f..63bce17 100644 --- a/src/Dispose.Scope.AspNetCore/Dispose.Scope.AspNetCore.csproj +++ b/src/Dispose.Scope.AspNetCore/Dispose.Scope.AspNetCore.csproj @@ -1,7 +1,12 @@ - netstandard2.0 + netstandard2.0;net6.0;net7.0;net8.0;net9.0 + $(TargetFrameworks);net10.0 + + latest + enable + true true snupkg @@ -32,9 +37,23 @@ - - + + + + + + + + + + + + + + + + @@ -49,6 +68,22 @@ + + + + + + + + + + + + + + + + diff --git a/tests/Dispose.Scope.Tests/Dispose.Scope.Tests.csproj b/tests/Dispose.Scope.Tests/Dispose.Scope.Tests.csproj index c1ff82a..4de4a72 100644 --- a/tests/Dispose.Scope.Tests/Dispose.Scope.Tests.csproj +++ b/tests/Dispose.Scope.Tests/Dispose.Scope.Tests.csproj @@ -1,7 +1,8 @@ - net6.0 + net7.0;net8.0;net9.0 + $(TargetFrameworks);net10.0 enable false