feat: Locked/LockedOrdered delegate all optional optimization interfaces #minor - #44
Merged
Conversation
…ces #minor Wrapping a set in Locked or LockedOrdered no longer loses its fast paths: both wrappers now implement Unioner, Intersectioner, Differencer, SymmetricDifferencer, Equaler, Disjointer, Subsetter, Maxer, and Minner by delegating to the inner set under the read lock, declining to the generic path when the inner set doesn't implement or declines the hook. Deadlock safety: the receiver's own lock is acquired normally, but a locked operand is unwrapped with a non-blocking TryRLock — under contention the delegation declines and the package-level function falls back to the generic path, which locks one set at a time. Mirror-image concurrent operations on the same pair of locked sets therefore cannot deadlock (pinned by a stress test). - delegated set-algebra results come back wrapped (Locked / LockedOrdered), so they stay concurrency-safe and keep the same-underlying-type contract - Locked's element type is only comparable, so its Max/Min match the inner set structurally; instantiated with an ordered type it satisfies Maxer/Minner - typed-nil and zero-value (nil inner) wrappers decline as both receivers and operands - rapid differential tests over wrapped×wrapped, wrapped×bare, cross-wrapper, and declining inner-type combinations; result-type pins; deterministic contention-decline test; nil/zero pins; -race stress test running both operand orders against concurrent writers Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RLrWSEKLMPuf1kq1KDCoxs
Contributor
There was a problem hiding this comment.
Pull request overview
This PR updates the Locked and LockedOrdered wrappers so they no longer drop optional optimization fast paths (set-algebra, predicates, and Max/Min) when wrapping an optimizable inner set. The wrappers now implement the optional interfaces by delegating to the inner set under a read lock, using TryRLock when unwrapping a locked operand to avoid deadlock and decline into the generic one-lock-at-a-time fallback under contention.
Changes:
- Implement optional interface delegation for
LockedandLockedOrdered, including deadlock-safe operand unwrapping via non-blocking read locks. - Add comprehensive tests (property-based + contention + race/deadlock stress) to validate correctness, result types, and concurrency safety.
- Update documentation (godoc, README, CLAUDE.md) to describe the delegation behavior and deadlock-avoidance rule.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| stresstest/locked_delegate_stress_test.go | Adds a -race-oriented concurrency stress test to validate no-deadlock behavior under mirrored operand orders and concurrent writers. |
| set.go | Updates set-algebra optional-interface documentation to note locked-wrapper delegation preserves fast paths. |
| README.MD | Documents locked-wrapper delegation, contention decline behavior, and wrapped results. |
| locked.go | Implements delegation for all optional optimization interfaces (algebra, predicates, Max/Min) with deadlock-safe operand unwrapping. |
| locked_ordered.go | Mirrors the delegation behavior for LockedOrdered, ensuring ordered results remain ordered and safely wrapped. |
| locked_delegate_test.go | Adds targeted tests covering delegation correctness, result wrapper types, contention declines, and nil/zero-value decline behavior. |
| CLAUDE.md | Updates repository documentation to reflect locked-wrapper optional-interface delegation and deadlock safety. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Third item from the optional-interface survey (follow-up to #42/#43): wrapping a set in
LockedorLockedOrderedno longer silently discards its fast paths. Both wrappers now implement all nine optional interfaces —Unioner/Intersectioner/Differencer/SymmetricDifferencer,Equaler/Disjointer/Subsetter, andMaxer/Minner— by delegating to the inner set under the read lock, declining to the generic path when the inner set doesn't implement or declines the hook.Deadlock safety
Delegation may need two locks (receiver + a locked operand), and two goroutines running mirror-image operations on the same pair is the classic deadlock shape. The rule: the receiver's own lock is acquired normally, but a locked operand is unwrapped with a non-blocking
TryRLock— under contention the delegation declines, and the package-level function falls back to the generic path, which locks one set at a time. The declining-boolean contract absorbs contention the same way it absorbs type mismatches: worst outcome is generic speed, never a wrong result or a deadlock. A-racestress test runs both operand orders concurrently against writers to pin this.Notes
Locked/LockedOrdered), staying concurrency-safe and keeping the package functions' same-underlying-type-as-a contractLocked's element type is onlycomparable, so itsMax/Minmatch the inner set structurally (interface{ Max() (M, bool) }); instantiated with an ordered element type,LockedsatisfiesMaxer/Minner(compile-time asserted)Locked(BitSet)×LockedOrdered(BitSet)still hits the word-wise fast pathTesting
TestLockedDelegation: rapid differential test against Map-based generic results across seven operand pairings — wrapped×wrapped, wrapped×bare, cross-wrapper, and declining combinations (mismatched inner types, non-optimizableMapinner) — for all four algebra ops, all four predicates, andMax/Min; algebra results asserted to beLockersTestLockedDelegationResultTypes: pins the wrapper/inner types of delegated results and method-level declinesTestLockedDelegationContention: deterministic decline while the operand is write-locked (and recovery after unlock; self-operand delegation)TestLockedDelegationNilAndZero: typed-nil and zero-value pinsstresstest.TestLockedDelegationConcurrency: 4 readers in mirror-image operand orders + 2 writers under-race— terminates (no deadlock) and race-cleango vet,staticcheck, andgo test -race ./...all clean🤖 Generated with Claude Code
https://claude.ai/code/session_01RLrWSEKLMPuf1kq1KDCoxs