You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Harden persistence boundaries and shard lifecycles
Preserve the base Redis connector alias for typed registrations.
Prevent Redis sequence reset-range overflow at the Int64 boundary.
Normalize EF save guards without redundant per-context state.
Quote Npgsql notification channels with provider semantics.
Avoid shard initialization/removal lock inversion and dispose async-only child services.
Copy file name to clipboardExpand all lines: docs/plans/fusion-library-audit.md
+10-10Lines changed: 10 additions & 10 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1050,8 +1050,8 @@ Status: **completed**. Confidence: **Confirmed by source and focused DI regressi
1050
1050
- Test: `PersistenceAuditRegressionTest.TypedRedisDatabasesShouldRetainTheirOwnConnectors` resolves two typed databases and finds the same connector instance.
1051
1051
-**Maintainer decision:** associate each connector with its context type and inject `RedisConnector<TContext>` or an equivalent typed holder.
1052
1052
-**Recommended:** key the connector by context type and inject `RedisConnector<TContext>` or an equivalent typed holder.
1053
-
-**Resolution:** typed Redis registrations now create and resolve `RedisConnector<TContext>`, isolating each context's connector while preserving the untyped single-database API.
1054
-
-**Validation:**the focused DI regression now resolves two typed databases with distinct connector instances.
1053
+
-**Resolution:** typed Redis registrations now create and resolve `RedisConnector<TContext>`, isolating each context's connector. Each registration also exposes its typed connector through the legacy base `RedisConnector` service, preserving the former last-registration-wins compatibility without using that ambiguous base service to construct typed databases.
1054
+
-**Validation:** focused DI regressions resolve two typed databases with distinct connector instances, resolve the typed connector through the base service for a single context, and retain the last typed registration as the base connector when multiple contexts are registered.
1055
1055
1056
1056
### PERS3. `RedisSequenceSet.Next` reset is not atomic
1057
1057
@@ -1061,8 +1061,8 @@ Status: **completed**. Confidence: **Confirmed by source and focused Redis concu
1061
1061
- Failure: concurrent callers can interleave two resets and return the same sequence number, violating the advertised atomic sequence contract.
1062
1062
-**Maintainer decision:** make the complete conditional reset and increment atomic with one Redis Lua script or an equivalent compare transaction.
1063
1063
-**Recommended:** perform the complete conditional reset/increment in one Redis Lua script or transaction with a compare condition.
1064
-
-**Resolution:**`Next` now performs increment, range validation, conditional reset, and the post-reset increment in one Redis Lua script.
1065
-
-**Validation:** the Redis concurrency regression reproduces duplicate values with the former multi-command reset and now returns a unique contiguous range for 100 simultaneous callers; the existing sequence test also passes.
1064
+
-**Resolution:**`Next` now performs increment, range validation, conditional reset, and the post-reset increment in one Redis Lua script. Negative reset ranges are rejected, and the upper reset boundary saturates at `Int64.MaxValue` instead of wrapping.
1065
+
-**Validation:** the Redis concurrency regression reproduces duplicate values with the former multi-command reset and now returns a unique contiguous range for 100 simultaneous callers. The sequence regression also preserves integer precision above `2^53`, advances through the final two valid `Int64` values without a duplicate, and rejects a negative reset range.
1066
1066
1067
1067
### PERS4. The `DbEvent` resolver is registered with the wrong key type
1068
1068
@@ -1084,8 +1084,8 @@ Status: **completed**. Confidence: **Confirmed by source and focused regression
1084
1084
- Failure: disable/disable/enable still throws on `SaveChanges`, which is especially hazardous for pooled contexts.
1085
1085
- Test: `PersistenceAuditRegressionTest.SaveChangesGuardShouldBeIdempotent` reproduces the remaining guard.
1086
1086
-**Maintainer decision:** track the disabled state explicitly so disabling and enabling are idempotent; irreversible disabling is acceptable only if a safe reversible implementation proves infeasible.
1087
-
-**Resolution:**`EnableSaveChanges`now tracks each context's enabled state in a weak table and normalizes the private `SavingChanges` subscription on every call, keeping repeated disable and enable calls reversible and idempotent without retaining contexts or trusting event state cleared by pooling.
1088
-
-**Validation:** the original focused regression failed 0/1 before the change and passes after exercising both disable and enable twice. A second regression reproduced EF's pooled-context event reset against the first state-only implementation and now passes; the EntityFramework/Npgsql graph builds for all nine supported target frameworks.
1087
+
-**Resolution:**`EnableSaveChanges` normalizes the private `SavingChanges` subscription on every call, keeping repeated disable and enable calls reversible and idempotent. EF pooling clears event subscriptions independently, so the reviewed implementation deliberately avoids a redundant weak-table state lookup and lock that cannot reliably describe the pooled context's current event state.
1088
+
-**Validation:** the original focused regression failed 0/1 before the change and passes after exercising both disable and enable twice. A second regression reproduced EF's pooled-context event reset against the first state-only implementation and now passes with direct subscription normalization; the EntityFramework/Npgsql graph builds for all nine supported target frameworks.
1089
1089
-**Recommended:** make state idempotent, using a tracked flag rather than event-subscription count.
1090
1090
1091
1091
### PERS6. Npgsql locking clauses are emitted in caller order
@@ -1108,8 +1108,8 @@ Status: **completed**. Confidence: **Confirmed by source and focused formatter r
1108
1108
- Failure: valid shard names such as `us-west` produce invalid SQL; quotes and overlength names also lack escaping/normalization.
-**Maintainer decision:** quote identifiers through the provider helper or map arbitrary inputs to bounded safe identifiers.
1111
-
-**Resolution:** the default formatter now reuses `FilePath.GetHashedName` to normalize the complete channel name to a distinct ASCII identifier bounded to PostgreSQL's 63-character limit, adding a stable hash when sanitization or truncation is required.
1112
-
-**Validation:**the original formatter regression failed before the change and now passes for hyphenated, quote-containing, and overlength shard names while checking syntax, uniqueness, and length; the Npgsql project builds for all nine supported target frameworks.
1111
+
-**Resolution:** the default formatter reuses `FilePath.GetHashedName` to normalize the complete channel name to a bounded ASCII identifier, adding a stable hash when sanitization or truncation is required. `LISTEN` and `NOTIFY` now quote every formatted channel through `NpgsqlCommandBuilder`, preserving case-sensitive shard distinctions and safely handling custom formatter output.
1112
+
-**Validation:** formatter regressions pass for hyphenated, quote-containing, and overlength shard names while checking syntax, uniqueness, and length. A SQL-emission regression verifies provider quoting for case-distinct names and embedded quotes; the Npgsql project builds for all nine supported target frameworks.
1113
1113
-**Recommended:** quote through the provider's identifier helper or map inputs to a bounded safe identifier.
1114
1114
1115
1115
### PERS8. An empty custom Npgsql hint crashes SQL generation
@@ -1151,8 +1151,8 @@ Status: **completed**. Confidence: **Confirmed by source and focused regression
1151
1151
- Source: `src/ActualLab.Fusion.EntityFramework/Sharding/ShardDbContextBuilder.cs:115-124` builds a provider per shard and returns only its `IDbContextFactory`.
1152
1152
- Failure: provider-owned pooled factories and disposable dependencies live indefinitely, even when shard factories are no longer used.
1153
1153
-**Maintainer decision:** retain provider ownership in a disposable shard-factory entry and dispose it when the entry is evicted or the root factory stops.
1154
-
-**Resolution:** action-configured shard factories now return an internal provider-owning factory entry and dispose the new provider if factory resolution fails. The root shard factory uses lifecycle-aware cache entries so initialization racing shard eviction or root shutdown cannot leak a newly created provider, and implements synchronous and asynchronous disposal; container-owned custom factories remain untouched.
1155
-
-**Validation:** focused lifecycle tests failed with per-shard dependencies alive, including a deterministically gated creation-versus-eviction race and a provider whose factory resolution failed after creating a disposable singleton. They now observe cleanup on resolution failure, immediate disposal for removed shards, exact owned-provider cleanup across eviction and root-shutdown races, and no disposal of externally supplied factories.
1154
+
-**Resolution:** action-configured shard factories return an internal provider-owning factory entry and use async-capable cleanup even from synchronous resolution-failure paths. Cache entries retain lock-free reads after initialization, but no longer hold their entry lock while building a factory or marking its shard used; eviction starts tracked asynchronous disposal without holding the shard-registry callback, and every later asynchronous disposal observes the shared completion. The initializer disposes any provider produced after eviction. The root factory supports synchronous and asynchronous disposal, while container-owned custom factories remain untouched.
1155
+
-**Validation:** focused lifecycle tests observe cleanup on resolution failure, prompt tracked disposal for removed shards, exact owned-provider cleanup across eviction and root-shutdown races, and no disposal of externally supplied factories. Review regressions also force removal to finish while the shard builder is still active, invoke a shard-update callback captured before rootshutdown only after shutdown completes, and verify async-only dependency cleanup on eviction, root disposal, and factory-resolution failure.
1156
1156
1157
1157
### PERS12. `DbWaitHint` singletons have the wrong runtime type
0 commit comments