Host-held adapter state, and fix pooled adapters sharing one configuration - #131
Conversation
…nfig Two changes the resident runtime needs before a database adapter can exist, both useful on their own. Host-held state. An adapter must not be the system of record for its own progress: the supervisor restarts it, the next instance may come up on another node, and a pooled one is not the same process twice — so a polling receiver that keeps its cursor in a field replays from the beginning at the least convenient moment. IAdapterContext gains GetStateAsync / SetStateAsync, answered over a new StateRequest / StateResult frame pair the same way PublishAsync is answered by EventAck. Host side is IAdapterStateStore, the counterpart of IAdapterEventSink; InMemoryAdapterStateStore keeps samples and tests working untouched, and a real deployment registers its own through AddResidentAdapters<TSink, TStateStore>(). State is keyed by instance, is not bounded by the in-flight window, and raises rather than swallows failures — see design doc 14.9 for why each of those matters. Pool keying. RentAsync keyed its pools on spec.AdapterId, and GetOrAdd captures the spec of whichever caller created the pool first, including its startup values — where the connection string and credentials live. One adapter serving two data sources therefore handed the second one a process connected as the first, silently. Masked so far because bus providers run as exclusive instances and never rent; anything going through ResidentAdapterRuntime was exposed. Fixed by keying on AdapterSpec.PoolKey when set, otherwise on the adapter id plus a hash of the startup values. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository: simplify9/coderabbit/.coderabbit.yaml Review profile: ASSERTIVE Plan: Advanced Run ID: 📒 Files selected for processing (12)
📝 SummarySummary
Risk: risk:medium Security-sensitive areas
Test coverage
Operational concerns
WalkthroughThe change adds host-held adapter state through correlated protocol frames, a pluggable state store, and new context APIs. It also isolates resident pools by explicit keys or startup-value hashes, with sample usage, tests, and documentation. ChangesAdapter state and pool isolation
Estimated code review effort: 4 (Complex) | ~45 minutes Change: Feature Suggested labels: Suggested reviewers: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Two changes the resident runtime needs before Bitween's database adapters can exist. Both stand on their own.
1. Host-held adapter state
An adapter must not be the system of record for its own progress. The supervisor restarts it, the next instance may come up on a different node, and a pooled one is not the same process twice — so a polling receiver that keeps its cursor in a field replays from the beginning at the least convenient moment.
IAdapterContextgains two calls:On the wire that is a new
StateRequest(get / set / delete) answered byStateResult, correlated by frame id exactly asEventAckanswers anEvent. Host side it isIAdapterStateStore, the counterpart ofIAdapterEventSink: where the sink is how an adapter hands work in, this is how it remembers where it got to.InMemoryAdapterStateStoreis registered by default so samples and existing tests are untouched; a real deployment registers its own via the newAddResidentAdapters<TSink, TStateStore>().Three deliberate properties, argued in design doc §14.9:
This is the role Airbyte's
stateargument plays for its connectors.2. A pool keyed by adapter id is a configuration leak
RentAsynckeyed its pools onspec.AdapterId, andGetOrAddcaptures the spec of whichever caller created the pool first — including its startup values, which is where the connection string and the credentials live. One adapter serving two data sources therefore handed the second one a process connected as the first, with no error anywhere.Masked until now because bus providers run as exclusive instances keyed by data source and never go through the pool. Anything that rents — Bitween's Xchange pipeline does, through
ResidentAdapterRuntime— was exposed, and a pooled database adapter would be exposed by design.Fixed by keying on
AdapterSpec.PoolKeywhen set, otherwise on the adapter id plus a hash of the startup values, so identical configuration shares warm processes and differing configuration cannot. Hashed rather than concatenated because the key reaches logs and diagnostics.Compatibility
Additive. New proto fields take unused numbers, the default state store keeps existing hosts working with no code change, and no existing adapter or host behaviour changes.
IAdapterContextgains members, so an out-of-tree implementation of that interface (test fakes, mostly) needs the two methods.Tests
Three new tests in
ResidentAdapterTests: state survives a restart of the adapter process, state is scoped to the instance, and pool keys separate specs that differ in configuration while staying free of credentials.dotnet test— 82 passed, 1 failed:ResourceLimitTests.Lowering_the_soft_ceiling_takes_effect_without_a_restart. That one fails identically on a cleanorigin/maincheckout on this machine, so it is pre-existing and not from this branch.Docs: design doc §14.9 and §14.10, plus a line in
docs/README.md.🤖 Generated with Claude Code