-
Notifications
You must be signed in to change notification settings - Fork 697
Fix keyset pagination with monotonic UUIDv7-like task IDs #1215
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 12 commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
0e085be
Initial plan
Copilot f32e1e1
Fix sporadic test failure by using monotonically increasing task IDs
Copilot b699d69
Fix keyset pagination by sorting before applying cursor filter
Copilot fba8ccf
Refactor keyset pagination code styling per review feedback
Copilot 0fd7769
Add TimeProvider support and test for identical timestamp handling
Copilot b8fa0d8
Revert TimeProvider NuGet dependency, use conditional compilation for…
Copilot 1213ce8
Add comment explaining CS0436 suppression for linked InMemoryMcpTaskS…
Copilot a80c8b4
Use conditional namespace to avoid CS0436 type conflicts
Copilot 32dbb40
Use monotonic UUID v7 for task IDs with polyfill for older targets
Copilot fdd7e85
Move UUID v7 polyfill to Common/Polyfills/System/GuidPolyfills.cs
Copilot 4053b29
Revert ListTasksAsync changes, use monotonic GUID with TimeProvider s…
Copilot a637ea2
Move UUID v7 generation logic back to GuidPolyfills class
Copilot 8ef5371
Simplify pagination cursor to use only TaskId since UUID v7 is monoto…
Copilot ef86c4d
Rename polyfill to CreateVersion7(DateTimeOffset) to match .NET 9 API
Copilot 676aecb
Revert to CreateMonotonicUuid name and document why CreateVersion7 ca…
Copilot 0877589
Refactor GuidPolyfills to GuidHelpers with simplified monotonic counter
Copilot 7ea4064
Simplify GUID creation using unsafe pointer manipulation
Copilot e17be78
Fix test project duplicate compile for GuidHelpers.cs on net472
Copilot a9f8a4d
Scope unsafe block to only the pointer manipulation lines
Copilot 446477d
Clarify comment that this is UUIDv7-like format, not RFC-compliant
Copilot 0854883
Move AllowUnsafeBlocks to specific project csproj files
Copilot fe42850
Use two longs (ticks + counter) for monotonic ID generation instead o…
Copilot 5dc6a21
Rename GuidHelpers to IdHelpers since it no longer generates GUIDs
Copilot 14d0d82
Update src/ModelContextProtocol.Core/ModelContextProtocol.Core.csproj
stephentoub File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| namespace System; | ||
|
|
||
| /// <summary> | ||
| /// Provides polyfills for GUID generation methods not available in older .NET versions. | ||
| /// </summary> | ||
| internal static class GuidPolyfills | ||
| { | ||
| private static long s_lastTimestamp; | ||
| private static long s_counter; | ||
| private static readonly object s_lock = new(); | ||
|
|
||
| /// <summary> | ||
| /// Creates a monotonically increasing GUID using UUID v7 format with the specified timestamp. | ||
| /// Uses a counter for intra-millisecond ordering to ensure strict monotonicity. | ||
| /// </summary> | ||
| /// <param name="timestamp">The Unix timestamp in milliseconds to embed in the GUID.</param> | ||
| /// <returns>A new monotonically increasing GUID.</returns> | ||
| public static Guid CreateMonotonicUuid(long timestamp) | ||
| { | ||
| // UUID v7 format (RFC 9562): | ||
| // - 48 bits: Unix timestamp in milliseconds (big-endian) | ||
| // - 4 bits: version (0111 = 7) | ||
| // - 12 bits: counter/sequence (for intra-millisecond ordering) | ||
| // - 2 bits: variant (10) | ||
| // - 62 bits: random | ||
|
|
||
| long counter; | ||
|
|
||
| lock (s_lock) | ||
| { | ||
| if (timestamp == s_lastTimestamp) | ||
| { | ||
| // Same millisecond - increment counter | ||
| s_counter++; | ||
| } | ||
| else | ||
| { | ||
| // New millisecond - reset counter | ||
|
stephentoub marked this conversation as resolved.
Outdated
|
||
| s_lastTimestamp = timestamp; | ||
| s_counter = 0; | ||
| } | ||
|
|
||
| counter = s_counter; | ||
| } | ||
|
|
||
| byte[] bytes = new byte[16]; | ||
|
stephentoub marked this conversation as resolved.
Outdated
|
||
|
|
||
| // Fill lower random bits (last 8 bytes) with random data | ||
| #if NETSTANDARD2_0 | ||
| using (var rng = Security.Cryptography.RandomNumberGenerator.Create()) | ||
| { | ||
| rng.GetBytes(bytes, 8, 8); | ||
| } | ||
| #else | ||
| Security.Cryptography.RandomNumberGenerator.Fill(bytes.AsSpan(8, 8)); | ||
|
stephentoub marked this conversation as resolved.
Outdated
|
||
| #endif | ||
|
|
||
| // Set timestamp (48 bits, big-endian) in first 6 bytes | ||
| bytes[0] = (byte)(timestamp >> 40); | ||
| bytes[1] = (byte)(timestamp >> 32); | ||
| bytes[2] = (byte)(timestamp >> 24); | ||
| bytes[3] = (byte)(timestamp >> 16); | ||
| bytes[4] = (byte)(timestamp >> 8); | ||
| bytes[5] = (byte)timestamp; | ||
|
|
||
| // Set version 7 (0111) in high nibble of byte 6, and high 4 bits of counter in low nibble | ||
| bytes[6] = (byte)(0x70 | ((counter >> 8) & 0x0F)); | ||
|
|
||
| // Set remaining 8 bits of counter in byte 7 | ||
| bytes[7] = (byte)(counter & 0xFF); | ||
|
stephentoub marked this conversation as resolved.
Outdated
|
||
|
|
||
| // Set variant (10) in high 2 bits of byte 8 | ||
| bytes[8] = (byte)((bytes[8] & 0x3F) | 0x80); | ||
|
|
||
| // Convert from big-endian byte array to Guid | ||
| return new Guid( | ||
| (int)(bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3]), | ||
| (short)(bytes[4] << 8 | bytes[5]), | ||
| (short)(bytes[6] << 8 | bytes[7]), | ||
| bytes[8], bytes[9], bytes[10], bytes[11], | ||
| bytes[12], bytes[13], bytes[14], bytes[15]); | ||
| } | ||
| } | ||
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
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
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
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.
Uh oh!
There was an error while loading. Please reload this page.