Skip to content

Native AOT: the async daemon, cursor paging and the remaining reflection-serializing paths (#412) - #421

Merged
jeremydmiller merged 1 commit into
mainfrom
fisher-412-aot-remaining-paths
Oct 4, 2026
Merged

jeremydmiller merged 1 commit into
mainfrom
fisher-412-aot-remaining-paths

Conversation

@jeremydmiller

@jeremydmiller jeremydmiller commented Oct 4, 2026 •

Copy link
Copy Markdown
Member

Part of #412 — items 2 and 3. Item 1 (strong-typed aggregate identities) stays blocked on jasperfx#942, so this does not close the issue.

Rebased onto main after #420 merged. Counts re-measured from a fresh CI-shaped run of the combined code: Fisher.Tests 2513, 2580 in total.

What changed

Item 2, the async daemon: no Fisher change was needed. smoke/aot-consumer now runs an async Snapshot<Ledger> and a MultiStreamProjection<PortVisits, string> under BuildProjectionDaemonAsync / StartAllAsync / WaitForNonStaleData, natively, and asserts both documents. (The hosted AddAsyncDaemon() path isn't exercised; the smoke has no host.)

Item 3, the reflection-serializing paths:

  • CursorPagination: fixed-shape Utf8JsonWriter to encode, JsonDocument to decode. The v1: payload is byte-identical to the old JsonSerializer output; cursor_encoding pins a string captured from the old implementation over every key type. Any other key type is now refused by name.
  • Bug fixed along the way: keyset paging over a document keyed by a strong-typed id always failed on page 2, because the wrapper was serialized as {"Value":…} and couldn't be decoded. It's now carried as its inner value (paged_queries.a_cursor_walk_over_a_strong_typed_identity, which failed before the fix).
  • IncludePlan: a statically closed Enumerable.Contains<object>. The old Array.CreateInstance threw natively over a strong-typed id ("TicketId[] is missing native code").
  • FullTextSearchExtensions and Include's marker: the closed MethodInfo comes from a delegate, not MakeGenericMethod by name.
  • DateMember: serializes through options.GetTypeInfo(type).
  • Projection step-through: state goes through the store's serializer, step properties are no longer read by reflection, and the class-wide suppressions are gone. The old shape crashed ILC 10.0.1 (IndexOutOfRangeException in MakeGenericMethodSite) once step-through was reachable.

⚠️ Release-notes item: RunProjectionByNameAsync used to render state with JsonSerializer's defaults (PascalCase). It now renders through the store's serializer, which is camelCase by default.

Verification (macOS arm64, ILC 10.0.1)

  • Native publish logs "Generating native code"; the binary runs to OK: and exits 0.
  • Reverting the cursor fix fails the native run (reflection serialization disabled). Reverting Include fails it (TicketId[] missing native code). The old step-through doesn't produce a binary at all. The FullTextSearch and DateMember changes only remove analyzer warnings; the smoke passes either way, and CLAUDE.md says so.
  • After the rebase, full CI-shaped run (Release, MTP executables with TRX) green on net9.0 and net10.0: 2513 / 36 / 31. check_scoreboard.py agrees for both TFMs; leak check clean.

🤖 Generated with Claude Code

…ion-serializing paths (#412)

smoke/aot-consumer now runs, in the native image, the async daemon over an
async Snapshot<T> and a multi-stream projection, a keyset cursor walk,
Include() by a strong-typed id, full-text relevance ordering, a DateOnly
comparison, and both projection step-through methods.

- The async daemon needed no Fisher change: it ran natively as soon as it was
  measured.
- CursorPagination writes the cursor with a fixed-shape Utf8JsonWriter and
  reads it with JsonDocument. The v1: payload is byte-identical to the old
  JsonSerializer output, pinned by cursor_encoding against a string captured
  from the old implementation over every key type. A strong-typed identity is
  now carried as its inner value; the old {"Value":...} shape could never be
  decoded, so the second page of any walk over a wrapper-keyed document failed.
- IncludePlan builds a statically closed Enumerable.Contains<object> instead
  of closing Contains and Array.CreateInstance over the member's runtime type.
  That threw natively over a strong-typed id ("TicketId[] is missing native
  code").
- OrderByRelevance and its siblings, and Include's marker, take their closed
  MethodInfo from a delegate rather than MakeGenericMethod by name.
- DateMember renders through options.GetTypeInfo(type).
- Projection step-through renders state through the store's serializer, where
  RunProjectionByNameAsync used JsonSerializer's default options (PascalCase,
  and reflection a native image disables). It no longer reads step properties
  by reflection. Its MakeGenericMethod moved into a non-generic helper,
  because the old spelling crashed ILC 10.0.1 with IndexOutOfRangeException
  once either step-through method was reachable.

Item 1 of #412, strong-typed aggregate identities, stays blocked on
jasperfx#942, and its refusal is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jeremydmiller
jeremydmiller force-pushed the fisher-412-aot-remaining-paths branch from ea44826 to 28ef953 Compare October 4, 2026 18:33
@jeremydmiller
jeremydmiller marked this pull request as ready for review October 4, 2026 18:33
@jeremydmiller
jeremydmiller merged commit b1ccf88 into main Oct 4, 2026
3 checks passed
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