From f7a8d21fc2ccf8eb74b75216ebf6b2704f009dda Mon Sep 17 00:00:00 2001 From: VolodymyrBg Date: Sun, 8 Feb 2026 23:22:35 +0200 Subject: [PATCH 1/4] fix: add missing yield break after shutdown response --- src/Nethermind/Nethermind.JsonRpc/JsonRpcProcessor.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Nethermind/Nethermind.JsonRpc/JsonRpcProcessor.cs b/src/Nethermind/Nethermind.JsonRpc/JsonRpcProcessor.cs index bd042072fa1c..86da34a2662a 100644 --- a/src/Nethermind/Nethermind.JsonRpc/JsonRpcProcessor.cs +++ b/src/Nethermind/Nethermind.JsonRpc/JsonRpcProcessor.cs @@ -132,6 +132,7 @@ public async IAsyncEnumerable ProcessAsync(PipeReader reader, Jso { JsonRpcErrorResponse response = _jsonRpcService.GetErrorResponse(ErrorCodes.ResourceUnavailable, "Shutting down"); yield return JsonRpcResult.Single(RecordResponse(response, new RpcReport("Shutdown", 0, false))); + yield break; } if (IsRecordingRequest) From 89ff44f726598d1d562521bdbd9ddd63d1724c80 Mon Sep 17 00:00:00 2001 From: VolodymyrBg Date: Sun, 8 Feb 2026 23:23:29 +0200 Subject: [PATCH 2/4] Add test --- .../JsonRpcProcessorTests.cs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcProcessorTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcProcessorTests.cs index 99d9b33ba9c8..199a3809b7b6 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcProcessorTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcProcessorTests.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using FluentAssertions; using Nethermind.Core.Extensions; +using Nethermind.Config; using Nethermind.Logging; using Nethermind.JsonRpc.Modules; using NSubstitute; @@ -401,6 +402,33 @@ public async Task Can_handle_null_request() result.DisposeItems(); } + [Test] + public async Task Should_stop_processing_when_shutdown_requested() + { + IJsonRpcService service = Substitute.For(); + service.GetErrorResponse(Arg.Any(), Arg.Any()) + .Returns(new JsonRpcErrorResponse { Error = new Error { Code = ErrorCodes.ResourceUnavailable, Message = "Shutting down" } }); + + IProcessExitSource processExitSource = Substitute.For(); + processExitSource.Token.Returns(new CancellationToken(canceled: true)); + + JsonRpcProcessor processor = new( + service, + new JsonRpcConfig(), + Substitute.For(), + LimboLogs.Instance, + processExitSource); + + string request = "{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]}"; + List results = await processor.ProcessAsync(request, new JsonRpcContext(RpcEndpoint.Http)).ToListAsync(); + + results.Should().HaveCount(1); + results[0].Response.Should().BeOfType(); + ((JsonRpcErrorResponse)results[0].Response!).Error!.Code.Should().Be(ErrorCodes.ResourceUnavailable); + await service.DidNotReceive().SendRequestAsync(Arg.Any(), Arg.Any()); + results.DisposeItems(); + } + [Test] public void Cannot_accept_null_file_system() { From 0bfdd6dc284b700f95c7e64592d43c4db678e13c Mon Sep 17 00:00:00 2001 From: VolodymyrBg Date: Mon, 9 Feb 2026 14:43:16 +0200 Subject: [PATCH 3/4] ensure pipereader is completed on shutdown early-exit --- .../Nethermind.JsonRpc/JsonRpcProcessor.cs | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/Nethermind/Nethermind.JsonRpc/JsonRpcProcessor.cs b/src/Nethermind/Nethermind.JsonRpc/JsonRpcProcessor.cs index 86da34a2662a..030b54c67428 100644 --- a/src/Nethermind/Nethermind.JsonRpc/JsonRpcProcessor.cs +++ b/src/Nethermind/Nethermind.JsonRpc/JsonRpcProcessor.cs @@ -128,24 +128,25 @@ private ArrayPoolList DeserializeArray(JsonElement element) => public async IAsyncEnumerable ProcessAsync(PipeReader reader, JsonRpcContext context) { - if (ProcessExit.IsCancellationRequested) + try { - JsonRpcErrorResponse response = _jsonRpcService.GetErrorResponse(ErrorCodes.ResourceUnavailable, "Shutting down"); - yield return JsonRpcResult.Single(RecordResponse(response, new RpcReport("Shutdown", 0, false))); - yield break; - } + if (ProcessExit.IsCancellationRequested) + { + JsonRpcErrorResponse response = _jsonRpcService.GetErrorResponse(ErrorCodes.ResourceUnavailable, "Shutting down"); + yield return JsonRpcResult.Single(RecordResponse(response, new RpcReport("Shutdown", 0, false))); + yield break; + } - if (IsRecordingRequest) - { - reader = await RecordRequest(reader); - } + if (IsRecordingRequest) + { + reader = await RecordRequest(reader); + } + + using CancellationTokenSource timeoutSource = _jsonRpcConfig.BuildTimeoutCancellationToken(); + JsonReaderState readerState = CreateJsonReaderState(context); + bool freshState = true; + bool shouldExit = false; - using CancellationTokenSource timeoutSource = _jsonRpcConfig.BuildTimeoutCancellationToken(); - JsonReaderState readerState = CreateJsonReaderState(context); - bool freshState = true; - bool shouldExit = false; - try - { while (!shouldExit) { long startTime = Stopwatch.GetTimestamp(); From 75c09256af3aaedc51b6c47abb3ff1047cc82a55 Mon Sep 17 00:00:00 2001 From: VolodymyrBg Date: Mon, 9 Feb 2026 14:44:06 +0200 Subject: [PATCH 4/4] cover pipereader completion when shutdown is requested --- .../JsonRpcProcessorTests.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcProcessorTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcProcessorTests.cs index 199a3809b7b6..3ecf250af76c 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcProcessorTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcProcessorTests.cs @@ -429,6 +429,38 @@ public async Task Should_stop_processing_when_shutdown_requested() results.DisposeItems(); } + [Test] + public async Task Should_complete_pipe_reader_when_shutdown_requested() + { + IJsonRpcService service = Substitute.For(); + service.GetErrorResponse(Arg.Any(), Arg.Any()) + .Returns(new JsonRpcErrorResponse { Error = new Error { Code = ErrorCodes.ResourceUnavailable, Message = "Shutting down" } }); + + IProcessExitSource processExitSource = Substitute.For(); + processExitSource.Token.Returns(new CancellationToken(canceled: true)); + + JsonRpcProcessor processor = new( + service, + new JsonRpcConfig(), + Substitute.For(), + LimboLogs.Instance, + processExitSource); + + Pipe pipe = new(); + await pipe.Writer.WriteAsync(Encoding.UTF8.GetBytes("{\"id\":1,\"jsonrpc\":\"2.0\",\"method\":\"eth_blockNumber\",\"params\":[]}")); + + List results = await processor.ProcessAsync(pipe.Reader, new JsonRpcContext(RpcEndpoint.Http)).ToListAsync(); + + results.Should().HaveCount(1); + results[0].Response.Should().BeOfType(); + + // Verify PipeReader was completed by the processor (reading again should throw) + await FluentActions.Invoking(async () => await pipe.Reader.ReadAsync()) + .Should().ThrowAsync(); + + results.DisposeItems(); + } + [Test] public void Cannot_accept_null_file_system() {