Skip to content

Commit 4a13caf

Browse files
authored
Logging: fix race in disposal of a passed-in TextWriter (#2581)
Occasionally we'd see `chunkLength` errors from `StringWriter` `.ToString()` calls after connecting. I think we've isolated this (via test stress runs) down to a write happening post-lock on the `TextWriterLogger` disposal. This lock in dispose ensures we're not trying to write to a writer we should have fully released at the end of a `.Connect()`/`.ConnectAsync()` call.
1 parent 5504ed9 commit 4a13caf

File tree

2 files changed

+20
-9
lines changed

2 files changed

+20
-9
lines changed

src/StackExchange.Redis/ConnectionMultiplexer.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -638,7 +638,7 @@ private static async Task<ConnectionMultiplexer> ConnectImplAsync(ConfigurationO
638638
{
639639
if (connectHandler != null && muxer != null) muxer.ConnectionFailed -= connectHandler;
640640
if (killMe != null) try { killMe.Dispose(); } catch { }
641-
if (log is TextWriterLogger twLogger) twLogger.Dispose();
641+
if (log is TextWriterLogger twLogger) twLogger.Release();
642642
}
643643
}
644644

@@ -740,7 +740,7 @@ private static ConnectionMultiplexer ConnectImpl(ConfigurationOptions configurat
740740
{
741741
if (connectHandler != null && muxer != null) muxer.ConnectionFailed -= connectHandler;
742742
if (killMe != null) try { killMe.Dispose(); } catch { }
743-
if (log is TextWriterLogger twLogger) twLogger.Dispose();
743+
if (log is TextWriterLogger twLogger) twLogger.Release();
744744
}
745745
}
746746

src/StackExchange.Redis/TextWriterLogger.cs

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44

55
namespace StackExchange.Redis;
66

7-
internal sealed class TextWriterLogger : ILogger, IDisposable
7+
internal sealed class TextWriterLogger : ILogger
88
{
99
private TextWriter? _writer;
10-
private ILogger? _wrapped;
10+
private readonly ILogger? _wrapped;
1111

1212
internal static Action<string> NullWriter = _ => { };
1313

@@ -26,16 +26,27 @@ public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Except
2626
{
2727
lock (writer)
2828
{
29-
writer.Write($"{DateTime.UtcNow:HH:mm:ss.ffff}: ");
30-
writer.WriteLine(formatter(state, exception));
29+
// We check here again because it's possible we've released below, and never want to write past releasing.
30+
if (_writer is TextWriter innerWriter)
31+
{
32+
innerWriter.Write($"{DateTime.UtcNow:HH:mm:ss.ffff}: ");
33+
innerWriter.WriteLine(formatter(state, exception));
34+
}
3135
}
3236
}
3337
}
3438

35-
public void Dispose()
39+
public void Release()
3640
{
37-
_writer = null;
38-
_wrapped = null;
41+
// We lock here because we may have piled up on a lock above and still be writing.
42+
// We never want a write to go past the Release(), as many TextWriter implementations are not thread safe.
43+
if (_writer is TextWriter writer)
44+
{
45+
lock (writer)
46+
{
47+
_writer = null;
48+
}
49+
}
3950
}
4051
}
4152

0 commit comments

Comments
 (0)