diff --git a/CHANGELOG.md b/CHANGELOG.md index 72ef7bf1..5c2c4e5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed * BREAKING: the package targets `net10.0` only and no longer supports `net8.0`, which makes this a major release * BREAKING: `Microsoft.Azure.Cosmos` updated from 3.60.0 to 3.62.0, which becomes the minimum version for consumers. Its release notes list a breaking change of their own: thin client mode is enabled by default and does not support resource-token authentication (opt out with `AZURE_COSMOS_THIN_CLIENT_ENABLED=false`) +* The package now depends on `IcedTasks` (>= 0.11.10-beta001): the enumerator returned by `AsAsyncEnumerable` builds its `ValueTask` with the `valueTask` computation expression instead of wrapping a `Task` ## [1.2.0] - 2026-10-01 diff --git a/src/Cosmos/FSharp.Azure.Cosmos.fsproj b/src/Cosmos/FSharp.Azure.Cosmos.fsproj index 6a2eb4b3..d018942d 100644 --- a/src/Cosmos/FSharp.Azure.Cosmos.fsproj +++ b/src/Cosmos/FSharp.Azure.Cosmos.fsproj @@ -40,6 +40,7 @@ + all diff --git a/src/Cosmos/IterationExtensions.fs b/src/Cosmos/IterationExtensions.fs index b8cc3b40..62f1b232 100644 --- a/src/Cosmos/IterationExtensions.fs +++ b/src/Cosmos/IterationExtensions.fs @@ -6,14 +6,16 @@ open System.Runtime.InteropServices open System.Threading open System.Threading.Tasks open Microsoft.Azure.Cosmos +open IcedTasks /// /// Enumerates the items of every page of a . /// /// -/// Implemented by hand rather than with a taskSeq { } computation expression: taskSeq has no -/// dynamic implementation, so it throws whenever the compiler does not -/// turn it into a static state machine, which is the case for assemblies built without optimizations (Debug). +/// Implemented by hand rather than with the computation +/// expression, which has no dynamic implementation, so it throws whenever the +/// compiler does not turn it into a static state machine, which is the case for assemblies built without optimizations +/// (Debug). /// [] type internal FeedIteratorAsyncEnumerator<'T> (iterator : FeedIterator<'T>, cancellationToken : CancellationToken) = @@ -30,28 +32,35 @@ type internal FeedIteratorAsyncEnumerator<'T> (iterator : FeedIterator<'T>, canc /// member _.Current = current - /// - member _.MoveNextAsync () = - let moveNext = task { - let mutable found = false - let mutable exhausted = false - - while not (found || exhausted) do - match page with - | ValueSome items when items.MoveNext () -> - cancellationToken.ThrowIfCancellationRequested () - current <- items.Current - found <- true - | _ when iterator.HasMoreResults -> - disposePage () - let! response = iterator.ReadNextAsync cancellationToken - page <- ValueSome (response.GetEnumerator ()) - | _ -> exhausted <- true - - return found - } - - ValueTask(moveNext) + /// + /// Advances to the next item, reading the next page from the once the current + /// page is exhausted, and checks the before it returns each item. + /// + /// + /// Built with , which produces + /// the itself instead of wrapping a in one. It is safe in Debug + /// builds too, where the compiler leaves every resumable computation expression to its builder's dynamic + /// implementation (dotnet/fsharp#20466): each call builds and starts a new value, and every probe of that builder + /// in passes in both Debug and Release. + /// + member _.MoveNextAsync () = valueTask { + let mutable found = false + let mutable exhausted = false + + while not (found || exhausted) do + match page with + | ValueSome items when items.MoveNext () -> + cancellationToken.ThrowIfCancellationRequested () + current <- items.Current + found <- true + | _ when iterator.HasMoreResults -> + disposePage () + let! response = iterator.ReadNextAsync cancellationToken + page <- ValueSome (response.GetEnumerator ()) + | _ -> exhausted <- true + + return found + } /// member _.DisposeAsync () = @@ -72,11 +81,12 @@ type internal FeedIteratorAsyncEnumerable<'T> (iterator : FeedIterator<'T>, canc interface IAsyncEnumerable<'T> with /// - /// Creates the enumerator, resolving which cancellation token it observes. + /// Creates the enumerator, resolving which it observes. /// /// - /// Mirrors the contract the previous taskSeq { } - /// implementation got from the compiler for free: - + /// Mirrors the contract the previous implementation with + /// got from the compiler for free: + /// - /// typically supplied by /// on an /// await foreach - is honored only when the caller of left its diff --git a/tests/Cosmos.Tests.Infrastructure/FSharp.Azure.Cosmos.Tests.Infrastructure.fsproj b/tests/Cosmos.Tests.Infrastructure/FSharp.Azure.Cosmos.Tests.Infrastructure.fsproj index d5416dd1..a415dcf0 100644 --- a/tests/Cosmos.Tests.Infrastructure/FSharp.Azure.Cosmos.Tests.Infrastructure.fsproj +++ b/tests/Cosmos.Tests.Infrastructure/FSharp.Azure.Cosmos.Tests.Infrastructure.fsproj @@ -27,6 +27,8 @@ + + diff --git a/tests/Cosmos.Tests.Infrastructure/IntegrationInfrastructure.fs b/tests/Cosmos.Tests.Infrastructure/IntegrationInfrastructure.fs index e1872898..236d721f 100644 --- a/tests/Cosmos.Tests.Infrastructure/IntegrationInfrastructure.fs +++ b/tests/Cosmos.Tests.Infrastructure/IntegrationInfrastructure.fs @@ -9,6 +9,8 @@ open System.Threading.Tasks open Microsoft.Azure.Cosmos open Microsoft.VisualStudio.TestTools.UnitTesting +open IcedTasks + open FSharp.Azure.Cosmos.Tests /// @@ -202,16 +204,14 @@ type DatabaseTestApplicationFactory (testContext : TestContext) = interface IAsyncDisposable with /// - member this.DisposeAsync () = - task { - try - // Read at cleanup time: MSTest gives [] a fresh token source, so a test that timed out - // still deletes its database - do! this.CleanupAsync testContext.CancellationToken - finally - client.Dispose () - } - |> ValueTask + member this.DisposeAsync () = valueTaskUnit { + try + // Read at cleanup time: MSTest gives [] a fresh token source, so a test that timed out + // still deletes its database + do! this.CleanupAsync testContext.CancellationToken + finally + client.Dispose () + } /// /// The base class of emulator-backed test classes: creates the fixture of every test, a diff --git a/tests/Cosmos.Tests/FSharp.Azure.Cosmos.Tests.fsproj b/tests/Cosmos.Tests/FSharp.Azure.Cosmos.Tests.fsproj index 8223bcfc..abfdc31e 100644 --- a/tests/Cosmos.Tests/FSharp.Azure.Cosmos.Tests.fsproj +++ b/tests/Cosmos.Tests/FSharp.Azure.Cosmos.Tests.fsproj @@ -40,7 +40,7 @@ - +