From c9ce35e77e657012905bd0b4870f429c576ad59c Mon Sep 17 00:00:00 2001 From: Nicolas Dorier Date: Fri, 6 Nov 2026 11:02:13 +0900 Subject: [PATCH] Fix sends racing with disconnect and handshake cancellation --- NBitcoin/Protocol/Node.cs | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/NBitcoin/Protocol/Node.cs b/NBitcoin/Protocol/Node.cs index e8c3b739b..aabb7a033 100644 --- a/NBitcoin/Protocol/Node.cs +++ b/NBitcoin/Protocol/Node.cs @@ -185,7 +185,7 @@ public NodeConnection(Node node, Socket socket) internal bool IsVerbose => Logs.NodeServer.IsEnabled(LogLevel.Trace); - internal BlockingCollection Messages = new BlockingCollection(new ConcurrentQueue()); + internal readonly BlockingCollection Messages = new BlockingCollection(new ConcurrentQueue()); public void BeginListen() { new Thread(() => @@ -261,18 +261,19 @@ public void BeginListen() ar?.Dispose(); } + // Close the queue before draining it so a concurrent send cannot be left without a consumer. + Messages.CompleteAdding(); if (processing != null) - Messages.Add(processing); + processing.Completion.TrySetException(new InvalidOperationException("The peer has been disconnected")); - foreach (var pending in Messages) + while (Messages.TryTake(out var pending)) { if (IsVerbose) { Logs.NodeServer.LogTrace("The connection cancelled before the message was sent"); } - pending.Completion.SetException(new InvalidOperationException("The peer has been disconnected")); + pending.Completion.TrySetException(new InvalidOperationException("The peer has been disconnected")); } - Messages = new BlockingCollection(new ConcurrentQueue()); Logs.NodeServer.LogInformation("Stop sending"); Cleanup(unhandledException); }).Start(); @@ -947,12 +948,20 @@ public Task SendMessageAsync(Payload payload) Action final = () => { - _Connection.Messages.Add(new SentMessage() + try { - Payload = payload, - ActivityId = activity, - Completion = completion - }); + _Connection.Messages.Add(new SentMessage() + { + Payload = payload, + ActivityId = activity, + Completion = completion + }); + } + catch (InvalidOperationException) + { + // The sender may have stopped after the IsConnected check, including while a filter ran. + completion.TrySetException(new InvalidOperationException("The peer has been disconnected")); + } }; OnSendingMessage(payload, final); return completion.Task; @@ -1104,7 +1113,7 @@ public VersionPayload PeerVersion p.Message.Payload is VerAckPayload)) { - await SendMessageAsync(MyVersion).ConfigureAwait(false); + await SendMessageAsync(MyVersion).WithCancellation(cancellationToken).ConfigureAwait(false); #if !NO_CHANNELS var version = await listener.ReceivePayloadAsync(cancellationToken).ConfigureAwait(false); #else @@ -1135,10 +1144,10 @@ public VersionPayload PeerVersion if (ProtocolCapabilities.SupportAddrv2) { // Signal ADDRv2 support (BIP155). - await SendMessageAsync(new SendAddrV2Payload()).ConfigureAwait(false); + await SendMessageAsync(new SendAddrV2Payload()).WithCancellation(cancellationToken).ConfigureAwait(false); } - await SendMessageAsync(new VerAckPayload()).ConfigureAwait(false); + await SendMessageAsync(new VerAckPayload()).WithCancellation(cancellationToken).ConfigureAwait(false); #if !NO_CHANNELS await listener.ReceivePayloadAsync(cancellationToken).ConfigureAwait(false); @@ -1156,7 +1165,7 @@ public VersionPayload PeerVersion await SendMessageAsync(new AddrPayload(new NetworkAddress(MyVersion.AddressFrom) { Time = DateTimeOffset.UtcNow - })).ConfigureAwait(false); + })).WithCancellation(cancellationToken).ConfigureAwait(false); } } }