Skip to content

Commit b2694b3

Browse files
authored
Fix #2576: PhysicalConnection: Better shutdown handling (#2629)
This adds a bit of null ref handling (few ifs). Fixes #2576. Overall, this is biting people in the shutdown race, more likely under load, so let's eat the if checks here to prevent it. I decided to go with the specific approach here as to not affect inlining.
1 parent ad2e69f commit b2694b3

File tree

2 files changed

+40
-15
lines changed

2 files changed

+40
-15
lines changed

docs/ReleaseNotes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Current package versions:
1010

1111
- Fix [#2619](https://github.com/StackExchange/StackExchange.Redis/issues/2619): Type-forward `IsExternalInit` to support down-level TFMs ([#2621 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2621))
1212
- `InternalsVisibleTo` `PublicKey` enhancements([#2623 by WeihanLi](https://github.com/StackExchange/StackExchange.Redis/pull/2623))
13+
- Fix [#2576](https://github.com/StackExchange/StackExchange.Redis/issues/2576): Prevent `NullReferenceException` during shutdown of connections ([#2629 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2629))
1314

1415
## 2.7.10
1516

src/StackExchange.Redis/PhysicalConnection.cs

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -790,39 +790,44 @@ internal void Write(in RedisKey key)
790790
var val = key.KeyValue;
791791
if (val is string s)
792792
{
793-
WriteUnifiedPrefixedString(_ioPipe!.Output, key.KeyPrefix, s);
793+
WriteUnifiedPrefixedString(_ioPipe?.Output, key.KeyPrefix, s);
794794
}
795795
else
796796
{
797-
WriteUnifiedPrefixedBlob(_ioPipe!.Output, key.KeyPrefix, (byte[]?)val);
797+
WriteUnifiedPrefixedBlob(_ioPipe?.Output, key.KeyPrefix, (byte[]?)val);
798798
}
799799
}
800800

801801
internal void Write(in RedisChannel channel)
802-
=> WriteUnifiedPrefixedBlob(_ioPipe!.Output, ChannelPrefix, channel.Value);
802+
=> WriteUnifiedPrefixedBlob(_ioPipe?.Output, ChannelPrefix, channel.Value);
803803

804804
[MethodImpl(MethodImplOptions.AggressiveInlining)]
805805
internal void WriteBulkString(in RedisValue value)
806-
=> WriteBulkString(value, _ioPipe!.Output);
807-
internal static void WriteBulkString(in RedisValue value, PipeWriter output)
806+
=> WriteBulkString(value, _ioPipe?.Output);
807+
internal static void WriteBulkString(in RedisValue value, PipeWriter? maybeNullWriter)
808808
{
809+
if (maybeNullWriter is not PipeWriter writer)
810+
{
811+
return; // Prevent null refs during disposal
812+
}
813+
809814
switch (value.Type)
810815
{
811816
case RedisValue.StorageType.Null:
812-
WriteUnifiedBlob(output, (byte[]?)null);
817+
WriteUnifiedBlob(writer, (byte[]?)null);
813818
break;
814819
case RedisValue.StorageType.Int64:
815-
WriteUnifiedInt64(output, value.OverlappedValueInt64);
820+
WriteUnifiedInt64(writer, value.OverlappedValueInt64);
816821
break;
817822
case RedisValue.StorageType.UInt64:
818-
WriteUnifiedUInt64(output, value.OverlappedValueUInt64);
823+
WriteUnifiedUInt64(writer, value.OverlappedValueUInt64);
819824
break;
820825
case RedisValue.StorageType.Double: // use string
821826
case RedisValue.StorageType.String:
822-
WriteUnifiedPrefixedString(output, null, (string?)value);
827+
WriteUnifiedPrefixedString(writer, null, (string?)value);
823828
break;
824829
case RedisValue.StorageType.Raw:
825-
WriteUnifiedSpan(output, ((ReadOnlyMemory<byte>)value).Span);
830+
WriteUnifiedSpan(writer, ((ReadOnlyMemory<byte>)value).Span);
826831
break;
827832
default:
828833
throw new InvalidOperationException($"Unexpected {value.Type} value: '{value}'");
@@ -833,6 +838,11 @@ internal static void WriteBulkString(in RedisValue value, PipeWriter output)
833838

834839
internal void WriteHeader(RedisCommand command, int arguments, CommandBytes commandBytes = default)
835840
{
841+
if (_ioPipe?.Output is not PipeWriter writer)
842+
{
843+
return; // Prevent null refs during disposal
844+
}
845+
836846
var bridge = BridgeCouldBeNull ?? throw new ObjectDisposedException(ToString());
837847

838848
if (command == RedisCommand.UNKNOWN)
@@ -856,14 +866,14 @@ internal void WriteHeader(RedisCommand command, int arguments, CommandBytes comm
856866
// *{argCount}\r\n = 3 + MaxInt32TextLen
857867
// ${cmd-len}\r\n = 3 + MaxInt32TextLen
858868
// {cmd}\r\n = 2 + commandBytes.Length
859-
var span = _ioPipe!.Output.GetSpan(commandBytes.Length + 8 + Format.MaxInt32TextLen + Format.MaxInt32TextLen);
869+
var span = writer.GetSpan(commandBytes.Length + 8 + Format.MaxInt32TextLen + Format.MaxInt32TextLen);
860870
span[0] = (byte)'*';
861871

862872
int offset = WriteRaw(span, arguments + 1, offset: 1);
863873

864874
offset = AppendToSpanCommand(span, commandBytes, offset: offset);
865875

866-
_ioPipe.Output.Advance(offset);
876+
writer.Advance(offset);
867877
}
868878

869879
internal void RecordQuit() // don't blame redis if we fired the first shot
@@ -1116,7 +1126,11 @@ private static int AppendToSpan(Span<byte> span, ReadOnlySpan<byte> value, int o
11161126

11171127
internal void WriteSha1AsHex(byte[] value)
11181128
{
1119-
var writer = _ioPipe!.Output;
1129+
if (_ioPipe?.Output is not PipeWriter writer)
1130+
{
1131+
return; // Prevent null refs during disposal
1132+
}
1133+
11201134
if (value == null)
11211135
{
11221136
writer.Write(NullBulkString.Span);
@@ -1156,8 +1170,13 @@ internal static byte ToHexNibble(int value)
11561170
return value < 10 ? (byte)('0' + value) : (byte)('a' - 10 + value);
11571171
}
11581172

1159-
internal static void WriteUnifiedPrefixedString(PipeWriter writer, byte[]? prefix, string? value)
1173+
internal static void WriteUnifiedPrefixedString(PipeWriter? maybeNullWriter, byte[]? prefix, string? value)
11601174
{
1175+
if (maybeNullWriter is not PipeWriter writer)
1176+
{
1177+
return; // Prevent null refs during disposal
1178+
}
1179+
11611180
if (value == null)
11621181
{
11631182
// special case
@@ -1259,8 +1278,13 @@ internal static unsafe void WriteRaw(PipeWriter writer, string value, int expect
12591278
}
12601279
}
12611280

1262-
private static void WriteUnifiedPrefixedBlob(PipeWriter writer, byte[]? prefix, byte[]? value)
1281+
private static void WriteUnifiedPrefixedBlob(PipeWriter? maybeNullWriter, byte[]? prefix, byte[]? value)
12631282
{
1283+
if (maybeNullWriter is not PipeWriter writer)
1284+
{
1285+
return; // Prevent null refs during disposal
1286+
}
1287+
12641288
// ${total-len}\r\n
12651289
// {prefix}{value}\r\n
12661290
if (prefix == null || prefix.Length == 0 || value == null)

0 commit comments

Comments
 (0)