Repository navigation
release: .NET 11 GA SDK, Runtime Async for the whole line, opt-in unions, capability gate - #498
MPCoreDeveloper wants to merge 7 commits into
Conversation
…hole line .NET 11 GA shipped 2026-11-10, so master moves off the RC: global.json (both copies) pins 11.0.100 with rollForward latestPatch and no allowPrerelease, the five net11 workflows install 11.0.x without include-prerelease (ci-net10.yml untouched), and the server Dockerfile comment names the GA images. While doing it, Features=runtime-async=on turned out to be three hand-copied project lines (core, EventSourcing, its tests) while the other 72 projects compiled classic - although docs/net11/UPGRADE.md already described it as a property of the whole line. It is now defined once, in a new Directory.Build.targets, conditioned on net11.0, with <UseRuntimeAsync>false</UseRuntimeAsync> as the per-project opt-out. Why targets and not props: a TFM-conditioned switch in Directory.Build.props is evaluated before the project body, where $(TargetFramework) is still empty, so it silently never applies - measured, not reasoned (163 state machines and 71,4 B/call survived with the block in props; the same block in targets gave 2 and 0). Measured on the GA runtime, 1.000-call loop: an awaited, synchronously-completing async Task<int> allocated 71.424 bytes before and 0 after; SharpCoreDB.dll went from 163 compiler-generated IAsyncStateMachine types to 2 (the async iterators, which runtime async does not cover at GA). Verified: SharpCoreDB.CI.slnf builds with the GA SDK (0 errors); core suite 2124 / 0 / 0 / 0.
…async codegen SharpCoreDB.Unions (net11.0-only, additive) exposes QueryOutcome (rows / not found / failure), ExecuteOutcome (rows affected / failure) and TableLookup (found / missing) as C# 15 union declarations with seven record class cases, so a switch over them is exhaustive at compile time. Nothing is retrofitted: QueryResult, ExecuteResult and every ExecuteSQL signature stay byte-identical, and BuildResult deliberately stays a record hierarchy (a union there would break a shipped type). The union keyword form was chosen over the [Union] class shape in the design doc because the GA compiler generates that shape itself. Two canaries pin facts that would otherwise drift silently: - RuntimeAsyncCodegenTests fails when the runtime-async flag is dropped (-p:UseRuntimeAsync=false: 163 state machines and 71,4 B/call) and passes on the GA build (2 state machines, 0 B/call on an awaited synchronously-completing call). - UnionOutcomeTests carries no discard arm in any switch, so exhaustiveness is enforced by the compiler. Docs: UPGRADE.md gains the GA section and the measured Runtime Async limits (the awaited shape is the win; a blocking GetResult still materialises the task; async iterators keep their compiler state machine), UNION_TYPES_DESIGN.md records that the design shipped, and ROADMAP/INDEX are updated to match. Verified: union canaries 3/3, async canaries 2/2, core suite 2124 / 0 / 0 / 0.
…location on batch INSERT The write-path gate's UPDATE cell is bimodal (~215k vs ~420k ops/sec) and six dials were tested and refuted for it: machine load (INSERT/READ stay inside 1,07x in the same reps), warm-up depth (8/16/2 passes), GC frequency (2 MB gen0), GC mode (DOTNET_gcServer=1 keeps the split at higher absolutes), tiering (DOTNET_TieredCompilation=0), hash-index maintenance (SHARPCOREDB_HASH_INDEXES=0) and compaction (SHARPCOREDB_COMPACTION_THRESHOLD=1e9). What the data says instead: inside every run the fastest rep is 50-70% above the median and the fastest rep agrees across runs to ~1%, a ~50 ms GC pause lands in 2 of 54 measured phases, and most slow passes carry one single-stage stall whose stage varies per pass. The mode is a per-rep disturbance, and the median is the wrong statistic for a 22-35 ms window. So the gate now reports and compares capability: - GateRow prints a `best` column (fastest of the reps); the verdict still uses the median. - --write-baseline records medians in raw/default and bests in rawBest/defaultBest. - When a baseline carries the bests, a capability table (best-to-best) decides the verdict and the spread is reported as information; median-only baselines keep the old behaviour exactly. - Recording is no longer gated on the rep spread (it is not a verdict - and on this box that gate made a baseline impossible: every attempt exited 2 with 2,63x-3,29x spreads), and instead refuses to write when any metric's best is more than 25% below the one on disk, which is what "a baseline recorded during a regression silently accepts that regression" is about. - SHARPCOREDB_PHASE_DIAG=1 adds wall vs process CPU vs GC counts/pause per phase. With that instrument the INSERT path got its first measured cut: NormalizeInsertRow allocated a bool[Columns.Count] per row and rebuilt it from the same columnIndexMap every time, but which columns a statement names is a property of the statement. Hoisting it to one mask per batch took the dictionary-free object[] overload from 1.838.776 to 1.787.608 bytes for 2.000 rows - 919 -> 894 B/row (-2,7%) - with identical behaviour, pinned by the new InsertBatchAllocationTests canary at a 900 B/row ceiling. Refuted in the same session and reverted, so nobody re-derives it: the identity-remap fast path (skip the normalized object[] when the column order matches) saves 3 B/row, because an insert never names the hidden AUTO column _rowid and the normalized row is required to carry it. Docs: docs/manual/performance.md documents server GC as the measured host lever (~1,75x INSERT, +20% READ, costs a heap per core) and records that GC frequency and tiering are not levers for this engine. The baseline file is re-recorded on the GA runtime with both statistics; review its diff before landing. Verified: gate PASSED on the capability comparison (raw INSERT 0,98x, default UPDATE 1,03x), suite 2124 / 0 / 0 / 0, canaries 57 / 0 / 0 / 0.
… session record CHANGELOG gains the GA upgrade, the opt-in union surface, the GA-runtime gate evidence and the batch-INSERT allocation cut. PROMOTION_TO_MAIN's GA checklist is closed item by item - SDK pin, CI pins, unions shipped, gate run (INCONCLUSIVE then a documented PASSED, plus the capability statistic that made it certifiable), the baseline recorded by the owner, the image rebuilt on the GA base images - and item 5 is stated as owner-only rather than started, because version metadata, packing and publishing are the rail. docs/net11/GA_RELEASE_NOTES.md is the release note draft plus the checklist: what the repository verified (slnf 0 errors, 2124/0/0/0, canaries 57/0/0/0, gate PASSED, docs 9/9, image reports 11.0.0) and the six owner steps (version bump, tag, merge that packs and publishes, GitHub release, image push, optional quiet-box re-record). docs/performance/NET11_C15_PERFORMANCE_PLAN.md is the standing plan: what is already in place, where the time actually goes per UPDATE stage, the refuted-dial list (including the identity-remap fast path), the UPDATE mode resolved as a statistic, phases P0-P3 and the zero-regression rails. WORKLOG carries session 87 and its eight addenda: the GA upgrade, the runtime-async measurement, the union implementation, the UPDATE mode hunt (seven refutations), the capability gate, the INSERT cut, the refuted identity path, and the "no regression against the previous version" check (gate against the 2026-09-27 baseline: PASSED, worst cell 1,08x, UPDATE faster on both arms).
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
The first CI run on this branch was red in two places. One is mine and is fixed here: the coverage job runs the
suites a second time under `dotnet-coverage collect`, and instrumentation changes what the JIT is allowed to
do, so both allocation canaries stopped measuring the engine and started measuring the collector. Evidence from
job 131142938452: RuntimeAsyncCodegenTests reported 71.424 bytes over 1.000 awaited calls - exactly the classic
(non-runtime-async) figure, i.e. the elision is gone under instrumentation, so asserting 0 was never true there -
and InsertBatchAllocationTests read 902 B/row against its 900 ceiling.
Both are now `[Trait("Category","Allocation")]`, and the coverage job passes -Filter
"...&Category!=Allocation". They still run and still gate in the uninstrumented Build and Test jobs: verified
locally, `Category=Allocation` selects exactly those 2 tests (green) and the coverage filter runs 2122 (green).
AGENTS.md documents the convention so the next allocation canary is tagged the same way.
The second failure is not this branch's: CI's "Fail on vulnerable NuGet packages" step fails on NU1903 by Aspire.Hosting
13.6.4 (GHSA-f7vh-h5g3-7jrh / CVE-2026-120006, high, published 2026-11-10, patched in 13.6.5) in
src/SharpCoreDB.Aspire.Hosting. No package moved in this branch. Bumping a package version is the owner's under the
standing rail, so it is recorded as a release blocker in docs/net11/GA_RELEASE_NOTES.md with the advisory, the
patched version and the file to change - not patched here.
… CI reds Records the pre-flight evidence (slnf 0 errors, suite 2124/0, canaries 57/0, gate PASSED, docs 9/9, image reports 11.0.0), the five commits on this branch, and the two CI failures separated by cause: the coverage job was mine (allocation canaries measure the collector under instrumentation - 71.424 bytes per 1.000 awaited calls is exactly the classic figure - fixed with the Allocation trait and the coverage filter), and the three Build and Test jobs are one dependency advisory (NU1903 Aspire.Hosting 13.6.4, GHSA-f7vh-h5g3-7jrh, patched in 13.6.5) which is a package-version change the rail keeps with the owner.
|
Status after the first CI round. Green: Fixed in Blocked on an owner decision: all three After the bump the three jobs become full build+test runs, and the remaining release steps are the ones in |
The status page still described 2.1.2 as the whole story, with no mention of the GA upgrade. Adds the factual line: GA SDK pin, Runtime Async once for every project (0 B/call on an awaited synchronously-completing call, 2 state machines instead of 163), the opt-in union surface, the capability gate, and the validated numbers - slnf 0 errors, core suite 2,124/0, canaries 57/0, gate PASSED (worst cell 1,08x, UPDATE faster on both arms), docs 9/9, image reporting 11.0.0. Points at net11/GA_RELEASE_NOTES.md for the checklist and the one open blocker, and states that version metadata, the tag and the publishing merge stay owner actions.
|



What this is
mastermoves from the .NET 11 RC to the .NET 11 GA SDK (11.0.100), Runtime Async becomes one definition for the whole line instead of three hand-copied project lines,SharpCoreDB.Unionsships as an additive opt-in surface, and the write-path gate learns to measure what it can actually certify. No storage-format, encryption, durability or WAL change.Measured (GA runtime
11.0.0, same binary per A/B)async Task<T>IAsyncStateMachinetypes inSharpCoreDB.dllDOTNET_gcServer=1(host lever)object[]overload)Why the gate changed shape
The UPDATE cell is bimodal (~215k vs ~420k ops/sec) and seven dials were tested and refuted for it: machine load, warm-up depth, GC frequency, GC mode, tiering, hash-index maintenance, compaction. Inside every run the fastest rep is 50–70 % above the median and agrees across runs to ~1 %; a ~50 ms GC pause lands in 2 of 54 measured phases. That is a per-rep disturbance in a 22–35 ms window, so the median cannot certify it. The gate now prints a
bestcolumn,--write-baselinerecordsrawBest/defaultBest, and when a baseline carries them the verdict is a best-to-best capability comparison. Recording is no longer blocked by the rep spread (it is not a verdict — on this box that made a baseline impossible) but refuses a capability more than 25 % below the one on disk.Verified on this branch
SharpCoreDB.CI.slnfwith the GA SDK: Build succeeded, 0 errorsGATE PASSED(exit 0)sharpcoredb-server:net11-ga(604 MB) reports runtime11.0.0Owner actions — deliberately not in this PR
Merging to
masterrunspackand Publish to NuGet.org, so landing is the owner'''s step. The same goes for the version bump (Version/AssemblyVersion/FileVersion,<PackageReleaseNotes>, tags) and the tag itself —docs/net11/GA_RELEASE_NOTES.md§4 lists all six steps, and §5 states why they are untouched.Review the baseline diff before landing:
baselines/dual-mode-baseline.jsonnow carries both statistics, and a baseline recorded during a regression would silently accept it for every later run.