Skip to content

Commit 4f341fc

Browse files
authored
Fix Union Dispose bug #2112 (#2131)
* Fix Union Dispose bug #2112 * Update Ix build to use .NET 8.0 SDK * Align ref project names with assembly names (This seems to have become necessary in .NET SDK 8.0.)
1 parent 2edf31a commit 4f341fc

File tree

10 files changed

+195
-12
lines changed

10 files changed

+195
-12
lines changed

Ix.NET/Source/Ix.NET.sln

+3-3
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Refs", "Refs", "{A3D72E6E-4
5353
refs\Directory.build.props = refs\Directory.build.props
5454
EndProjectSection
5555
EndProject
56-
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Interactive.Ref", "refs\System.Interactive.Ref\System.Interactive.Ref.csproj", "{2EC0C302-B029-4DDB-AC91-000BF11006AD}"
56+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Interactive", "refs\System.Interactive.Ref\System.Interactive.csproj", "{2EC0C302-B029-4DDB-AC91-000BF11006AD}"
5757
EndProject
58-
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Interactive.Providers.Ref", "refs\System.Interactive.Providers.Ref\System.Interactive.Providers.Ref.csproj", "{5DF341BE-B369-4250-AFD4-604DE8C95E45}"
58+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Interactive.Providers", "refs\System.Interactive.Providers.Ref\System.Interactive.Providers.csproj", "{5DF341BE-B369-4250-AFD4-604DE8C95E45}"
5959
EndProject
6060
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmarks.System.Interactive", "Benchmarks.System.Interactive\Benchmarks.System.Interactive.csproj", "{3285529A-8227-4D40-B524-1A1F919F0E7B}"
6161
EndProject
62-
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Linq.Async.Ref", "refs\System.Linq.Async.Ref\System.Linq.Async.Ref.csproj", "{1754B36C-D0DB-4E5D-8C30-1F116046DC0F}"
62+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Linq.Async", "refs\System.Linq.Async.Ref\System.Linq.Async.csproj", "{1754B36C-D0DB-4E5D-8C30-1F116046DC0F}"
6363
EndProject
6464
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Linq.Async.SourceGenerator", "System.Linq.Async.SourceGenerator\System.Linq.Async.SourceGenerator.csproj", "{5C26D649-5ED4-49EE-AFBD-8FA8F12C4AE4}"
6565
EndProject

Ix.NET/Source/System.Interactive.Providers/System.Interactive.Providers.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
<ItemGroup>
1515
<ProjectReference Include="..\System.Interactive\System.Interactive.csproj" />
16-
<ReferenceAssemblyProjectReference Include="..\refs\System.Interactive.Providers.Ref\System.Interactive.Providers.Ref.csproj" ReferenceOutputAssembly="false" />
16+
<ReferenceAssemblyProjectReference Include="..\refs\System.Interactive.Providers.Ref\System.Interactive.Providers.csproj" ReferenceOutputAssembly="false" />
1717
</ItemGroup>
1818

1919
</Project>

Ix.NET/Source/System.Interactive/System.Interactive.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
<ItemGroup>
1212
<EmbeddedResource Include="Properties\System.Interactive.rd.xml" />
13-
<ReferenceAssemblyProjectReference Include="..\refs\System.Interactive.Ref\System.Interactive.Ref.csproj" ReferenceOutputAssembly="false" />
13+
<ReferenceAssemblyProjectReference Include="..\refs\System.Interactive.Ref\System.Interactive.csproj" ReferenceOutputAssembly="false" />
1414
</ItemGroup>
1515

1616
</Project>

Ix.NET/Source/System.Linq.Async.Tests/System/Linq/Operators/Union.cs

+179
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System;
66
using System.Collections.Generic;
77
using System.Linq;
8+
using System.Threading;
89
using System.Threading.Tasks;
910
using Xunit;
1011

@@ -162,6 +163,128 @@ public async Task Union_ToList()
162163
Assert.Equal(new[] { 1, 2, 3, 4, 5 }, (await res.ToListAsync()).OrderBy(x => x));
163164
}
164165

166+
167+
[Fact]
168+
public async Task Union_DisposesNotEmpty()
169+
{
170+
var e1 = new DisposalDetectingEnumerable(10, 2);
171+
var e2 = new DisposalDetectingEnumerable(20, 2);
172+
var res = e1.Union(e2).OrderBy(x => x);
173+
174+
var e = res.GetAsyncEnumerator();
175+
await HasNextAsync(e, 10);
176+
await HasNextAsync(e, 11);
177+
await HasNextAsync(e, 20);
178+
await HasNextAsync(e, 21);
179+
await NoNextAsync(e);
180+
181+
Assert.Single(e1.Enumerators);
182+
Assert.Single(e2.Enumerators);
183+
Assert.Equal([true, true], [e1.Enumerators[0].Disposed, e2.Enumerators[0].Disposed]);
184+
}
185+
186+
[Fact]
187+
public async Task Union_DisposesFirstEmpty()
188+
{
189+
var e1 = new DisposalDetectingEnumerable(0, 0);
190+
var e2 = new DisposalDetectingEnumerable(1, 1);
191+
var res = e1.Union(e2);
192+
193+
var e = res.GetAsyncEnumerator();
194+
await HasNextAsync(e, 1);
195+
await NoNextAsync(e);
196+
197+
Assert.Single(e1.Enumerators);
198+
Assert.Single(e2.Enumerators);
199+
Assert.Equal([true, true], [e1.Enumerators[0].Disposed, e2.Enumerators[0].Disposed]);
200+
}
201+
202+
[Fact]
203+
public async Task Union_DisposesSecondOfTwoEmpty()
204+
{
205+
var e1 = new DisposalDetectingEnumerable(1, 1);
206+
var e2 = new DisposalDetectingEnumerable(0, 0);
207+
var res = e1.Union(e2);
208+
209+
var e = res.GetAsyncEnumerator();
210+
await HasNextAsync(e, 1);
211+
await NoNextAsync(e);
212+
213+
Assert.Single(e1.Enumerators);
214+
Assert.Single(e2.Enumerators);
215+
Assert.Equal([true, true], [e1.Enumerators[0].Disposed, e2.Enumerators[0].Disposed]);
216+
}
217+
218+
[Fact]
219+
public async Task Union_DisposesSecondOfThreeEmpty()
220+
{
221+
var e1 = new DisposalDetectingEnumerable(10, 1);
222+
var e2 = new DisposalDetectingEnumerable(0, 0);
223+
var e3 = new DisposalDetectingEnumerable(30, 1);
224+
var res = e1.Union(e2).Union(e3);
225+
226+
var e = res.GetAsyncEnumerator();
227+
await HasNextAsync(e, 10);
228+
await HasNextAsync(e, 30);
229+
await NoNextAsync(e);
230+
231+
Assert.Single(e1.Enumerators);
232+
Assert.Single(e2.Enumerators);
233+
Assert.Single(e3.Enumerators);
234+
Assert.Equal([true, true, true], [e1.Enumerators[0].Disposed, e2.Enumerators[0].Disposed, e3.Enumerators[0].Disposed]);
235+
}
236+
237+
[Fact]
238+
public async Task Union_DisposesThirdOfThreeEmpty()
239+
{
240+
var e1 = new DisposalDetectingEnumerable(10, 1);
241+
var e2 = new DisposalDetectingEnumerable(20, 1);
242+
var e3 = new DisposalDetectingEnumerable(0, 0);
243+
var res = e1.Union(e2).Union(e3);
244+
245+
var e = res.GetAsyncEnumerator();
246+
await HasNextAsync(e, 10);
247+
await HasNextAsync(e, 20);
248+
await NoNextAsync(e);
249+
250+
Assert.Single(e1.Enumerators);
251+
Assert.Single(e2.Enumerators);
252+
Assert.Single(e3.Enumerators);
253+
Assert.Equal([true, true, true], [e1.Enumerators[0].Disposed, e2.Enumerators[0].Disposed, e3.Enumerators[0].Disposed]);
254+
}
255+
256+
[Fact]
257+
public async Task Union_DisposesAllOfTwoEmpty()
258+
{
259+
var e1 = new DisposalDetectingEnumerable(0, 0);
260+
var e2 = new DisposalDetectingEnumerable(0, 0);
261+
var res = e1.Union(e2);
262+
263+
var e = res.GetAsyncEnumerator();
264+
await NoNextAsync(e);
265+
266+
Assert.Single(e1.Enumerators);
267+
Assert.Single(e2.Enumerators);
268+
Assert.Equal([true, true], [e1.Enumerators[0].Disposed, e2.Enumerators[0].Disposed]);
269+
}
270+
271+
[Fact]
272+
public async Task Union_DisposesAllOfThreeEmpty()
273+
{
274+
var e1 = new DisposalDetectingEnumerable(0, 0);
275+
var e2 = new DisposalDetectingEnumerable(0, 0);
276+
var e3 = new DisposalDetectingEnumerable(0, 0);
277+
var res = e1.Union(e2).Union(e3);
278+
279+
var e = res.GetAsyncEnumerator();
280+
await NoNextAsync(e);
281+
282+
Assert.Single(e1.Enumerators);
283+
Assert.Single(e2.Enumerators);
284+
Assert.Single(e3.Enumerators);
285+
Assert.Equal([true, true, true], [e1.Enumerators[0].Disposed, e2.Enumerators[0].Disposed, e3.Enumerators[0].Disposed]);
286+
}
287+
165288
private sealed class Eq : IEqualityComparer<int>
166289
{
167290
public bool Equals(int x, int y)
@@ -174,5 +297,61 @@ public int GetHashCode(int obj)
174297
return EqualityComparer<int>.Default.GetHashCode(Math.Abs(obj));
175298
}
176299
}
300+
301+
private class DisposalDetectingEnumerable : IAsyncEnumerable<int>
302+
{
303+
private readonly int _start;
304+
private readonly int _count;
305+
306+
public DisposalDetectingEnumerable(int start, int count)
307+
{
308+
_start = start;
309+
_count = count;
310+
}
311+
312+
public List<Enumerator> Enumerators { get; } = new List<Enumerator>();
313+
314+
public IAsyncEnumerator<int> GetAsyncEnumerator(CancellationToken cancellationToken = default)
315+
{
316+
Enumerator r = new(_start, _count);
317+
Enumerators.Add(r);
318+
return r;
319+
}
320+
321+
public class Enumerator : IAsyncEnumerator<int>
322+
{
323+
private readonly int _max;
324+
325+
public Enumerator(int start, int count)
326+
{
327+
Current = start - 1;
328+
_max = start + count;
329+
}
330+
331+
public int Current { get; private set; }
332+
333+
public bool Disposed { get; private set; }
334+
335+
public void Dispose()
336+
{
337+
Disposed = true;
338+
}
339+
340+
public ValueTask DisposeAsync()
341+
{
342+
Disposed = true;
343+
return new ValueTask();
344+
}
345+
public ValueTask<bool> MoveNextAsync()
346+
{
347+
if (++Current < _max)
348+
{
349+
return new ValueTask<bool>(true);
350+
}
351+
352+
return new ValueTask<bool>(false);
353+
}
354+
}
355+
}
177356
}
178357
}

Ix.NET/Source/System.Linq.Async/System.Linq.Async.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727

2828
<ItemGroup>
2929
<PackageReference Condition="'$(TargetFramework)' == 'net48' or '$(TargetFramework)' == 'netstandard2.0' " Include="Microsoft.Bcl.AsyncInterfaces" Version="6.0.0" />
30-
<ReferenceAssemblyProjectReference Include="..\refs\System.Linq.Async.Ref\System.Linq.Async.Ref.csproj" />
30+
<ReferenceAssemblyProjectReference Include="..\refs\System.Linq.Async.Ref\System.Linq.Async.csproj" />
3131
<ProjectReference Include="..\System.Linq.Async.SourceGenerator\System.Linq.Async.SourceGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" Private="false" />
3232
</ItemGroup>
3333

Ix.NET/Source/System.Linq.Async/System/Linq/Operators/Union.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -127,10 +127,10 @@ protected sealed override async ValueTask<bool> MoveNextCore()
127127
++_index;
128128

129129
var enumerator = enumerable.GetAsyncEnumerator(_cancellationToken);
130+
await SetEnumeratorAsync(enumerator).ConfigureAwait(false);
130131

131132
if (await enumerator.MoveNextAsync().ConfigureAwait(false))
132133
{
133-
await SetEnumeratorAsync(enumerator).ConfigureAwait(false);
134134
StoreFirst();
135135

136136
_state = AsyncIteratorState.Iterating;

Ix.NET/Source/refs/System.Interactive.Providers.Ref/System.Interactive.Providers.Ref.csproj renamed to Ix.NET/Source/refs/System.Interactive.Providers.Ref/System.Interactive.Providers.csproj

+1-2
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,10 @@
55
<AssemblyTitle>Interactive Extensions - Providers Library</AssemblyTitle>
66
<TargetFrameworks>net4.8;netstandard2.1;net6.0</TargetFrameworks>
77
<PackageTags>Ix;Interactive;Extensions;Enumerable</PackageTags>
8-
<AssemblyName>System.Interactive.Providers</AssemblyName>
98
</PropertyGroup>
109

1110
<ItemGroup>
12-
<ProjectReference Include="..\System.Interactive.Ref\System.Interactive.Ref.csproj" />
11+
<ProjectReference Include="..\System.Interactive.Ref\System.Interactive.csproj" />
1312
</ItemGroup>
1413

1514
<ItemGroup>

Ix.NET/Source/refs/System.Interactive.Ref/System.Interactive.Ref.csproj renamed to Ix.NET/Source/refs/System.Interactive.Ref/System.Interactive.csproj

-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
<PropertyGroup>
44
<Description>Interactive Extensions Main Library used to express queries over enumerable sequences.</Description>
55
<AssemblyTitle>Interactive Extensions - Main Library</AssemblyTitle>
6-
<AssemblyName>System.Interactive</AssemblyName>
76
<Authors>Microsoft</Authors>
87
<TargetFrameworks>net4.8;netstandard2.1;net6.0</TargetFrameworks>
98
<PackageTags>Ix;Interactive;Extensions;Enumerable</PackageTags>

azure-pipelines.ix.yml

+8-2
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,15 @@ stages:
3232
vmImage: ubuntu-latest
3333
steps:
3434
- task: UseDotNet@2
35-
displayName: Use .NET Core 6.x SDK
35+
displayName: Use .NET Core 8.x SDK
3636
inputs:
37-
version: 6.x
37+
version: 8.x
38+
39+
- task: UseDotNet@2
40+
displayName: .NET 6.0 runtime
41+
inputs:
42+
version: '6.x'
43+
packageType: runtime
3844

3945
- task: UseDotNet@2
4046
displayName: .NET Core 3.1 runtime

0 commit comments

Comments
 (0)