Skip to content

feat: use valueTask for the feed iterator's MoveNextAsync - #41

Merged
xperiandri merged 3 commits into
mainfrom
feat/valuetask-feed-iterator
Oct 9, 2026
Merged

xperiandri merged 3 commits into
mainfrom
feat/valuetask-feed-iterator

Conversation

@xperiandri

@xperiandri xperiandri commented Oct 3, 2026 •

Copy link
Copy Markdown
Collaborator

Proposed Changes

The first use of IcedTasks in library code. #40, now merged, added IcedTasks 0.11.10-beta001 and the tests that check its builders in Debug and Release (the probes); every probe of valueTask, valueTaskUnit, cancellableTask and cancellableValueTask passes, so the gate in .github/copilot-instructions.md allows these builders.

  • feat: use valueTask for the feed iterator's MoveNextAsync: FeedIteratorAsyncEnumerator.MoveNextAsync built a Task with task { } and wrapped it in a ValueTask<bool>, a hand-rolled wrapper the coding guidelines forbid. It now uses valueTask { } around the same loop. The enumerator itself stays hand-written, because taskSeq still has no dynamic implementation. The XML documentation of MoveNextAsync says why this is safe in Debug builds. The package now depends on IcedTasks (>= 0.11.10-beta001), recorded in the CHANGELOG.
  • refactor: use valueTaskUnit for the test fixture's DisposeAsync: the task { } |> ValueTask wrapper in DisposeAsync of tests/Cosmos.Tests.Infrastructure becomes valueTaskUnit { }. The infrastructure project references IcedTasks directly.
  • docs: reference taskSeq through see cref in the feed iterator docs: the documentation of FeedIteratorAsyncEnumerator and of FeedIteratorAsyncEnumerable.GetAsyncEnumerator named the taskSeq builder in <c> tags and the token type in plain words; it now refers to both through <see cref>, as the rest of the file does.

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)

Internal change; the public API is unchanged. The package gains the IcedTasks dependency.

Checklist

  • Build and tests pass locally
  • I have added tests that prove my fix is effective or that my feature works (if appropriate)
  • I have added necessary documentation (if appropriate)

Further comments

  • Behaviour: a check harness compared the old task wrapper, the new valueTask version and the built library over these cases, in Debug and Release:

    • several pages and empty pages;
    • asynchronously completing and pending pages;
    • 100,000 empty pages;
    • cancellation before, during and between pages;
    • failing reads;
    • double dispose.

    All transcripts were identical, and allocation per synchronously completing call is unchanged.

  • Tests without the emulator on the current head (category Unit, all 30 probes included): Debug 77/77 and Release 77/77. The full emulator suite last ran before the latest rebase and documentation changes, at the default parallelism: Debug 185/185 and Release 185/185, none skipped; the library and test-infrastructure code it ran are unchanged since. Every commit builds in Release with the same 19 warnings as main; Fantomas is clean.

  • dotnet pack reports NU5104: a stable package should not depend on the prerelease IcedTasks 0.11.10-beta001. It is a warning, and CI packages are prereleases themselves. A stable release of this package should wait for IcedTasks 0.11.10 stable.

🤖 Generated with Claude Code

Copilot AI balanced review requested due to automatic review settings October 3, 2026 00:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟢 Approval recommended

The focused implementation preserves behavior, follows project guidance, and is covered by existing iterator and builder probes.

Review effort: Balanced
Findings: None

What changed in this PR

Replaces the feed iterator’s hand-built Task-wrapped ValueTask with the approved IcedTasks valueTask computation expression.

Changes:

  • Uses valueTask in MoveNextAsync.
  • Adds IcedTasks as a library dependency.
  • Documents builder-specific safety requirements and the dependency change.
File Description
.github/​copilot-instructions.md Defines builder-specific IcedTasks usage gates.
CHANGELOG.md Records the new dependency and implementation change.
src/​Cosmos/​FSharp.Azure.Cosmos.fsproj Adds the IcedTasks package reference.
src/​Cosmos/​IterationExtensions.fs Converts MoveNextAsync to valueTask.
tests/​Cosmos.Tests/​FSharp.Azure.Cosmos.Tests.fsproj Clarifies the direct probe-test dependency.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@xperiandri
xperiandri force-pushed the feat/valuetask-feed-iterator branch from 0af1f4b to 4c87a2e Compare October 3, 2026 01:38
@xperiandri
xperiandri force-pushed the feat/valuetask-feed-iterator branch from 4c87a2e to 787df6f Compare October 4, 2026 23:16
@xperiandri xperiandri changed the title feat: use valueTask for the feed iterator's MoveNextAsync feat: use valueTask for the feed iterator's MoveNextAsync Oct 9, 2026
@xperiandri
xperiandri force-pushed the feat/valuetask-feed-iterator branch from 787df6f to 09f1908 Compare October 9, 2026 23:29
Comment thread src/Cosmos/IterationExtensions.fs Outdated
Base automatically changed from feat/icedtasks to main October 9, 2026 23:40
@xperiandri
xperiandri force-pushed the feat/valuetask-feed-iterator branch from 09f1908 to a250667 Compare October 9, 2026 23:44
xperiandri and others added 3 commits October 10, 2026 01:44
FeedIteratorAsyncEnumerator.MoveNextAsync built a Task with task { } and
wrapped it in a ValueTask<bool>, the kind of hand-rolled wrapper the
coding guidelines forbid. It was kept only because library code could
not use IcedTasks yet; valueTask is now allowed, so MoveNextAsync
returns valueTask { } around the same loop.

The XML documentation of MoveNextAsync says why this is safe in Debug
as well: there the pinned SDK leaves every resumable computation
expression to its builder's dynamic implementation (dotnet/fsharp#20466),
each call builds and starts a new value, and every valueTask probe in
IcedTasksProbeTests passes in both Debug and Release.

The library now references IcedTasks, so consumers get it
(>= 0.11.10-beta001) as a transitive dependency, recorded in the
changelog. The enumerator stays hand-written: taskSeq still has no
dynamic implementation.

DisposeAsync keeps returning ValueTask.CompletedTask: it converts
nothing, and valueTaskUnit would only add a state machine around a
synchronous body. No other hand-rolled Task or ValueTask wrapper exists
in src.

The test project keeps its direct IcedTasks reference, as the probes
use IcedTasks themselves; its comment now says why.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DatabaseTestApplicationFactory implemented IAsyncDisposable.DisposeAsync
by piping a task { } into the ValueTask constructor, the kind of
hand-rolled wrapper the coding guidelines forbid. The shared test
infrastructure reached main before IcedTasks did, so it could not use
valueTaskUnit yet. Now that valueTaskUnit is allowed, DisposeAsync
returns valueTaskUnit { } around the same try/finally: the database is
still deleted with the TestContext token read at cleanup time, and the
client is still disposed when that cleanup throws.

It is safe in Debug as well: every valueTaskUnit probe in
IcedTasksProbeTests passes in both Debug and Release, and each call of
DisposeAsync builds and starts a new value, so the dynamic
implementation that Debug builds fall back to (dotnet/fsharp#20466)
never starts one value twice.

The infrastructure project references IcedTasks directly, as it does
Microsoft.Azure.Cosmos, although the library brings it in transitively:
the fixtures use it themselves and must not depend on what the library
happens to reference. No other Task wrapped into a ValueTask is left in
the repository. The AsTask () in IterationExtensionsUnitTests is no
round-trip: it converts the ValueTask<bool> of MoveNextAsync once into
the Task of the Func<Task> that Assert.ThrowsExactlyAsync takes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The documentation of FeedIteratorAsyncEnumerator and of
FeedIteratorAsyncEnumerable.GetAsyncEnumerator named the taskSeq builder
in <c> tags and the token type in plain words. The contributor guidance
keeps <c> for literal values and refers to types and members through
<see cref>, so both now do, as the rest of the file already does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@xperiandri
xperiandri force-pushed the feat/valuetask-feed-iterator branch from a250667 to 2d0970e Compare October 9, 2026 23:44
@xperiandri
xperiandri requested a balanced review from Copilot October 9, 2026 23:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The focused refactor follows project guidance and is supported by existing iterator tests and builder probes.

0 open findings

🧠 Review effort: Balanced

@xperiandri
xperiandri merged commit 1cf6b17 into main Oct 9, 2026
8 checks passed
@xperiandri
xperiandri deleted the feat/valuetask-feed-iterator branch October 9, 2026 23:54
xperiandri added a commit that referenced this pull request Oct 10, 2026
Our TheAngryByrd/IcedTasks#67 fixed the multi-start defect the Phase 0
probes found and shipped in IcedTasks 0.11.10-beta001. With it every
probe passes in Debug and Release, so the gate now covers valueTask,
valueTaskUnit, cancellableTask and cancellableValueTask, and the risk
is resolved except for the prerelease dependency, recorded as an open
point: a stable release of this library waits for IcedTasks 0.11.10.

The execution section no longer says that Phase 0 did not reach the
gate: it names the builders the gate allows and records that #41
converted both hand-rolled wrappers. The Phase 0 result describes the
0.11.9 outcome in the past tense and counts the 30 probes over the four
builders they cover; there are no coldTask probes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xperiandri added a commit that referenced this pull request Oct 10, 2026
Our TheAngryByrd/IcedTasks#67 fixed the multi-start defect the Phase 0
probes found and shipped in IcedTasks 0.11.10-beta001. With it every
probe passes in Debug and Release, so the gate now covers valueTask,
valueTaskUnit, cancellableTask and cancellableValueTask, and the risk
is resolved except for the prerelease dependency, recorded as an open
point: a stable release of this library waits for IcedTasks 0.11.10.

The execution section no longer says that Phase 0 did not reach the
gate: it names the builders the gate allows and records that #41
converted both hand-rolled wrappers. The Phase 0 result describes the
0.11.9 outcome in the past tense and counts the 30 probes over the four
builders they cover; there are no coldTask probes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xperiandri added a commit that referenced this pull request Oct 10, 2026
Our TheAngryByrd/IcedTasks#67 fixed the multi-start defect the Phase 0
probes found and shipped in IcedTasks 0.11.10-beta001. With it every
probe passes in Debug and Release, so the gate now covers valueTask,
valueTaskUnit, cancellableTask and cancellableValueTask, and the risk
is resolved except for the prerelease dependency, recorded as an open
point: a stable release of this library waits for IcedTasks 0.11.10.

The execution section no longer says that Phase 0 did not reach the
gate: it names the builders the gate allows and records that #41
converted both hand-rolled wrappers. The Phase 0 result describes the
0.11.9 outcome in the past tense and counts the 30 probes over the four
builders they cover; there are no coldTask probes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xperiandri added a commit that referenced this pull request Oct 10, 2026
Our TheAngryByrd/IcedTasks#67 fixed the multi-start defect the Phase 0
probes found and shipped in IcedTasks 0.11.10-beta001. With it every
probe passes in Debug and Release, so the gate now covers valueTask,
valueTaskUnit, cancellableTask and cancellableValueTask, and the risk
is resolved except for the prerelease dependency, recorded as an open
point: a stable release of this library waits for IcedTasks 0.11.10.

The execution section no longer says that Phase 0 did not reach the
gate: it names the builders the gate allows and records that #41
converted both hand-rolled wrappers. The Phase 0 result describes the
0.11.9 outcome in the past tense and counts the 30 probes over the four
builders they cover; there are no coldTask probes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xperiandri added a commit that referenced this pull request Oct 11, 2026
Our TheAngryByrd/IcedTasks#67 fixed the multi-start defect the Phase 0
probes found and shipped in IcedTasks 0.11.10-beta001. With it every
probe passes in Debug and Release, so the gate now covers valueTask,
valueTaskUnit, cancellableTask and cancellableValueTask, and the risk
is resolved except for the prerelease dependency, recorded as an open
point: a stable release of this library waits for IcedTasks 0.11.10.

The execution section no longer says that Phase 0 did not reach the
gate: it names the builders the gate allows and records that #41
converted both hand-rolled wrappers. The Phase 0 result describes the
0.11.9 outcome in the past tense and counts the 30 probes over the four
builders they cover; there are no coldTask probes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xperiandri added a commit that referenced this pull request Oct 11, 2026
Our TheAngryByrd/IcedTasks#67 fixed the multi-start defect the Phase 0
probes found and shipped in IcedTasks 0.11.10-beta001. With it every
probe passes in Debug and Release, so the gate now covers valueTask,
valueTaskUnit, cancellableTask and cancellableValueTask, and the risk
is resolved except for the prerelease dependency, recorded as an open
point: a stable release of this library waits for IcedTasks 0.11.10.

The execution section no longer says that Phase 0 did not reach the
gate: it names the builders the gate allows and records that #41
converted both hand-rolled wrappers. The Phase 0 result describes the
0.11.9 outcome in the past tense and counts the 30 probes over the four
builders they cover; there are no coldTask probes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants