Skip to content

Commit 01fb9f5

Browse files
[Bug] Fix CloseAllAsync exception. Fixes #33 (#36)
Fix the issue with the CloseAllAsync method that was throwing a cancelation exception because we were canceling the token rather than closing the channels. Fixes #33
1 parent 1670a22 commit 01fb9f5

File tree

5 files changed

+55
-12
lines changed

5 files changed

+55
-12
lines changed

Marille.Tests/CancellationTests.cs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ public async Task CloseAllWorkersNoEvents ()
9090

9191
var topic2 = "topic2";
9292
var tcs2 = new TaskCompletionSource<bool> ();
93-
var worker2 = new BlockingWorker(tcs1);
93+
var worker2 = new BlockingWorker(tcs2);
9494
await _hub.CreateAsync<WorkQueuesEvent> (topic2, configuration);
9595
await _hub.RegisterAsync (topic2, worker2);
9696

@@ -154,4 +154,40 @@ public async Task MultithreadedClose ()
154154
}
155155
Assert.False (finalResult);
156156
}
157+
158+
[Fact]
159+
public async Task CloseAllChannelsAsync ()
160+
{
161+
// build several channels and then close them all, this should ensure that all the workers
162+
// have consume all the messages
163+
var eventCount = 100;
164+
var list = new List<Task> (200);
165+
166+
configuration.Mode = ChannelDeliveryMode.AtLeastOnce;
167+
var topic1 = "topic1";
168+
var tcs1 = new TaskCompletionSource<bool> ();
169+
var worker1 = new BlockingWorker(tcs1);
170+
await _hub.CreateAsync<WorkQueuesEvent> (topic1, configuration);
171+
await _hub.RegisterAsync (topic1, worker1);
172+
173+
var topic2 = "topic2";
174+
var tcs2 = new TaskCompletionSource<bool> ();
175+
var worker2 = new BlockingWorker(tcs2);
176+
await _hub.CreateAsync<WorkQueuesEvent> (topic2, configuration);
177+
await _hub.RegisterAsync (topic2, worker2);
178+
179+
for (var index = 0; index < eventCount; index++) {
180+
await _hub.Publish (topic1, new WorkQueuesEvent($"myID{index}"));
181+
await _hub.Publish (topic2, new WorkQueuesEvent($"myID{index}"));
182+
}
183+
184+
// we are blocking the consume of the channels
185+
Assert.True(tcs1.TrySetResult(true));
186+
Assert.True(tcs2.TrySetResult(true));
187+
188+
// close the hub, should throw no cancellation token exceptions and events should have been processed
189+
await _hub.CloseAllAsync ();
190+
Assert.Equal (100, worker1.ConsumedCount);
191+
Assert.Equal (100, worker2.ConsumedCount);
192+
}
157193
}

Marille/Hub.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -250,12 +250,14 @@ public async Task CloseAllAsync ()
250250
let tasks = topic.ConsumerTasks
251251
from task in tasks select task;
252252

253-
var cancellationTasks = from topic in topics.Values
254-
let cancellationTokens = topic.CancellationTokenSources
255-
from source in cancellationTokens select source.CancelAsync ();
253+
var topicInfos = from topic in topics.Values
254+
let channels = topic.Channels
255+
from ch in channels select ch;
256+
257+
foreach (var topicInfo in topicInfos) {
258+
topicInfo.CloseChannel ();
259+
}
256260

257-
// we could do a nested Task.WhenAll but we want to ensure that the cancellation tasks are done before
258-
await Task.WhenAll (cancellationTasks);
259261
await Task.WhenAll (consumingTasks);
260262
} finally {
261263
semaphoreSlim.Release ();

Marille/Marille.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
<PropertyGroup>
1919
<Title>Marille</Title>
2020
<PackageId>Marille</PackageId>
21-
<Version>0.4.2</Version>
21+
<Version>0.4.3</Version>
2222
<Authors>Manuel de la Peña Saenz</Authors>
2323
<Owners>Manuel de la Peña Saenz</Owners>
2424
<Copyright>Manuel de la Peña Saenz</Copyright>

Marille/Topic.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@ internal class Topic (string name) {
1212
where info.ConsumerTask is not null
1313
select info.ConsumerTask;
1414

15-
public IEnumerable<CancellationTokenSource> CancellationTokenSources => from info in channels.Values
16-
where info.CancellationTokenSource is not null
17-
select info.CancellationTokenSource;
15+
public IEnumerable<TopicInfo> Channels => channels.Values;
1816

1917
public bool TryGetChannel<T> ([NotNullWhen (true)] out TopicInfo<T>? channel) where T : struct
2018
{
@@ -46,7 +44,7 @@ public void CloseChannel<T> () where T : struct
4644
if (!TryGetChannel<T> (out var chInfo))
4745
return;
4846

49-
chInfo.Channel.Writer.Complete ();
47+
chInfo.CloseChannel ();
5048
channels.Remove (typeof (T));
5149
}
5250

Marille/TopicInfo.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
namespace Marille;
44

5-
internal record TopicInfo (TopicConfiguration Configuration){
5+
internal abstract record TopicInfo (TopicConfiguration Configuration){
66
public CancellationTokenSource? CancellationTokenSource { get; set; }
77
public Task? ConsumerTask { get; set; }
8+
9+
public abstract void CloseChannel ();
810
}
911

1012
internal record TopicInfo<T> (TopicConfiguration Configuration, Channel<Message<T>> Channel) : TopicInfo (Configuration)
@@ -16,4 +18,9 @@ public TopicInfo (TopicConfiguration configuration, Channel<Message<T>> channel,
1618
{
1719
Workers.AddRange (workers);
1820
}
21+
22+
public override void CloseChannel ()
23+
{
24+
Channel.Writer.Complete ();
25+
}
1926
}

0 commit comments

Comments
 (0)