Wrapping combinators (TaskSeq.map, taskSeq { for .. }) yield the final item twice over an external IAsyncEnumerable on a non-default TaskScheduler (Orleans grain)
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ả
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.TaskSeq0.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'
IAsyncEnumerableGrainExtensionmachinery — batchedMoveNextpulls 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:
directenumeration 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
MoveNextAsynccompletes 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 customTaskScheduleran 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:
- The discriminator test (asserts
direct = 3; deliberately does not pin the divergent value so an upstream fix cannot break our suite): https://github.com/Neftedollar/orleans-fsharp/blob/e31b2e3c5190acb1cbcaadd4078dccd8d67b6b95/tests/Orleans.FSharp.Integration/FunctionalPhaseFIntegrationTests.fs#L598-L627 - The in-grain probe that performs the three consumptions: https://github.com/Neftedollar/orleans-fsharp/blob/e31b2e3c5190acb1cbcaadd4078dccd8d67b6b95/tests/Orleans.FSharp.Integration/FunctionalPhaseFFixture.fs#L305-L324
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
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- 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.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của fsprojects/FSharp.Control.TaskSeq
-
agentic-workflows automation repo-assist
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 70/100
-
agentic-workflows automation repo-assist
Độ khó 1/5 1-3 giờ Mức phù hợp với người mới 35/100
fsprojects/FSharp.Control.TaskSeq#476 · 1 bình luận ·
-
bug
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 45/100
fsprojects/FSharp.Control.TaskSeq#473 · 1 bình luận · 1 reaction ·
-
automation enhancement repo-assist
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 10/100
-
enhancement needs triage
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 45/100
Tất cả issue của fsprojects/FSharp.Control.TaskSeq
Issue tương tự
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 80/100
microsoft/magentic-ui#588 ·
-
bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 70/100
alexgorbatchev/simple-ptt#3 · 1 bình luận ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
CorrelAid/formtransform#44 ·