Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<bool>` with the `valueTask` computation expression instead of wrapping a `Task`

## [1.2.0] - 2026-10-01

Expand Down
1 change: 1 addition & 0 deletions src/Cosmos/FSharp.Azure.Cosmos.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
<ItemGroup>
<PackageReference Include="FSharp.Control.TaskSeq" />
<PackageReference Include="FsToolkit.ErrorHandling" />
<PackageReference Include="IcedTasks" />
<PackageReference Include="Microsoft.Azure.Cosmos" />
<PackageReference Include="Microsoft.SourceLink.GitHub">
<PrivateAssets>all</PrivateAssets>
Expand Down
66 changes: 38 additions & 28 deletions src/Cosmos/IterationExtensions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@ open System.Runtime.InteropServices
open System.Threading
open System.Threading.Tasks
open Microsoft.Azure.Cosmos
open IcedTasks

/// <summary>
/// Enumerates the items of every page of a <see cref="FeedIterator{T}" />.
/// </summary>
/// <remarks>
/// Implemented by hand rather than with a <c>taskSeq { }</c> computation expression: <c>taskSeq</c> has no
/// dynamic implementation, so it throws <see cref="NotImplementedException" /> 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 <see cref="P:FSharp.Control.TaskSeqBuilderModule.taskSeq" /> computation
/// expression, which has no dynamic implementation, so it throws <see cref="NotImplementedException" /> whenever the
/// compiler does not turn it into a static state machine, which is the case for assemblies built without optimizations
/// (Debug).
/// </remarks>
[<Sealed>]
type internal FeedIteratorAsyncEnumerator<'T> (iterator : FeedIterator<'T>, cancellationToken : CancellationToken) =
Expand All @@ -30,28 +32,35 @@ type internal FeedIteratorAsyncEnumerator<'T> (iterator : FeedIterator<'T>, canc
/// <inheritdoc />
member _.Current = current

/// <inheritdoc />
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<bool>(moveNext)
/// <summary>
/// Advances to the next item, reading the next page from the <see cref="FeedIterator{T}" /> once the current
/// page is exhausted, and checks the <see cref="CancellationToken" /> before it returns each item.
/// </summary>
/// <remarks>
/// Built with <see cref="P:IcedTasks.ValueTasks.ValueTasks.ValueTaskBuilderModule.valueTask" />, which produces
/// the <see cref="ValueTask{T}" /> itself instead of wrapping a <see cref="Task{T}" /> 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 <see cref="T:FSharp.Azure.Cosmos.Tests.IcedTasksProbeTests" /> passes in both Debug and Release.
/// </remarks>
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
}

/// <inheritdoc />
member _.DisposeAsync () =
Expand All @@ -72,11 +81,12 @@ type internal FeedIteratorAsyncEnumerable<'T> (iterator : FeedIterator<'T>, canc
interface IAsyncEnumerable<'T> with

/// <summary>
/// Creates the enumerator, resolving which cancellation token it observes.
/// Creates the enumerator, resolving which <see cref="CancellationToken" /> it observes.
/// </summary>
/// <remarks>
/// Mirrors the <see cref="EnumeratorCancellationAttribute" /> contract the previous <c>taskSeq { }</c>
/// implementation got from the compiler for free: <paramref name="enumeratorCancellationToken" /> -
/// Mirrors the <see cref="EnumeratorCancellationAttribute" /> contract the previous implementation with
/// <see cref="P:FSharp.Control.TaskSeqBuilderModule.taskSeq" /> got from the compiler for free:
/// <paramref name="enumeratorCancellationToken" /> -
/// typically supplied by
/// <see cref="System.Threading.Tasks.TaskAsyncEnumerableExtensions.WithCancellation" /> on an
/// <c>await foreach</c> - is honored only when the caller of <see cref="AsAsyncEnumerable" /> left its
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
</ItemGroup>

<ItemGroup>
<!-- Referenced directly although the library references it too: the fixtures use valueTaskUnit themselves, so they must not depend on what the library happens to reference. -->
<PackageReference Include="IcedTasks" />
<PackageReference Include="Microsoft.Azure.Cosmos" />
<PackageReference Include="MSTest.TestFramework" />
</ItemGroup>
Expand Down
20 changes: 10 additions & 10 deletions tests/Cosmos.Tests.Infrastructure/IntegrationInfrastructure.fs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ open System.Threading.Tasks
open Microsoft.Azure.Cosmos
open Microsoft.VisualStudio.TestTools.UnitTesting

open IcedTasks

open FSharp.Azure.Cosmos.Tests

/// <summary>
Expand Down Expand Up @@ -202,16 +204,14 @@ type DatabaseTestApplicationFactory (testContext : TestContext) =

interface IAsyncDisposable with
/// <inheritdoc />
member this.DisposeAsync () =
task {
try
// Read at cleanup time: MSTest gives [<TestCleanup>] 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 [<TestCleanup>] a fresh token source, so a test that timed out
// still deletes its database
do! this.CleanupAsync testContext.CancellationToken
finally
client.Dispose ()
}

/// <summary>
/// The base class of emulator-backed test classes: creates the fixture of every test, a
Expand Down
2 changes: 1 addition & 1 deletion tests/Cosmos.Tests/FSharp.Azure.Cosmos.Tests.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
</ItemGroup>

<ItemGroup>
<!-- Referenced directly rather than through the library: the probe tests gate whether library code may use IcedTasks at all. -->
<!-- Referenced directly although the library references it too: the probe tests use IcedTasks themselves and gate which of its builders library code may use, so they must not depend on what the library happens to reference. -->
<PackageReference Include="IcedTasks" />
<PackageReference Include="MSTest" />
<PackageReference Include="Unquote" />
Expand Down
Loading