perf: parse DID document entries once per document #203
No reviewers
Labels
No labels
breaking-change
bug
documentation
duplicate
enhancement
good first issue
help wanted
performance
question
wontfix
No milestone
No project
No assignees
5 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Grandiras/ATProto.NET!203
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "issue-200-identity-perf"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #200.
DidDocumentlookups now parse and decode each entry once per (immutable, shared) document. Every property they read isinit-only, so the caches cannot go stale.DidDocumentServicecaches its parsed http(s) endpoint, andVerificationMethoditsdid:key. The strictTryGetVerificationKeyremembers that it decoded the key, soParseDidKeyruns once per method; failures are not cached, so errors behave as before.GetHandlevalidates once viaHandle.TryCreateand still rejectsat://@….Handle.IsValidSyntax.PlcClientdrops the invariant-culture formatting of non-negative integers.GetPdsEndpointGetSigningKeyTryGetVerificationKey("#atproto")GetHandleTryGetVerificationKeysits on every signature check against a cached document (service auth, firehose commit verification).Net
src: −14 lines. Build has 0 warnings; tests pass.🤖 Generated with Claude Code
🤖 Reviewing this PR — the review will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the review runs.
✅ Verdict: approve — no blocking issues found.
The caches are safe and behavior is preserved. I verified the two externally-checkable claims:
Utf8JsonWriter.WriteStringValue(null)writes the JSONnullvalue, so the rewrittenStringOrNullConverter.Writeis output-identical; andNumberFormatInfo.NativeDigitshas no effect on formatting (only Basic Latin digits are used, and default "G" integer formatting emits no group separators), so the "non-negative integers format the same under every culture" comment inPlcClientis correct — and the values are validated non-negative anyway, so the culture-dependent negative sign can't appear.Other checks: the
at://@pre-check inGetHandlereproduces exactly what the oldIsValidSyntax-then-TryParseordering rejected (single-@stripping vs. syntax check), and the first-at://-entry-decides rule is unchanged. The removedJsonTokenType.Nullcase inStringListConverter.Readwas genuinely dead code: withoutHandleNull => truethe serializer handles null tokens itself, as the new comment states;NonNullListConvertercorrectly keepsHandleNull.TryGetVerificationKeystill distinguishes absent/malformed, failures are still not cached (exceptions inMultibaseToBytes/ParseDidKeyleave nothing cached, so errors repeat as before), and the_didKey/HttpEndpoint/KeyCheckedcaches derive only frominit-only properties, so they cannot go stale. Null entries in hand-built lists are still skipped.Minor note, no action needed: the lazy caches introduce a benign data race when a document is shared across threads — concurrent first calls may each compute the value — but reference/bool writes are atomic and the values are deterministic (idempotent lazy init), so this is fine as a perf cache.
⏱ 20m 48s · 12 steps · $0.0423 · tokens: 158.2k in / 1.3k out (+239.2k cache-read) · Go: 5h 0% · wk 0% · mo 91%
📜 Full session transcript
11fecd366f5b47094bb95b47094bb96c1d0ef9bc🤖 Reviewing this PR — the review will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the review runs.
✅ Verdict: approve — no blocking issues found.
Re-review: no blocking issues; the approved state stands unchanged. Re-verified against the current sources:
KeyCheckedflag is set only afterParseDidKeysucceeds, so failed decodes stay uncached and repeat exactly as before; statuses (Absent/Malformed/Found) and the first-entry-decides rule for both services and verification methods are unchanged (thetypemismatch still returnsMalformedbefore the endpoint is consulted)._didKeyandHttpEndpointcaches derive only frominit-only properties, so they cannot go stale on the immutable, shared document; aToDidKey()result ofnullis not cached by??=, which is the correct no-value semantics.GetHandle'sat://@pre-check reproduces the oldIsValidSyntaxrejection of an@-prefixed claim, and the existing theory matrix covers it; the new repeated-lookup test (Assert.Sameon the memoizedUri, repeatedTryGetVerificationKey, agreement withGetSigningKey) directly exercises the caches.WriteStringValue(null)emits JSONnull; thenulltoken case inStringListConverter.Readwas genuinely dead (HandleNullunset means the serializer handles the nullable type itself), whileNonNullListConvertercorrectly keepsHandleNull => trueand its malformed-entry exception.PlcClientinterpolation change is behavior-preserving: non-negativelongs (all three are validated non-negative) format identically across cultures.Minor non-blocking note carried over from the previous pass: the lazy caches are idempotent non-atomic — concurrent first reads on a shared document may each compute the value once — but the writes are atomic and the values deterministic, so this is benign for a perf cache.
⏱ 0m 44s · 23 steps · $0.0157 · tokens: 33.4k in / 2.2k out (+261.0k cache-read) · Go: 5h 1% · wk 0% · mo 91%
📜 Full session transcript
@claude /review
🤖 Reviewing this PR — follow along in the action run; the reply will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the task runs.
✅ Verdict: approve — no blocking issues found.
The change looks correct and I'd merge it.
_didKey,_endpointandKeyCheckedall derive frominit-only properties. Concurrent first calls can race, but they all compute the same value, so the race is benign. Failures are not cached, so errors behave as before.GetHandle: I checkedHandle.TryCreate. It strips a leading@, so the explicitat://@rejection preserves the old behaviour. Thetextargument ofnullmakes it take the string from the span.HandleNullis set, so removing the null branches andHandleNullis safe. Thevalue!in the list converter'sWriteis fine for the same reason.PlcClient:afteris alongchecked non-negative andcountis anint?, so culture-sensitive formatting cannot change the output.Minor nit: the CHANGELOG entry doesn't list the
GetHandleallocation change as a separate bullet, but it is mentioned in the same line.🧠 claude-sonnet-5-5 · ⏱ 0m 13s · 4 turns · tokens: 18.8k in / 1.3k out (+70.7k cache-read)
📜 Full session transcript
6c1d0ef9bc661d4a0371Approval carry-over: this head (
661d4a0) is a CHANGELOG-only rebase of the approved6c1d0ef. The PR's patch against its merge base, excludingCHANGELOG.md, has the samegit patch-id(bd21783c), and CI is green on the new head. opencode-reviewer and claude-reviewer both approved6c1d0ef.