Skip to content

feat: add peer generation synchronization - #718

Open
SunSi12138 wants to merge 1 commit into
devfrom
issue-691-peer-generation-sync
Open

SunSi12138 wants to merge 1 commit into
devfrom
issue-691-peer-generation-sync

Conversation

@SunSi12138

@SunSi12138 SunSi12138 commented Sep 19, 2026 •

Copy link
Copy Markdown
Owner

Background

Issue #691 was refined from a remote-control model to peer declaration + local reconciliation.

Either Client or Server may change generation first. The changed endpoint declares its current generation/compatibility state; the peer observes that state and decides locally whether it needs to follow.

Core rule:

Peer can inform me, but peer cannot mutate me.

This PR supersedes the closed #717 design. It intentionally removes #717's remote Stage/Activate RPC surface.

Transport model

SharpLink v2 RPC requests are Client-initiated, so the implementation does not pretend that Server can open an RPC back to Client.

The stable remote contract is declaration-only:

  • GetInventoryAsync: Client queries the Server's current complete generation inventory.
  • SynchronizeAsync(clientInventories, ...): Client opens a duplex stream.
    • request stream: Client publishes complete Client inventories
    • response stream: Server publishes complete Server inventories

This supports both Server-first and Client-first generation changes without adding Server-initiated RPC or remote mutation commands.

Inventory ordering / reconnect semantics

Each SharpLinkGenerationInventory carries:

  • EndpointEpoch: opaque ordering scope for the endpoint's declaration sequence
  • Revision: monotonic only within that epoch
  • complete generation snapshots

Rules:

  • revisions are compared only when EndpointEpoch is equal
  • an endpoint must change epoch whenever the revision sequence can reset or declaration authority changes
  • process restart normally means a new epoch unless the application durably preserves revision continuity
  • a receiver that sees a different epoch must accept the complete inventory as a new ordering domain even if its numeric revision is lower
  • GetInventoryAsync and duplex synchronization use the same rule; historical revisions from another epoch must never make a current response look stale

On reconnect/restart, each side publishes its current complete inventory again. Correctness does not depend on event replay.

Local-only reconciliation

StageAsync, ValidateAsync, ActivateAsync, and DrainAsync exist only on local ISharpLinkGenerationProvider.

Peer metadata is input to local policy; it is never an instruction to execute/load an artifact or replace a process.

NativeAOT remains explicit through local ProcessReplacementRequired outcomes.

Package / CI integration

  • optional SharpLink.GenerationControl package
  • runtime dependency only on SharpLink.Abstractions; Generator remains analyzer-only
  • solution/project-reference/package/release/public-API integration
  • package smoke verifies:
    • RPC surface contains only GetInventoryAsync and SynchronizeAsync
    • provider operations remain local SPI
    • EndpointEpoch survives query and duplex serialization
    • Client inventory reaches Server
    • Server inventory reaches Client

Non-goals

The package does not:

  • remotely execute peer upgrades
  • transfer artifacts
  • choose a global authority
  • solve versions
  • define rollout/rollback policy
  • load unknown assemblies under NativeAOT
  • add Server-initiated RPC
  • change Protocol v2 or ordinary RPC hot paths

Review fix

The blocking reconnect/restart review finding is addressed by introducing EndpointEpoch as the revision ordering scope and documenting the acceptance rule for a changed epoch across both query and duplex paths.

Fixes #691

Copy link
Copy Markdown
Owner Author

Squashed the implementation to one focused commit without changing the final tree.

  • previous head: a558c0eda800a4ebdeee013b0ea153b6094fe65d
  • final single-commit head: 58c18cb5da304b35d8ed4adb8d15cb5f7b2bb8cd
  • parent: current dev f624d0968a8e65a3bd1356f42f287e25aeae3e93
  • changed files remain 22; implementation content is unchanged from the already-green four-commit head

Final-head CI is being rerun now. After the core checks pass, PR Extended will be triggered on this exact head.

@SunSi12138
SunSi12138 marked this pull request as draft September 19, 2026 16:32
@SunSi12138
SunSi12138 marked this pull request as ready for review September 19, 2026 16:32

SunSi12138 commented Sep 19, 2026 •

Copy link
Copy Markdown
Owner Author

Final development verification completed on focused head 58c18cb5da304b35d8ed4adb8d15cb5f7b2bb8cd.

Self-review pass 1 — engineering boundaries:

  • final tree was squashed to one commit directly on current dev
  • SharpLink.GenerationControl remains optional; runtime dependency is only SharpLink.Abstractions, with Generator analyzer-only
  • reviewed public API matches the generated duplex contract
  • package smoke verifies package contracts and real NuGet consumption
  • package smoke exercises both directions of SynchronizeAsync: Client inventory reaches Server, Server inventory reaches Client

Self-review pass 2 — #691 refined acceptance:

  • either Client or Server may change generation first
  • remote surface is declaration-only: GetInventoryAsync + Client-initiated duplex SynchronizeAsync
  • no remote Stage/Validate/Activate/Drain commands
  • local provider owns stage/validate/activate/drain and NativeAOT process-replacement outcome
  • reconnect correctness uses complete inventories/revisions, not event replay
  • documentation explicitly records the SharpLink v2 Client-initiated RPC constraint and “Peer can inform me, but peer cannot mutate me.”

Final-head CI:

  • PR Fast: success
  • PR Package Smoke: success
  • CodeQL: success
  • Codec Padding Security Evidence: success
  • PR Extended: success
  • Extended includes Integration Tests, NativeAOT transport/topology smoke, pack/package verification, NuGet package smoke, Oneway demo, load smoke, and all six desktop codec producer/verification lanes
  • the additional PR Fast triggered by the final PR-description edit also completed successfully on the same head

The PR description has been updated from the earlier query/watch wording to the final duplex API. No merge action has been taken.

@SunSi12138 SunSi12138 left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

审核结论:当前有 1 个需要修改的 correctness / reconnect 语义问题,暂不通过。

整体方向符合 #691 最新设计:远端 surface 只有 declaration/query + Client-initiated duplex sync,本地 provider 才拥有 Stage/Validate/Activate/Drain;没有重新引入 #717 的 remote-mutation 模型。包依赖边界、public API/package smoke、NativeAOT 边界也都合理。

阻塞点是 inventory revision 的重启/切换语义:当前公开 API 只给出 endpoint-local monotonic Revision,但文档同时要求 process restart / reconnect 后重新发送完整 snapshot 并自然收敛。API 中没有 endpoint epoch / instance identity,也没有规定新 stream 的首个 snapshot 必须无条件覆盖旧 revision。如果 revision 在进程重启后重置(或重连到另一个 endpoint),消费者按常见的 revision <= lastRevision 去重就会把新的真实状态持续当成 stale。

建议在 public API 首次发布前明确一种可执行语义,例如:

  • 增加 endpoint epoch / instance id,并把顺序定义为同一 epoch 内比较 revision;或
  • 明确 revision 只在单次 sync stream 内可比较,每个新 stream 的首个完整 inventory 必须无条件接受,同时说明 GetInventoryAsync 响应不能用历史 revision 跨连接判 stale。

当前 head 的 PR Fast、PR Package Smoke、CodeQL、Codec Padding Security Evidence、PR Extended 均为 success;这个问题是 API 语义缺口,不是 CI 失败。

不合并、不修改 #691 状态。等待作者修正后再复审。

Comment thread src/SharpLink.GenerationControl/GenerationControlContracts.cs
@SunSi12138
SunSi12138 force-pushed the issue-691-peer-generation-sync branch 2 times, most recently from 063c054 to d8fc5db Compare September 20, 2026 08:52
@SunSi12138
SunSi12138 force-pushed the issue-691-peer-generation-sync branch from d8fc5db to 1743169 Compare September 20, 2026 08:54
@SunSi12138
SunSi12138 marked this pull request as draft September 20, 2026 08:58
@SunSi12138
SunSi12138 marked this pull request as ready for review September 20, 2026 08:58

Copy link
Copy Markdown
Owner Author

Addressed the blocking reconnect/restart review finding on final head 1743169226e2c59a839751fed8b395a86dcb339f.

Root cause:

  • Revision previously had no explicit ordering scope across process restart, endpoint-instance changes, or reconnects.
  • A consumer retaining revision 100 could incorrectly reject a restarted/replaced endpoint's revision 1 forever.

Change:

  • added SharpLinkGenerationInventory.EndpointEpoch
  • Revision is comparable only within the same epoch
  • the endpoint must change epoch whenever its revision sequence can reset or declaration authority changes
  • observing a different epoch starts a new ordering domain; the receiver must accept that complete inventory even when its numeric revision is lower
  • normal process restart uses a new epoch unless the application durably preserves revision continuity
  • GetInventoryAsync and duplex SynchronizeAsync use the same cross-connection ordering rule
  • package smoke verifies Client and Server epoch values through both query and duplex serialization
  • generation-control, control-plane, dynamic-module docs and PR description now state the epoch/revision rule

CI failure/fix:

  • the first run after adding the field failed only at reviewed public API verification because the generated inventory codec hash changed from e5378d3491c6b231ee0d5b22b9874f0e to 3264754839103fc88750806e219b9906
  • build, pack and package-contract verification had already passed
  • the exact generator-produced descriptor from job 106051982311 was recorded in the reviewed API baseline; no test or gate was weakened

Final verification:

  • PR Fast: success
  • PR Package Smoke: success
  • CodeQL: success
  • Codec Padding Security Evidence: success
  • PR Extended: success
  • Extended includes Integration Tests, NativeAOT transport/topology smoke, pack/package contracts, NuGet consumption, Oneway demo, load smoke, and all six desktop codec producer/verification lanes

The PR remains one focused commit directly on current dev. No merge action was taken.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant