Skip to content
Merged
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
41 changes: 40 additions & 1 deletion .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ on:
schedule:
- cron: "0 2 * * *"
workflow_dispatch:
inputs:
release_soak:
description: Run the five-hour release soak alongside the other validation jobs
type: boolean
required: false
default: false

jobs:
codec-compatibility:
Expand Down Expand Up @@ -68,6 +74,8 @@ jobs:
timeout-minutes: 35
env:
TESTINGPLATFORM_TELEMETRY_OPTOUT: '1'
# Race tests create dedicated workers; bound simultaneous cases on hosted runners.
TUNIT_MAX_PARALLEL_TESTS: ${{ matrix.os == 'macos-latest' && '8' || '32' }}
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand Down Expand Up @@ -185,7 +193,7 @@ jobs:
retention-days: 30

chaos-soak:
if: github.event_name != 'push'
if: github.event_name != 'push' && !inputs.release_soak
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -253,3 +261,34 @@ jobs:
path: artifacts/p3-generated-abi/dynamic-module-soak
if-no-files-found: warn
retention-days: 14

release-soak:
if: github.event_name == 'workflow_dispatch' && inputs.release_soak
strategy:
fail-fast: false
matrix:
transport: [tcp, sharedmemory]
runs-on: ubuntu-latest
timeout-minutes: 330
env:
SHARPLINK_SOAK_DURATION: 5h
SHARPLINK_SOAK_TRANSPORT: ${{ matrix.transport }}
SHARPLINK_SOAK_OUTPUT: artifacts/chaos/release-5h-${{ matrix.transport }}.json
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Setup .NET CI environment
uses: ./.github/actions/setup-dotnet

- name: Five-hour release soak
run: ./eng/run-release-soak.sh

- name: Upload release soak evidence
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-soak-5h-${{ matrix.transport }}-${{ github.sha }}
path: artifacts/chaos
if-no-files-found: error
retention-days: 30
1 change: 1 addition & 0 deletions .github/workflows/pr-fast.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ jobs:
timeout-minutes: 5
env:
TESTINGPLATFORM_TELEMETRY_OPTOUT: '1'
TUNIT_MAX_PARALLEL_TESTS: '32'
steps:
- name: Start Fast validation timing
run: echo "FAST_STARTED_AT=$(date +%s)" >> "$GITHUB_ENV"
Expand Down
11 changes: 11 additions & 0 deletions .github/workflows/release-gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ jobs:
timeout-minutes: 45
env:
TESTINGPLATFORM_TELEMETRY_OPTOUT: '1'
# Preserve per-test race concurrency without exhausting hosted native threads.
TUNIT_MAX_PARALLEL_TESTS: ${{ matrix.os == 'macos-latest' && '8' || '32' }}
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand Down Expand Up @@ -160,6 +162,15 @@ jobs:
- name: Verify NuGet package contracts
uses: ./.github/actions/verify-packages

- name: Install architecture guard prerequisite
run: sudo apt-get update && sudo apt-get install -y ripgrep

- name: Verify reviewed public API and release versions
run: |
bash ./eng/verify-public-api.sh
python3 ./eng/verify-release-versions.py
bash ./eng/verify-runtime-architecture.sh

- name: Verify Generated ABI Package Mixing
run: ./eng/verify-generated-abi-mixing.sh

Expand Down
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@

## [Unreleased]

## [2.0.0] - 2026-09-13

### Release boundaries

- Added immutable operation-specific runtime configuration update results with stable failure codes; callers can distinguish rejected updates from committed configuration without parsing exception messages.
- Added desired-session snapshots and bounded connection refresh across fixed, static, and dynamic pools; publication, readiness, old-session retirement, and current negotiated settings remain distinct outcomes.
- Froze all eight shipping package API snapshots and calibrated version axes against published 1.1.1. The first-release diagnostic schema is 1; Generated API is 4, Protocol minor is 4, and Contract Manifest format is 2.

### Changed

- Runtime multi-cluster `AddClusterAsync`, `ReplaceClusterAsync`, and `RemoveClusterAsync` now return operation-specific immutable results with stable `SharpLinkClusterMutationFailureCode` values for expected control-plane rejection. Add reports publication only (not readiness); Replace separates pre-publication failure from committed publication plus old-child cleanup; Remove preserves cleanup outcome while adding structured `NotFound`/`Busy`/`LifecycleClosed`. Programmer/configuration errors, cancellation, and unexpected failures remain exceptions.
Expand Down Expand Up @@ -74,7 +82,7 @@
configuration is also single-choice: mixing or repeating `UseTransport`, `UseEndpoint(s)`, and
`UseEndpointResolver` now fails at the second configuration call instead of replacing or delaying
validation until Build. See [`doc/runtime-phase-11-build-plan.md`](doc/runtime-phase-11-build-plan.md).
- Public `RpcSession` error-send extensions now accept only an already structured `SharpLinkException`; callers that use these low-level protocol helpers must map arbitrary exceptions before encoding them.
- Internal error-send helpers accept already structured `SharpLinkException`; public Session helpers are removed. Applications customize exception mapping through the Server SPI.
- The PipeReader/PipeWriter/disconnect/isConnected `RpcSession` constructor is removed without an obsolete or forwarding shim. Custom transports must implement `ITransportConnection` and expose themselves through a client factory or server listener; the Session no longer completes caller-supplied pipelines or invokes lifecycle callbacks.
- The incomplete `RpcSession` constructors and the `BindRuntimeContext` follow-up call are removed instead of retained as forwarding shims. Internal Client/Server construction also requires an already-built Runtime Context; no process-wide Context or codec fallback remains.
- Assemblies generated by SharpLink 1.1.x use Generated API 3 and cannot be loaded into a SharpLink 2.0 process. Rebuild every contract, service, and plugin assembly with the 2.0 SDK after deleting stale `bin` and `obj` outputs, and do not mix 1.1.x and 2.0 SharpLink packages in one process. Intermediate development-only ABI artifacts are outside the release compatibility contract.
Expand Down
3 changes: 3 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
<!-- Nuget包信息 -->
<PropertyGroup>
<VersionPrefix>2.0.0</VersionPrefix>
<!-- The first 2.0 release is frozen by eng/public-api. Once published, every later
2.x package must also preserve the available 2.0.0 binary baseline. -->
<PackageValidationBaselineVersion Condition="'$(PackageValidationBaselineVersion)' == '' and $([MSBuild]::VersionGreaterThan('$(VersionPrefix)', '2.0.0')) and $([MSBuild]::VersionLessThan('$(VersionPrefix)', '3.0.0'))">2.0.0</PackageValidationBaselineVersion>
<Authors>sunsi</Authors>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
Expand Down
5 changes: 3 additions & 2 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
</PropertyGroup>
<ItemGroup>
<!-- 分析器依赖 -->
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="5.6.0">
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="5.9.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageVersion>
Expand All @@ -20,6 +20,7 @@
<PackageVersion Include="SharpPack" Version="[1.1.0]" />
<PackageVersion Include="ZstdSharp.Port" Version="[0.8.8]" />
<PackageVersion Include="BenchmarkDotNet" Version="0.15.8" />
<PackageVersion Include="PublicApiGenerator" Version="11.5.4" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.11" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.11" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions" Version="10.0.2" />
Expand All @@ -29,6 +30,6 @@
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="10.0.11" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="10.0.11" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.11" />
<PackageVersion Include="TUnit" Version="1.65.68" />
<PackageVersion Include="TUnit" Version="1.66.16" />
</ItemGroup>
</Project>
54 changes: 54 additions & 0 deletions doc/adr/0001-2.0-public-api-and-packages.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# ADR 0001: 2.0 public API 与包边界

Status: Accepted
Date: 2026-09-13
Tracking: #86 / #67

## Context

2.0 在已发布 1.1.1 之后提供一次 source/generated ABI 破坏性升级。生命周期重构已将
Session、StreamManager、dispatcher、构建 transaction、manifest publication 与 counter owner
收进内部实现。本轮必须固定可支持的公开边界,避免用包拆分代替所有权设计。

## Decision

| Surface | 分类与保留理由 | 所有权/使用边界 |
| --- | --- | --- |
| Client、Server、Hosting、metadata、readiness、health、support snapshot、control-plane result | 业务调用、配置与诊断 | 应用创建 owner;等待 Stop/Dispose 和 mutation cleanup;snapshot 不授予 engine 控制权。 |
| Codec/adapter、transport factory/listener/connection、resolver、interceptor、auth、exception mapping、retry SPI | 可由外部实现的扩展契约 | 由各 SPI XML 文档定义 caller/framework ownership;不暴露 Session 或状态容器。 |
| `SharpLinkRuntimeContext` / Builder | 独立 codec/协议工具可实际使用的 public service container | 业务应用优先 Client/Server Builder;独立创建者负责 Dispose。无 Default、Bind 或公开 manifest mutator;传入 TimeProvider 不转移所有权。 |
| `IRpcChannel`、client-stream sink、`IRpcStub`、`IRpcGeneratedServerBridge`、generated codec factory/descriptor/manifest、contract codec provider resolver | API 4 generated infrastructure | 跨用户生成程序集必须 public;依赖 Abstractions 的窄接口,不依赖 concrete Runtime Context 或内部 engine。只能在注册/启动校验版本,不能每 RPC 查询 Catalog。 |
| `SharpLinkGeneratedAssemblyCatalog` | generated bootstrap infrastructure | `EditorBrowsable(Never)` 与 XML 注明用途;weak catalog 不拥有应用 ALC。动态注册使用 owner 的 assembly registry。 |
| Protocol records、frame codec、buffer pool | 协议工具与有界 codec SPI 支持 | 数据和编码工具不授予 live Session mutator;调用方遵守 buffer 租借/归还约定。 |
| Session、StreamManager、raw dispatcher、Default Context、旧 ABI constructor/adapter | 内部机制或已删除 | 不作为第三方扩展 SPI;不保留 forwarding shim。 |

全部 public/protected 成员(包括 SDK type forwards)在
[`eng/public-api/2.0.0`](../../eng/public-api/2.0.0) 固定;
[`1.1.1 diff`](../../eng/public-api/1.1.1-to-2.0.0.diff) 来自实际已发布 NuGet 包,下载摘要记录在
[`provenance.json`](../../eng/public-api/1.1.1/provenance.json)。上述分类覆盖业务、配置/诊断、可实现 SPI
与 generated/protocol infrastructure,后者公开不代表可直接控制 Runtime engine。

SharedMemory 暂留 Runtime。当前实现使用 BCL memory mapping/pipes 和平台调用,没有独立外部
NuGet 依赖可通过拆包消除;拆分会改变 transport construction/平台测试与包图。没有量化的
NativeAOT rooting/体积收益,因此不声称拆分有 AOT 收益,也不扩大本轮范围。
SharedMemory 的平台及实验性限制继续由 [transports](../transports.md) 约束。

Telemetry 保持各 owner 采集、Runtime 共享机制的现状。Activity/Meter 是 BCL API;Runtime 的
Logging.Abstractions 引用也被非 telemetry 的生命周期日志使用,拆出 Telemetry 不会直接移除它。
本轮不新增 telemetry NuGet 包;若后续有可测的依赖或 AOT 收益,再独立 ADR。
独立算法包 `SharpLink.Compression.Zstd` 保持外部依赖隔离,Core 只含算法无关 SPI/策略。

## Validation and consequences

Release Gate 从实际八个 nupkg 提取 net10.0 assembly,生成完整 API 并与 reviewed snapshot 比较。
改变签名、删除成员或新增 public surface 必须显式更新并 review snapshot;API 的无害编译器
state-machine attribute 不参与比较。版本、程序集/package identity 由独立 package/version gate 检查。
2.0 首发不要求 1.1.1 的 binary compatibility;未来 2.x 使用 2.0.0 package-validation baseline。
2.0 snapshot 是首发 gate,不能用未发布的 2.0 包恢复作为首发前置条件。

`verify-runtime-architecture.sh` 检查已退休的 Default/Bind/engine/adapter pattern,并固定剩余
Task.Yield 和 UTC site 的位置、数量和理由。它不是并发正确性的证明:rollback/counter winner、
drain、ALC、fake-time、Streaming、Chaos 仍由对应测试与发布验证覆盖。

代价是 Runtime 仍暴露独立 context 与协议工具,基础设施 API 仍需 major/ABI 管理;我们接受这项
成本,以保留真实可实现用例并避免新 wrapper/interface 层。迁移条目见 [migration](../migration.md)。
4 changes: 4 additions & 0 deletions doc/adr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,7 @@ Review ADR 时重点确认:
- ADR 是否避免复制会演进的阈值、允许边和完整工具规则。

从 [`0000-template.md`](0000-template.md) 复制最小结构即可;不适用的可选小节可以删除。

## Accepted records

- [0001: 2.0 public API 与包边界](0001-2.0-public-api-and-packages.md)
8 changes: 8 additions & 0 deletions doc/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,11 @@ Streaming 沿用相同边界:Runtime 拥有 frame/stream/flow-control 机制
- Interceptor、Activity、Meter 和日志:[`observability.md`](observability.md)

当某个决定需要独立的取舍背景、替代方案或迁移策略时,应放入相应设计/ADR 类文档,而不是把决策历史复制进本页。

## 2.0 public/package 边界

[ADR 0001](adr/0001-2.0-public-api-and-packages.md) 固定业务 API、扩展 SPI 和 generated
infrastructure 的分类。生成程序集只引用 Abstractions;Catalog 是弱引用 bootstrap,
`SharpLinkRuntimeContext` 是实例级服务容器,普通应用通过 Client/Server Builder 配置。
内部 Session/StreamManager 的创建、发布和关闭只能由 owner 完成。八个发布包的实际 public API
由 [API baseline](../eng/public-api/2.0.0) 和 Release Gate 检查;SharedMemory/Telemetry 本轮不拆包。
8 changes: 8 additions & 0 deletions doc/calls-and-streaming.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,11 @@ OneWay 成功表示请求已进入本地发送/服务端接收流程,不包含
没有 `CancellationToken` 的 RPC 必须显式标注 `[NonCancellable]`。调用方取消后不再等待并清理框架资源,但服务实现可能继续运行;适用于确实不可取消、可接受后台完成的短任务。长任务应接收 token 并及时观察。

完整调用形态见 `demo/Streaming`、`demo/Oneway`、`demo/Cancel` 和 `demo/Timeout`。

## 2.0 扩展边界

业务只通过生成的契约 proxy 调用 Unary、OneWay 和三类 Streaming。Generated API 4
用 `IRpcChannel`/client-stream sink 与 `IRpcGeneratedServerBridge` 连接调用生命周期;
bridge 不公开 Session、dispatcher 或 flow-control mutator。所有框架后台发送/排空仍由 owner
监督并在停止时等待;应用提前停止枚举必须释放 enumerator。调用时间只传播剩余 TimeBudget
(Protocol v2 minor 4),不读取跨机器绝对 deadline。旧接口迁移见 [migration](migration.md)。
8 changes: 8 additions & 0 deletions doc/codec-compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,11 @@ The preferred progression is:
4. promote an environment to Guaranteed / release-gated only when SharpLink intentionally accepts the infrastructure cost and product commitment.

Build-only, emulator, simulator, and physical-device results must always be labeled as the environment that actually executed the probe.

## 2.0 release evidence boundary

The six desktop CoreCLR identities are the blocking compatibility matrix. Browser/Mono/WASM
remains an investigation lane tracked by #253; a green desktop summary is not a claim that the
Browser bidirectional evidence passed. In candidate run 34708344598, Browser production succeeded
but Browser and desktop consumption of that corpus failed. Keep those failures visible; do not
promote Browser/Mono interoperability to a supported guarantee from this release's desktop result.
9 changes: 9 additions & 0 deletions doc/dynamic-modules-and-multicluster.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,12 @@ NativeAOT 不支持运行时加载未知插件,动态模块只适用于 JIT
动态模块的 runnable 证据位于 `test/SharpLink.DynamicContracts`、`SharpLink.DynamicServices`、`SharpLink.RollbackPlugin` 和 `RuntimeAssemblyIntegrationTests`,覆盖注册、冲突、替换、调用排空、取消、回滚、cleanup failure、弱引用与 collectible ALC 回收。

运行时 slot 的 unit 与真实 TCP 证据位于 `test/SharpLink.UnitTests/Client/SharpLinkMultiCluster*Tests.cs` 和 `RuntimeMultiClusterIntegrationTests`,覆盖 Created/Running 状态、Add publication 与 readiness 解耦、structured expected rejection、Replace ready-before-swap、publication-vs-cleanup 结果、取消/Stop race、预算、Proxy 一次绑定、Add/Replace/Remove 和删除后的资源释放结果。

## 2.0 Generated ABI 和发布门禁

所有契约/服务/插件必须用 2.0 SDK 重新生成 API 4,并携带当前 ABI identity。
locator 在 manifest materialization 之前 fail fast;Catalog 是 generated bootstrap,
应用只通过 `ISharpLinkAssemblyRegistry` 注册、替换和注销。
`ReferencesReleased` 描述框架引用释放,不保证应用自身 Assembly/Type/proxy 引用已清空,
因此 ALC 卸载仍需调用方释放引用并验证回收。版本/identity 与完整公开签名由
[API baseline](../eng/public-api/2.0.0) 和 [版本清单](../eng/release-versions.json) 检查。
4 changes: 2 additions & 2 deletions doc/error-details.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ The public constants live under `SharpLinkErrorDetails.ResourceExhausted`.

## Wire format and compatibility

Protocol v2 minor 6 encodes every binary error payload as:
Protocol v2 minor 4 encodes every binary error payload as:

```text
SharpLinkErrorCode : uint16 little-endian
Expand All @@ -56,4 +56,4 @@ Message : MessageLength bytes of UTF-8

Message truncation only changes the UTF-8 message and the frame's `Truncated` flag; it never removes or changes `DetailCode`.

This binary shape is not compatible with the previous minor-5 `(Code, Message)` layout. SharpLink therefore sets both `ProtocolV2Constants.MinorVersion` and `MinimumCompatibleMinorVersion` to 6. A minor-5 peer is rejected during handshake instead of allowing either side to misinterpret an error payload.
This binary shape is not compatible with the published 1.1.1/minor-3 `(Code, Message)` layout. SharpLink 2.0 increments that released baseline once and sets both `ProtocolV2Constants.MinorVersion` and `MinimumCompatibleMinorVersion` to 4. A minor-3 peer is rejected during handshake instead of allowing either side to misinterpret an error payload. Development-only minor numbers are not released compatibility boundaries.
Loading