Skip to content
This repository was archived by the owner on Jun 21, 2023. It is now read-only.

Commit 422bd95

Browse files
committed
Add a test for concurrent requests to GetPage().
And fix a race condition.
1 parent 43ba6e3 commit 422bd95

File tree

2 files changed

+40
-5
lines changed

2 files changed

+40
-5
lines changed

src/GitHub.App/Collections/SequentialListSource.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,9 @@ async Task<Page<TModel>> EnsureLoaded(int pageNumber)
155155

156156
if (pageLoaded.IsCompleted)
157157
{
158-
return pages[pageNumber];
158+
// A previous waiting task may have already returned the page. If so, return null.
159+
pages.TryGetValue(pageNumber, out var result);
160+
return result;
159161
}
160162
}
161163

test/GitHub.App.UnitTests/Collections/SequentialListSourceTests.cs

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4+
using System.Reactive;
5+
using System.Reactive.Linq;
6+
using System.Reactive.Subjects;
7+
using System.Reactive.Threading.Tasks;
48
using System.Threading.Tasks;
59
using GitHub.Collections;
610
using GitHub.Models;
@@ -57,14 +61,38 @@ public void IsLoading_Should_Be_Set_To_False_When_LoadPage_Throws()
5761
Assert.That(target.IsLoading, Is.False);
5862
}
5963

64+
[Test]
65+
public async Task Should_Not_Load_Duplicate_Pages()
66+
{
67+
var trigger = new Subject<Unit>();
68+
var target = new TestSource(loadTrigger: trigger);
69+
70+
var task1 = target.GetPage(1);
71+
var task2 = target.GetPage(1);
72+
73+
Assert.That(target.PagesLoaded, Is.Empty);
74+
75+
trigger.OnNext(Unit.Default);
76+
trigger.OnNext(Unit.Default);
77+
78+
await task1;
79+
await task2;
80+
81+
Assert.That(target.PagesLoaded, Is.EqualTo(new[] { 0, 1 }));
82+
}
83+
6084
class TestSource : SequentialListSource<string, string>
6185
{
6286
const int PageCount = 10;
6387
readonly int? throwAtPage;
88+
readonly ISubject<Unit> loadTrigger;
6489

65-
public TestSource(int? throwAtPage = null)
90+
public TestSource(
91+
int? throwAtPage = null,
92+
ISubject<Unit> loadTrigger = null)
6693
{
6794
this.throwAtPage = throwAtPage;
95+
this.loadTrigger = loadTrigger;
6896
}
6997

7098
public override int PageSize => 10;
@@ -75,24 +103,29 @@ protected override string CreateViewModel(string model)
75103
return model + " loaded";
76104
}
77105

78-
protected override Task<Page<string>> LoadPage(string after)
106+
protected override async Task<Page<string>> LoadPage(string after)
79107
{
80108
var page = after != null ? int.Parse(after) : 0;
81109

110+
if (loadTrigger != null)
111+
{
112+
await loadTrigger.Take(1).ToTask().ConfigureAwait(false);
113+
}
114+
82115
if (page == throwAtPage)
83116
{
84117
throw new GitHubLogicException("Thrown.");
85118
}
86119

87120
PagesLoaded.Add(page);
88121

89-
return Task.FromResult(new Page<string>
122+
return new Page<string>
90123
{
91124
EndCursor = (page + 1).ToString(),
92125
HasNextPage = page < PageCount,
93126
Items = Enumerable.Range(page * PageSize, PageSize).Select(x => "Item " + x).ToList(),
94127
TotalCount = PageSize * PageCount,
95-
});
128+
};
96129
}
97130
}
98131
}

0 commit comments

Comments
 (0)