背景 / 为什么需要这个架构 issue
关联:
#244 的本质不是单纯“让 rejected request 更快”,而是:
服务器已经没有 call capacity、请求最终必然 reject 时,远端仍能让 request loop 支付 decompression CPU、decoded buffer 与 memory-bandwidth 成本。
#261 已验证:将真正 decompression 移到 capacity admission 之后,可以让 capacity-rejected compressed Request 达到 Decompress = 0、decoded payload rent = 0,并显著降低 overload 路径 CPU/op。但 #261 也暴露出现有架构限制:
RequestLoop 同时承担 I/O/control frame consumption 与 dispatch
+
ISharpLinkCompressionProvider.Decompress 是同步 CPU API
+
call capacity 只有 acquire/release,没有 reservation/activation 分层
+
remote Cancel 要在 post-capacity decompression 期间保持可见
当前局部解法因此演化为:
TryAcquireCall
-> retain/copy compressed frame
-> per-request ThreadPool handoff
-> worker synchronous decompress
-> reader loop 继续消费 Cancel
该方案正确性已被大量测试覆盖,但最终 benchmark 显示 remaining accepted-path cost 与 handoff 强相关:64 KiB cancellable compressed case 约 +7%8% CPU/op、约 -4%-5% QPS;no-handoff compressed control 基本持平;small payload 的固定调度成本比例更明显。
因此本 issue 的目标不是继续优化 #261 的 work-item pool,而是从架构上把 “谁有资格消耗 server resource” 与 “昂贵 decode 在哪里执行” 建模清楚,让安全性和性能来自同一个资源治理模型。
核心设计目标
1. 统一资源准入,而不是把 compression 特判塞进现有 Admission Controller
需要引入 server-owned 的基础资源治理层,例如概念上的:
Request facts
│
▼
┌──────────────────────────┐
│ ResourceGovernor │
│ │
│ call reservation │
│ decode concurrency │
│ decode byte budget │
│ retained byte budget │
│ stream buffer budget │
└────────────┬─────────────┘
│
RequestPermit
Advanced Admission / policy admission 与 ResourceGovernor 是不同层:
ResourceGovernor :始终存在,保护 server 的硬资源边界;不能因为高级 admission disabled 就绕过。
AdmissionProgram / policy admission :可选、可动态更新,负责业务策略、global/contract/method/partition/rate/queue 等规则。
明确禁止继续保留这种耦合:
_admissionController != null
=> deferred decode
_admissionController == null
=> eager decode
Compression 的安全 decode 时机不能由“是否启用高级 admission policy”决定。
2. Call lifecycle 拆成 Reserve -> Activate
当前 TryAcquireCall() 同时承担“未来处理资格”与“已经进入 active call 生命周期”。建议拆为:
TryReserveCall()
↓
CallReservation
↓
decode / prepare
↓
ActivateCall()
↓
service invoke
硬不变量:
active calls + reserved calls <= configured call capacity
这样 compressed Request 在 decode 前只需要一个轻量 reservation:
cheap validation
-> policy admission if configured
-> TryReserveCall
-> fail: cheap reject, Decompress = 0
-> success: server has committed resource budget
-> decode
-> ActivateCall
-> invoke
这直接满足 #244 的安全目标,而不要求仅为了“已经是 active call”建立完整 async handoff/call-state machinery。
3. 资源成本不只是一枚 call slot
Compressed RPC 至少涉及:
call reservation;
concurrent decode CPU work;
compressed retained bytes;
decoded bytes in flight;
pre-admission/client-stream stream-buffer reservation。
概念上可以形成:
RequestCost
CallSlots
DecodeOriginalBytes
RetainedCompressedBytes
StreamBufferBytes
DecodeClass / ProviderClass
不要求最终 public API 直接暴露 RequestCost,但内部架构必须能证明:
没有对应 permit,就不能开始对应昂贵工作。
4. RequestLoop 明确成为 I/O / control-plane loop
RequestLoop 的长期职责应优先限定为 bounded cheap work:
frame parse;
compression envelope validation;
routing/deadline/metadata cheap facts;
Cancel / StreamData / FlowControl / lifecycle frame consumption;
resource/policy admission;
ownership handoff。
不应让任意大 payload 的 CPU-bound decompression 无条件长期占住 reader loop。
同时,也不要求所有 compressed request 都无条件 per-request offload;decode execution 应根据成本和 cancellation semantics 决定。
必须建立的架构不变量
Resource invariants
No call reservation -> no decompression.
No decode permit -> no provider decode.
No retained-byte permit -> no long-lived compressed frame retention.
No decoded-byte permit -> no large decoded buffer rent.
capacity/policy rejected compressed Request:Decompress = 0,decoded bytes rented = 0。
resource permit 的 acquisition / transfer / release 必须 exactly once。
Server Stop/Drain/connection close/cancellation/decode failure 都不能泄漏 reservation。
Dynamic-generation invariants
一个 Request 对需要动态化的 policy generation 只 capture 一次;await/decode/queue 后不重新读取 current generation 改写其语义。
动态 Admission enable/disable 不能改变基础 resource-safety invariant。 Disable policy admission 只表示后续请求 bypass 该 policy,不表示可以绕过 call/decode/memory resource governor。
ResourceGovernor/StateKernel 应是 server-owned stable runtime state;AdmissionProgram generation 可以动态替换,但不能通过 new controller -> swap 重置真实 active/queued/decode accounting。
retired policy generation 与 outstanding RequestPermit/AdmissionLease 必须有明确 transfer/drain/reclaim 规则。
与 #262 / #264 Dynamic Runtime Configuration 的关系
本 issue 不是 #264 的替代品 ,而是 #264 需要共享的底层 server resource architecture。
建议职责边界:
本 issue 负责
AdmissionProgram immutable policy generation;
runtime enable / disable / replace;
concurrency/queue/rate/partition policy dynamic semantics;
state-preserving update / retire / drain;
policy generation capture 与 publication;
dynamic control API。
两者共享 / 必须协同
Request frame
↓
cheap RequestFacts
↓
capture AdmissionProgram generation(若 enabled)
↓
policy decision / cost classification
↓
ResourceGovernor reserve
↓
RequestPermit
↓
decode / activate / invoke
重要:#264 当前文档中“Admission enabled 决定 ValidateEnvelope vs DecodeInboundPayload”的耦合,在本架构落地后应被删除。动态 policy generation 可以参与 routing/admission/cost classification,但 compression 是否允许做昂贵 decode 由 RequestPermit/resource reservation 决定,而不是由 _admissionController != null 决定。
#264 的 policy/state split 与本 issue 的 ResourceGovernor/RequestPermit 应复用同一 state ownership 思路,避免出现:
DynamicAdmissionController 自己计 active/queue
+
ResourceGovernor 又计 call/decode
+
两边独立 generation / 生命周期
导致双重 accounting 或状态漂移。
建议实施顺序上:
Compression execution model:不要预设“async = 更好”
当前 ISharpLinkCompressionProvider 是同步接口:
SharpLinkCompressionResult Decompress (
ReadOnlySequence < byte > input ,
IBufferWriter < byte > output ,
int maxOutputBytes ,
CancellationToken cancellationToken = default ) ;
内置 Brotli 已经使用 stateful BrotliDecoder 分块 decode,并在循环中检查 cancellation。因此当前代码具备 incremental/cooperative decode 的技术基础,但还没有正式 execution abstraction。
需要分别研究以下路线。
A. Inline incremental decode
对小 payload / 低估算 decode cost:
RequestPermit
-> inline decode
-> ActivateCall
优势:
不产生 scheduler handoff;
不需要 retain/copy 仅为了跨 async;
small compressed accepted path 更接近 baseline;
cancellation/deadline 可以在 decode quantum 边界检查。
B. Cooperative decode quantum
把一次完整 decode 进一步明确为 bounded quantum:
decode N KiB
-> cancellation / lifecycle check
-> optionally yield / reschedule
-> next quantum
目标不是制造 async 关键字,而是控制 request-loop / executor 单次不可抢占 CPU 时间。
需要评估:
quantum size;
provider state ownership;
output writer ownership;
yield 是否真的改善 Cancel latency;
对 1 KiB / 64 KiB / 1 MiB 的 CPU/P99 影响。
C. Bounded persistent DecodeExecutor
如果大型 remote-cancellable decode 不适合 reader loop,则建立正式 executor,而不是 per-request ThreadPool.QueueUserWorkItem:
connection queues
↓
fair / bounded scheduling
↓
small fixed set of persistent decode workers
必须具备:
bounded queue;
bounded decode concurrency;
per-connection fairness / anti-monopoly;
Stop/Drain supervision;
worker exception observation;
no unbounded detached tasks;
RequestPermit 与 worker ownership 明确 transfer。
D. Optional genuine async provider capability
可以研究新的 provider SPI 是否需要 async capability,但不能把:
ValueTask DecompressAsync ( ... )
本身当作解决方案。
对于内存中的 Brotli,主要成本是 CPU,不是等待 I/O。若 provider 的 DecompressAsync 内部只是 Task.Run(Decompress),那只是把 #261 的 ThreadPool handoff 从 server 层移动到 provider 层。
只有 provider 真正具备异步/外部执行能力(例如 dedicated native engine / accelerator / remote execution),async capability 才有独立价值。
因此建议 provider SPI 最终允许:
fast synchronous/incremental decode;
optional async execution capability;
但不要强迫普通 CPU codec 实现 fake async。
推荐 Decode 策略:adaptive,而不是一刀切
候选策略:
if request is not remote-cancellable:
inline incremental decode
else if estimated decode cost <= InlineDecodeBudget:
inline bounded decode
else:
DecodeExecutor.Enqueue(permit, payload)
估算可以先基于:
originalLength;
compressedLength;
provider/profile;
server load / decode credits;
cancellation semantics。
不要在第一版过度智能化。最初甚至可以只按 originalLength 阈值做 deterministic prototype,再用 benchmark 决定是否值得增加 provider-specific cost model。
目标语义:
remote Cancel 允许被一个明确 bounded 的小 decode quantum 延迟;大/昂贵 decode 不阻塞 control-plane consumption。
这比“所有 wire-cancellable compressed request 都必须 per-request handoff”更符合性能/复杂度平衡。
Cancellation / Deadline 语义需要重新审计
当前默认 request timeout 会使普通 unary request 具有 wire Cancellable 语义,因此 #261 中很多看似普通的 compressed unary 也进入 handoff path。
本架构需要明确区分概念:
HasDeadline
AcceptsRemoteCancellation
ServiceSupportsCooperativeCancellation
它们是否必须继续由同一个 wire flag 表达,需要独立协议/兼容性分析;本 issue 不应偷改 Protocol v2。
但 server 内部至少不能把“有 deadline”自动等同于“decode 期间必须依赖 reader loop 立即收到 remote Cancel 才能取消”。Server-side deadline / Stop / connection-close cancellation 可以来自独立 cancellation source。
需要形成明确 ADR:
small bounded inline decode 可以允许多少 remote-Cancel observation latency;
large decode 何时必须 offload;
deadline 与 remote Cancel 的 execution requirement 是否完全相同;
如果 wire semantics 无法拆分,内部仍如何避免不必要 handoff。
Phase 0 — Architecture spike / 数据先行
在正式生产重构前,用 #261 已有 benchmark/test harness 做同宿主对比,至少 prototype:
A. #261 per-request ThreadPool handoff baseline
B. ReserveCall + inline incremental decode
C. ReserveCall + bounded cooperative decode quantum
D. ReserveCall + persistent bounded DecodeExecutor
如果准备新增 provider async SPI,再加:
E. optional async-provider prototype
测试矩阵至少:
payload: 1 KiB / 64 KiB / 1 MiB
compression ratio: high / low
remote cancellable: off / on
capacity: available / full
advanced admission: off / immediate / queued
concurrency: 1 / 16 / 128(按 runner 能力调整)
指标:
QPS;
CPU/op;
P50/P99;
B/op;
decompression calls/rejected request;
decoded bytes rented/rejected request;
retained compressed bytes in flight;
decoded bytes in flight;
decode queue depth;
worker/scheduler delay;
remote Cancel observation latency;
Stop/Drain latency。
Phase 0 输出一个简短 ADR/issue comment,明确选择哪种 execution model,再开始大规模 production plumbing。
建议实施阶段
Phase 1 — Decouple resource safety from optional Admission
compressed Request cheap envelope validation 不再依赖 _admissionController != null;
建立 RequestFacts / routing facts 的单次解析边界;
保持 uncompressed direct/zero-copy fast path;
不在本阶段引入最终 async executor。
Phase 2 — Introduce RequestPermit + CallReservation
TryReserveCall / ActivateCall;
server + per-connection capacity 统一 reservation accounting;
early reject / corruption / cancel / close / stop exactly-once release;
active + reserved invariant deterministic tests;
不改变 wire semantics。
Phase 3 — Add decode/memory resource budgets
decode concurrency credits;
decoded bytes in flight;
retained compressed bytes;
pre-admission stream buffer budget 与 RequestPermit ownership 对齐;
明确 per-server / per-connection 两级预算关系。
Phase 4 — Implement selected decode execution model
根据 Phase 0 结果选择:
inline + adaptive threshold;
cooperative quantum;
persistent DecodeExecutor;
optional async provider capability。
不要默认把所有方案都实现到生产代码。
Phase 5 — Integrate Dynamic Admission (#264 )
Phase 6 — Stress / performance / documentation
Correctness acceptance suite
优先复用/迁移 #261 已验证的测试,不要重新从零构造:
Capacity / cheap reject
Envelope / corruption
Cancellation / lifecycle
Streaming
Dynamic configuration
Stress
性能门禁
本重构的目标不是“安全性允许长期牺牲正常路径性能”,而是建立可以同时保护 overload 与 accepted fast path 的架构。
Uncompressed disabled/default path
Small compressed accepted path
Large/remote-cancellable compressed path
Rejected path
明确禁止的实现
不再用 _admissionController != null 决定 compression eager/deferred decode。
不为了支持 dynamic admission 建第二套独立 call/decode accounting。
不把 ResourceGovernor 做成每请求全局锁。
不通过 process-global static registry 保存 permits/generations。
不把 ValueTask DecompressAsync + 内部 Task.Run 当作 async architecture 完成。
不让所有 small compressed requests 无条件 ThreadPool handoff。
不为了跨 async 把整个 payload copy 到普通 byte[];retained ownership 必须受 budget 管理。
不在 config update 时重置 active/decode/queue resource state。
不在 ordinary dynamic Admission Disable 时释放/绕过基础 server resource safety。
不在一个超大 PR 中同时完成 ResourceGovernor、所有 dynamic rate limiter、provider API redesign、wire protocol redesign。
Out of scope
Protocol v2 wire format redesign / compression renegotiation;如确需拆分 deadline/remote-cancel wire semantics,另开协议 issue。
Client endpoint admission / circuit breaker 动态化。
所有 Runtime Configuration public API 的统一设计(仍由 [Runtime Configuration] 动态配置 / Hot Reload 长期跟踪 #262 后续统一评估)。
GPU/native accelerator 等具体 provider 实现;只定义必要 execution capability 边界。
为了本 issue 重写全部 server dispatch。
建议 PR 拆分
不要重复 #261 的大分支模式。建议每一步可单独验证/回滚:
test: preserve issue-244 resource amplification evidence
refactor(server): decouple compressed envelope handling from optional admission
refactor(server): add call reservation and request permit lifecycle
feat(server): add decode and byte resource budgets
perf(server): prototype and select decode execution strategy
feat(server): implement bounded/adaptive decode execution
refactor(server): integrate dynamic admission generation with resource governor
test: add dynamic/compression/cancellation resource stress
perf: record accepted/rejected/resource-governor matrix
docs: document server request resource admission architecture
如果 Phase 0 证明 provider SPI 必须变化,再单独开/拆 provider API PR,不把 public SPI redesign 偷塞进基础 reservation PR。
Definition of Done
Checks
背景 / 为什么需要这个架构 issue
关联:
#244 的本质不是单纯“让 rejected request 更快”,而是:
#261 已验证:将真正 decompression 移到 capacity admission 之后,可以让 capacity-rejected compressed Request 达到
Decompress = 0、decoded payload rent = 0,并显著降低 overload 路径 CPU/op。但 #261 也暴露出现有架构限制:当前局部解法因此演化为:
该方案正确性已被大量测试覆盖,但最终 benchmark 显示 remaining accepted-path cost 与 handoff 强相关:64 KiB cancellable compressed case 约 +7%
8% CPU/op、约 -4%-5% QPS;no-handoff compressed control 基本持平;small payload 的固定调度成本比例更明显。因此本 issue 的目标不是继续优化 #261 的 work-item pool,而是从架构上把 “谁有资格消耗 server resource” 与 “昂贵 decode 在哪里执行” 建模清楚,让安全性和性能来自同一个资源治理模型。
核心设计目标
1. 统一资源准入,而不是把 compression 特判塞进现有 Admission Controller
需要引入 server-owned 的基础资源治理层,例如概念上的:
Advanced Admission / policy admission 与 ResourceGovernor 是不同层:
明确禁止继续保留这种耦合:
Compression 的安全 decode 时机不能由“是否启用高级 admission policy”决定。
2. Call lifecycle 拆成 Reserve -> Activate
当前
TryAcquireCall()同时承担“未来处理资格”与“已经进入 active call 生命周期”。建议拆为:硬不变量:
这样 compressed Request 在 decode 前只需要一个轻量 reservation:
这直接满足 #244 的安全目标,而不要求仅为了“已经是 active call”建立完整 async handoff/call-state machinery。
3. 资源成本不只是一枚 call slot
Compressed RPC 至少涉及:
概念上可以形成:
不要求最终 public API 直接暴露
RequestCost,但内部架构必须能证明:4. RequestLoop 明确成为 I/O / control-plane loop
RequestLoop 的长期职责应优先限定为 bounded cheap work:
不应让任意大 payload 的 CPU-bound decompression 无条件长期占住 reader loop。
同时,也不要求所有 compressed request 都无条件 per-request offload;decode execution 应根据成本和 cancellation semantics 决定。
必须建立的架构不变量
Resource invariants
Decompress = 0,decoded bytes rented = 0。Dynamic-generation invariants
new controller -> swap重置真实 active/queued/decode accounting。与 #262 / #264 Dynamic Runtime Configuration 的关系
本 issue 不是 #264 的替代品,而是 #264 需要共享的底层 server resource architecture。
建议职责边界:
本 issue 负责
#264 负责
两者共享 / 必须协同
重要:#264 当前文档中“Admission enabled 决定 ValidateEnvelope vs DecodeInboundPayload”的耦合,在本架构落地后应被删除。动态 policy generation 可以参与 routing/admission/cost classification,但 compression 是否允许做昂贵 decode 由 RequestPermit/resource reservation 决定,而不是由
_admissionController != null决定。#264 的 policy/state split 与本 issue 的 ResourceGovernor/RequestPermit 应复用同一 state ownership 思路,避免出现:
导致双重 accounting 或状态漂移。
建议实施顺序上:
Compression execution model:不要预设“async = 更好”
当前
ISharpLinkCompressionProvider是同步接口:内置 Brotli 已经使用 stateful
BrotliDecoder分块 decode,并在循环中检查 cancellation。因此当前代码具备 incremental/cooperative decode 的技术基础,但还没有正式 execution abstraction。需要分别研究以下路线。
A. Inline incremental decode
对小 payload / 低估算 decode cost:
优势:
B. Cooperative decode quantum
把一次完整 decode 进一步明确为 bounded quantum:
目标不是制造
async关键字,而是控制 request-loop / executor 单次不可抢占 CPU 时间。需要评估:
C. Bounded persistent DecodeExecutor
如果大型 remote-cancellable decode 不适合 reader loop,则建立正式 executor,而不是 per-request
ThreadPool.QueueUserWorkItem:必须具备:
D. Optional genuine async provider capability
可以研究新的 provider SPI 是否需要 async capability,但不能把:
本身当作解决方案。
对于内存中的 Brotli,主要成本是 CPU,不是等待 I/O。若 provider 的
DecompressAsync内部只是Task.Run(Decompress),那只是把 #261 的 ThreadPool handoff 从 server 层移动到 provider 层。只有 provider 真正具备异步/外部执行能力(例如 dedicated native engine / accelerator / remote execution),async capability 才有独立价值。
因此建议 provider SPI 最终允许:
推荐 Decode 策略:adaptive,而不是一刀切
候选策略:
估算可以先基于:
不要在第一版过度智能化。最初甚至可以只按 originalLength 阈值做 deterministic prototype,再用 benchmark 决定是否值得增加 provider-specific cost model。
目标语义:
这比“所有 wire-cancellable compressed request 都必须 per-request handoff”更符合性能/复杂度平衡。
Cancellation / Deadline 语义需要重新审计
当前默认 request timeout 会使普通 unary request 具有 wire
Cancellable语义,因此 #261 中很多看似普通的 compressed unary 也进入 handoff path。本架构需要明确区分概念:
它们是否必须继续由同一个 wire flag 表达,需要独立协议/兼容性分析;本 issue 不应偷改 Protocol v2。
但 server 内部至少不能把“有 deadline”自动等同于“decode 期间必须依赖 reader loop 立即收到 remote Cancel 才能取消”。Server-side deadline / Stop / connection-close cancellation 可以来自独立 cancellation source。
需要形成明确 ADR:
Phase 0 — Architecture spike / 数据先行
在正式生产重构前,用 #261 已有 benchmark/test harness 做同宿主对比,至少 prototype:
如果准备新增 provider async SPI,再加:
测试矩阵至少:
指标:
Phase 0 输出一个简短 ADR/issue comment,明确选择哪种 execution model,再开始大规模 production plumbing。
建议实施阶段
Phase 1 — Decouple resource safety from optional Admission
_admissionController != null;RequestFacts/ routing facts 的单次解析边界;Phase 2 — Introduce RequestPermit + CallReservation
TryReserveCall/ActivateCall;Phase 3 — Add decode/memory resource budgets
Phase 4 — Implement selected decode execution model
根据 Phase 0 结果选择:
不要默认把所有方案都实现到生产代码。
Phase 5 — Integrate Dynamic Admission (#264)
Phase 6 — Stress / performance / documentation
doc/admission-control.md、compression/runtime architecture docs。Correctness acceptance suite
优先复用/迁移 #261 已验证的测试,不要重新从零构造:
Capacity / cheap reject
Envelope / corruption
Cancellation / lifecycle
Streaming
Dynamic configuration
Stress
性能门禁
本重构的目标不是“安全性允许长期牺牲正常路径性能”,而是建立可以同时保护 overload 与 accepted fast path 的架构。
Uncompressed disabled/default path
Small compressed accepted path
Large/remote-cancellable compressed path
Rejected path
明确禁止的实现
_admissionController != null决定 compression eager/deferred decode。ValueTask DecompressAsync+ 内部Task.Run当作 async architecture 完成。byte[];retained ownership 必须受 budget 管理。Out of scope
建议 PR 拆分
不要重复 #261 的大分支模式。建议每一步可单独验证/回滚:
test: preserve issue-244 resource amplification evidencerefactor(server): decouple compressed envelope handling from optional admissionrefactor(server): add call reservation and request permit lifecyclefeat(server): add decode and byte resource budgetsperf(server): prototype and select decode execution strategyfeat(server): implement bounded/adaptive decode executionrefactor(server): integrate dynamic admission generation with resource governortest: add dynamic/compression/cancellation resource stressperf: record accepted/rejected/resource-governor matrixdocs: document server request resource admission architecture如果 Phase 0 证明 provider SPI 必须变化,再单独开/拆 provider API PR,不把 public SPI redesign 偷塞进基础 reservation PR。
Definition of Done
Reserve -> Activate -> Releasecall lifecycle 或等价模型。Decompress=0/ decoded bytes=0;accepted path性能达到新架构门禁。Checks