Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

Wrapping combinators (TaskSeq.map, taskSeq { for .. }) yield the final item twice over an external IAsyncEnumerable on a non-default TaskScheduler (Orleans grain)

オープン
#452 コメント 11 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

評価

難易度
4/5
見積もり時間
3〜5日
初心者へのやさしさ
45/100
issue の種類
バグ
明瞭さ
おおむね明確
活発さ
活発
技術スタック
fsharp
領域
backend

調査の方向性

まず tests/Orleans.FSharp.Integration/FunctionalPhaseFIntegrationTests.fs の598-627行付近と FunctionalPhaseFFixture.fs の305-324行付近を確認し、その後、提供されているフィルター済みの dotnet test コマンドを実行します。Orleans scheduler 下の TaskSeq.map および taskSeq wrapper パスと、直接列挙を比較します。wrapped パスで最後の項目が2回返されなくなり、直接列挙が引き続き正しく動作すれば完了です。

索引モデルが issue の本文から書いたものです。

説明

bug needs investigation

Summary

TaskSeq.map and taskSeq { for item in upstream do yield ... } over an externally produced IAsyncEnumerable<T> each yield the final item twice when the consumption runs on a non-default TaskScheduler — observed deterministically inside a Microsoft Orleans grain activation (Orleans' per-activation scheduler). Enumerating the same IAsyncEnumerable<T> directly (the exact await foreach desugaring: GetAsyncEnumerator / MoveNextAsync / Current / DisposeAsync) yields the correct count.

Environment

  • FSharp.Control.TaskSeq 0.6.0
  • .NET 10 (net10.0), F#
  • Reproduces identically on Microsoft Orleans 10.1.0 and 10.2.2 (the enumerable being wrapped is produced by Orleans' IAsyncEnumerableGrainExtension machinery — batched MoveNext pulls over grain calls)

The discriminator (what isolates it to the wrapping construct)

One grain method consumes the same 3-item upstream stream three ways and returns the three counts as an ordinary unary reply, so nothing about our own streaming-reply transport participates in the measurement:

let! direct = TaskSeq.toListAsync (upstream.watch (label, 3))          // plain enumeration of the source

let! viaMap =
    upstream.watch (label2, 3)
    |> TaskSeq.map (fun tick -> tick.note)                             // wrapped
    |> TaskSeq.toListAsync

let! viaFor =
    TaskSeq.toListAsync (taskSeq {                                     // wrapped
        for tick in upstream.watch (label3, 3) do yield tick.note
    })

Result, measured 2026-08-18, stable across runs and across both Orleans versions:

(List.length direct, List.length viaMap, List.length viaFor) = (3, 4, 4)

direct = 3 is correct; both wrapping forms report 4 items for a 3-item stream, the last item duplicated.

What tracing showed

Instrumenting all enumerator layers on the failing path localized it precisely: the taskSeq wrapper yielded one extra MoveNextAsync = true with a stale Current immediately after the inner enumerator had already answered false.

What we ruled out

  • Our own enumerable implementation: direct enumeration is correct everywhere, including batched sources, empty sources, and a throwing producer.
  • The obvious structural suspects: four progressively more faithful offline models on the ordinary thread pool — an all-synchronous enumerator, a batched one, one whose terminating MoveNextAsync completes asynchronously, and a full in-process re-implementation of the Orleans pull loop driving two stacked legs — none reproduced it. The trigger appears to require the custom TaskScheduler an Orleans activation runs on.

Repro status (honest)

We could not reduce it to a standalone console repro — that is the main obstacle to a better report, and why this issue points at a live test instead. In our repository it reproduces deterministically:

To observe the raw counts: clone the branch, change the discriminator's assertion to print the tuple, and run dotnet test tests/Orleans.FSharp.Integration --filter "FullyQualifiedName~consuming an upstream stream inside a grain".

Happy to run any diagnostic build or instrumented package against this environment if that helps narrow it down.

主要言語
F#
スター
111
フォーク
13
平均マージ
7時間 59分
マージ済み PR(30日)
3

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

fsprojects/FSharp.Control.TaskSeq のほかの issue

fsprojects/FSharp.Control.TaskSeq の issue をすべて見る

似ている issue

Backend & API Design の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。