Hacktoberfest 2026: những issue maintainer đã đánh dấu cho tháng Mười, đang mở và phù hợp người mới. Xem issue Hacktoberfest

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

Đang mở
#452 11 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

Đánh giá

Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức phù hợp với người mới
45/100
Loại issue
Lỗi
Độ rõ ràng
Khá rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
fsharp
Lĩnh vực
backend

Hướng nghiên cứu

Bắt đầu với tests/Orleans.FSharp.Integration/FunctionalPhaseFIntegrationTests.fs quanh các dòng 598-627 và FunctionalPhaseFFixture.fs quanh các dòng 305-324, sau đó chạy lệnh dotnet test đã cung cấp với bộ lọc. So sánh phép liệt kê trực tiếp với các đường đi qua wrapper TaskSeq.map và taskSeq dưới Orleans scheduler. Hoàn thành khi các đường đi qua wrapper không còn trả về mục cuối hai lần, trong khi phép liệt kê trực tiếp vẫn đúng.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

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.

Ngôn ngữ chính
F#
Star
111
Fork
13
Merge trung bình
7 giờ 59 phút
Pull request đã merge (30 ngày)
3

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của fsprojects/FSharp.Control.TaskSeq

Tất cả issue của fsprojects/FSharp.Control.TaskSeq

Issue tương tự

Thêm issue về Backend & API Design

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.