Skip to content

Commit 188741e

Browse files
committed
Response to review of #21
Response to review of #21
1 parent 2ee0f26 commit 188741e

File tree

4 files changed

+48
-23
lines changed

4 files changed

+48
-23
lines changed

src/Serilog.Sinks.Async/LoggerConfigurationAsyncExtensions.cs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using System;
2+
using System.ComponentModel;
23
using Serilog.Configuration;
3-
44
using Serilog.Sinks.Async;
55

66
namespace Serilog
@@ -10,6 +10,24 @@ namespace Serilog
1010
/// </summary>
1111
public static class LoggerConfigurationAsyncExtensions
1212
{
13+
/// <summary>
14+
/// Configure a sink to be invoked asynchronously, on a background worker thread.
15+
/// </summary>
16+
/// <param name="loggerSinkConfiguration">The <see cref="LoggerSinkConfiguration"/> being configured.</param>
17+
/// <param name="configure">An action that configures the wrapped sink.</param>
18+
/// <param name="bufferSize">The size of the concurrent queue used to feed the background worker thread. If
19+
/// the thread is unable to process events quickly enough and the queue is filled, subsequent events will be
20+
/// dropped until room is made in the queue.</param>
21+
/// <returns>A <see cref="LoggerConfiguration"/> allowing configuration to continue.</returns>
22+
[EditorBrowsable(EditorBrowsableState.Never)]
23+
public static LoggerConfiguration Async(
24+
this LoggerSinkConfiguration loggerSinkConfiguration,
25+
Action<LoggerSinkConfiguration> configure,
26+
int bufferSize)
27+
{
28+
return loggerSinkConfiguration.Async(configure, bufferSize, false);
29+
}
30+
1331
/// <summary>
1432
/// Configure a sink to be invoked asynchronously, on a background worker thread.
1533
/// </summary>
@@ -32,5 +50,6 @@ public static LoggerConfiguration Async(
3250
wrappedSink => new BackgroundWorkerSink(wrappedSink, bufferSize, blockWhenFull),
3351
configure);
3452
}
53+
3554
}
3655
}

src/Serilog.Sinks.Async/Sinks/Async/BackgroundWorkerSink.cs

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ sealed class BackgroundWorkerSink : ILogEventSink, IDisposable
1313
readonly ILogEventSink _pipeline;
1414
readonly int _bufferCapacity;
1515
readonly bool _blockWhenFull;
16-
volatile bool _disposed;
1716
readonly BlockingCollection<LogEvent> _queue;
1817
readonly Task _worker;
1918

@@ -30,27 +29,36 @@ public BackgroundWorkerSink(ILogEventSink pipeline, int bufferCapacity, bool blo
3029

3130
public void Emit(LogEvent logEvent)
3231
{
33-
// The disposed check is racy, but only ensures we don't prevent flush from
34-
// completing by pushing more events.
35-
if (_disposed)
32+
if (this._queue.IsAddingCompleted)
3633
return;
3734

38-
if (!this._blockWhenFull)
35+
try
3936
{
40-
if (!_queue.TryAdd(logEvent))
41-
SelfLog.WriteLine("{0} unable to enqueue, capacity {1}", typeof(BackgroundWorkerSink), _bufferCapacity);
37+
if (_blockWhenFull)
38+
{
39+
_queue.Add(logEvent);
40+
}
41+
else
42+
{
43+
if (!_queue.TryAdd(logEvent))
44+
SelfLog.WriteLine("{0} unable to enqueue, capacity {1}", typeof(BackgroundWorkerSink), _bufferCapacity);
45+
}
4246
}
43-
else
47+
catch (InvalidOperationException)
4448
{
45-
this._queue.Add(logEvent);
49+
// Thrown in the event of a race condition when we try to add another event after
50+
// CompleteAdding has been called
4651
}
4752
}
4853

4954
public void Dispose()
5055
{
51-
_disposed = true;
56+
// Prevent any more events from being added
5257
_queue.CompleteAdding();
53-
_worker.Wait();
58+
59+
// Allow queued events to be flushed
60+
_worker.Wait();
61+
5462
(_pipeline as IDisposable)?.Dispose();
5563
}
5664

test/Serilog.Sinks.Async.Tests/BackgroundWorkerSinkSpec.cs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using System.Linq;
55
using System.Threading.Tasks;
66
using Serilog.Core;
7-
using Serilog.Debugging;
87
using Serilog.Events;
98
using Serilog.Parsing;
109
using Serilog.Sinks.Async.Tests.Support;
@@ -41,7 +40,6 @@ public async Task WhenEmitSingle_ThenRelaysToInnerSink()
4140
{
4241
var logEvent = CreateEvent();
4342
_sink.Emit(logEvent);
44-
_sink.Dispose();
4543

4644
await Task.Delay(TimeSpan.FromSeconds(3));
4745

@@ -90,7 +88,7 @@ public async Task WhenQueueFull_ThenDropsEvents()
9088

9189
// Cause a delay when emmitting to the inner sink, allowing us to fill the queue to capacity
9290
// after the first event is popped
93-
_innerSink.DelayEmit = true;
91+
_innerSink.DelayEmit = TimeSpan.FromMilliseconds(300);
9492

9593
var events = new List<LogEvent>
9694
{
@@ -106,12 +104,12 @@ public async Task WhenQueueFull_ThenDropsEvents()
106104
_sink.Emit(e);
107105
sw.Stop();
108106

109-
Assert.True(sw.ElapsedMilliseconds < 2000, "Should not block the caller when the queue is full");
107+
Assert.True(sw.ElapsedMilliseconds < 200, "Should not block the caller when the queue is full");
110108
});
111109

112110
// If we *weren't* dropped events, the delay in the inner sink would mean the 5 events would take
113111
// at least 15 seconds to process
114-
await Task.Delay(TimeSpan.FromSeconds(18));
112+
await Task.Delay(TimeSpan.FromSeconds(2));
115113

116114
// Events should be dropped
117115
Assert.Equal(2, _innerSink.Events.Count);
@@ -124,7 +122,7 @@ public async Task WhenQueueFull_ThenBlocks()
124122

125123
// Cause a delay when emmitting to the inner sink, allowing us to fill the queue to capacity
126124
// after the first event is popped
127-
_innerSink.DelayEmit = true;
125+
_innerSink.DelayEmit = TimeSpan.FromMilliseconds(300);
128126

129127
var events = new List<LogEvent>
130128
{
@@ -144,11 +142,11 @@ public async Task WhenQueueFull_ThenBlocks()
144142
// subsequent calls, the queue should be full, so we should be blocked
145143
if (i > 0)
146144
{
147-
Assert.True(sw.ElapsedMilliseconds > 2000, "Should block the caller when the queue is full");
145+
Assert.True(sw.ElapsedMilliseconds > 200, "Should block the caller when the queue is full");
148146
}
149147
});
150148

151-
await Task.Delay(TimeSpan.FromSeconds(12));
149+
await Task.Delay(TimeSpan.FromSeconds(2));
152150

153151
// No events should be dropped
154152
Assert.Equal(3, _innerSink.Events.Count);

test/Serilog.Sinks.Async.Tests/Support/MemorySink.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ public class MemorySink : ILogEventSink
1010
{
1111
public ConcurrentBag<LogEvent> Events { get; } = new ConcurrentBag<LogEvent>();
1212
public bool ThrowAfterCollecting { get; set; }
13-
public bool DelayEmit { get; set; }
13+
public TimeSpan? DelayEmit { get; set; }
1414

1515
public void Emit(LogEvent logEvent)
1616
{
17-
if (DelayEmit)
18-
Task.Delay(TimeSpan.FromSeconds(3)).Wait();
17+
if (DelayEmit.HasValue)
18+
Task.Delay(DelayEmit.Value).Wait();
1919

2020
Events.Add(logEvent);
2121

0 commit comments

Comments
 (0)