From 4e1f03006a07fb3e83e59357c5848b00a27a0823 Mon Sep 17 00:00:00 2001 From: Thibault Reigner Date: Thu, 23 Jun 2022 23:47:46 +0800 Subject: [PATCH 1/4] Fix bug in SkipLast(0) for custom sequence (try to dequeue from empty queue) --- .../System/Linq/Operators/SkipLast.cs | 5 +++-- .../System.Linq.Async/System/Linq/Operators/SkipLast.cs | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Ix.NET/Source/System.Linq.Async.Tests/System/Linq/Operators/SkipLast.cs b/Ix.NET/Source/System.Linq.Async.Tests/System/Linq/Operators/SkipLast.cs index 016e10f246..829d5ceb4a 100644 --- a/Ix.NET/Source/System.Linq.Async.Tests/System/Linq/Operators/SkipLast.cs +++ b/Ix.NET/Source/System.Linq.Async.Tests/System/Linq/Operators/SkipLast.cs @@ -1,6 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT License. -// See the LICENSE file in the project root for more information. +// See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; @@ -64,12 +64,13 @@ public async Task SkipLast_Zero() } [Fact] - public void SkipLast_Zero_NoAlias() + public async Task SkipLast_Zero_NoAlias() { var xs = Xs(); var ys = xs.SkipLast(0); Assert.NotSame(xs, ys); + await NoNextAsync(ys.GetAsyncEnumerator()); } private async IAsyncEnumerable Xs() diff --git a/Ix.NET/Source/System.Linq.Async/System/Linq/Operators/SkipLast.cs b/Ix.NET/Source/System.Linq.Async/System/Linq/Operators/SkipLast.cs index fab55d9687..0c7c02ea9e 100644 --- a/Ix.NET/Source/System.Linq.Async/System/Linq/Operators/SkipLast.cs +++ b/Ix.NET/Source/System.Linq.Async/System/Linq/Operators/SkipLast.cs @@ -1,6 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT License. -// See the LICENSE file in the project root for more information. +// See the LICENSE file in the project root for more information. using System.Collections.Generic; using System.Diagnostics; @@ -55,7 +55,8 @@ static async IAsyncEnumerable Core(IAsyncEnumerable source, in { do { - yield return queue.Dequeue(); + if (queue.Count > 0) + yield return queue.Dequeue(); queue.Enqueue(e.Current); } while (await e.MoveNextAsync()); From bdefb3cfde23b5cc2dc773936871fdc411263c9c Mon Sep 17 00:00:00 2001 From: Thibault Reigner Date: Thu, 23 Jun 2022 23:59:51 +0800 Subject: [PATCH 2/4] Better fix for IAsyncEnumerable.SkipLast, match algorithm of IEnumerable.SkipLast --- .../System/Linq/Operators/SkipLast.cs | 3 ++- .../System/Linq/Operators/SkipLast.cs | 25 +++++-------------- 2 files changed, 8 insertions(+), 20 deletions(-) diff --git a/Ix.NET/Source/System.Linq.Async.Tests/System/Linq/Operators/SkipLast.cs b/Ix.NET/Source/System.Linq.Async.Tests/System/Linq/Operators/SkipLast.cs index 829d5ceb4a..5e43d12671 100644 --- a/Ix.NET/Source/System.Linq.Async.Tests/System/Linq/Operators/SkipLast.cs +++ b/Ix.NET/Source/System.Linq.Async.Tests/System/Linq/Operators/SkipLast.cs @@ -70,7 +70,8 @@ public async Task SkipLast_Zero_NoAlias() var ys = xs.SkipLast(0); Assert.NotSame(xs, ys); - await NoNextAsync(ys.GetAsyncEnumerator()); + var e = ys.GetAsyncEnumerator(); + await HasNextAsync(e, 1); } private async IAsyncEnumerable Xs() diff --git a/Ix.NET/Source/System.Linq.Async/System/Linq/Operators/SkipLast.cs b/Ix.NET/Source/System.Linq.Async/System/Linq/Operators/SkipLast.cs index 0c7c02ea9e..b86897d15b 100644 --- a/Ix.NET/Source/System.Linq.Async/System/Linq/Operators/SkipLast.cs +++ b/Ix.NET/Source/System.Linq.Async/System/Linq/Operators/SkipLast.cs @@ -45,27 +45,14 @@ public static IAsyncEnumerable SkipLast(this IAsyncEnumerable< static async IAsyncEnumerable Core(IAsyncEnumerable source, int count, [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken = default) { - var queue = new Queue(); + var q = new Queue(); - await using var e = source.GetConfiguredAsyncEnumerator(cancellationToken, false); - - while (await e.MoveNextAsync()) + await foreach (var x in source.WithCancellation(cancellationToken)) { - if (queue.Count == count) - { - do - { - if (queue.Count > 0) - yield return queue.Dequeue(); - queue.Enqueue(e.Current); - } - while (await e.MoveNextAsync()); - break; - } - else - { - queue.Enqueue(e.Current); - } + q.Enqueue(x); + + if (q.Count > count) + yield return q.Dequeue(); } } } From ff6e92a87d3c514de2f322632de5936f5f0dd565 Mon Sep 17 00:00:00 2001 From: Thibault Reigner Date: Sat, 25 Jun 2022 22:06:45 +0800 Subject: [PATCH 3/4] Restore configureAwait false --- .../Source/System.Linq.Async/System/Linq/Operators/SkipLast.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Ix.NET/Source/System.Linq.Async/System/Linq/Operators/SkipLast.cs b/Ix.NET/Source/System.Linq.Async/System/Linq/Operators/SkipLast.cs index b86897d15b..72273d7772 100644 --- a/Ix.NET/Source/System.Linq.Async/System/Linq/Operators/SkipLast.cs +++ b/Ix.NET/Source/System.Linq.Async/System/Linq/Operators/SkipLast.cs @@ -47,7 +47,7 @@ static async IAsyncEnumerable Core(IAsyncEnumerable source, in { var q = new Queue(); - await foreach (var x in source.WithCancellation(cancellationToken)) + await foreach (var x in source.WithCancellation(cancellationToken).ConfigureAwait(false)) { q.Enqueue(x); From e818952bbf232547e35dd58dfa623d3f703cba43 Mon Sep 17 00:00:00 2001 From: thibault-reigner Date: Thu, 8 Sep 2022 15:25:45 +0800 Subject: [PATCH 4/4] (PR review) Revert change to SkipLast.Core + add Wrap method which actually hides an async sequence identity --- .../System/Linq/Operators/SkipLast.cs | 27 ++++++++++++++----- .../System/Linq/Operators/Wrap.cs | 14 ++++++++++ 2 files changed, 34 insertions(+), 7 deletions(-) create mode 100644 Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Wrap.cs diff --git a/Ix.NET/Source/System.Linq.Async/System/Linq/Operators/SkipLast.cs b/Ix.NET/Source/System.Linq.Async/System/Linq/Operators/SkipLast.cs index 72273d7772..b2065ae84a 100644 --- a/Ix.NET/Source/System.Linq.Async/System/Linq/Operators/SkipLast.cs +++ b/Ix.NET/Source/System.Linq.Async/System/Linq/Operators/SkipLast.cs @@ -38,21 +38,34 @@ public static IAsyncEnumerable SkipLast(this IAsyncEnumerable< return source; } - count = 0; + return source.Wrap(); } return Core(source, count); - static async IAsyncEnumerable Core(IAsyncEnumerable source, int count, [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken = default) + static async IAsyncEnumerable Core(IAsyncEnumerable source, int count, + [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken = default) { - var q = new Queue(); + var queue = new Queue(); - await foreach (var x in source.WithCancellation(cancellationToken).ConfigureAwait(false)) + await using var e = source.GetConfiguredAsyncEnumerator(cancellationToken, false); + + while (await e.MoveNextAsync()) { - q.Enqueue(x); + if (queue.Count == count) + { + do + { + yield return queue.Dequeue(); + queue.Enqueue(e.Current); + } while (await e.MoveNextAsync()); - if (q.Count > count) - yield return q.Dequeue(); + break; + } + else + { + queue.Enqueue(e.Current); + } } } } diff --git a/Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Wrap.cs b/Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Wrap.cs new file mode 100644 index 0000000000..e82002eade --- /dev/null +++ b/Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Wrap.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace System.Linq +{ + public static partial class AsyncEnumerable + { + internal static async IAsyncEnumerable Wrap(this IAsyncEnumerable source) + { + await foreach (var e in source.ConfigureAwait(false)) + yield return e; + } + } +}