From 7d0c311ea9c73b18b2cd7918174d61526077b933 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 28 Nov 2025 13:46:24 +0000 Subject: [PATCH 1/9] Initial plan From e1a0e8c2beb01d18f4f06bcac300e844236754d3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 28 Nov 2025 14:00:40 +0000 Subject: [PATCH 2/9] Add test to verify graceful shutdown of in-flight requests Co-authored-by: nielsenko <22237677+nielsenko@users.noreply.github.com> --- test/relic_server_graceful_shutdown_test.dart | 276 ++++++++++++++++++ 1 file changed, 276 insertions(+) create mode 100644 test/relic_server_graceful_shutdown_test.dart diff --git a/test/relic_server_graceful_shutdown_test.dart b/test/relic_server_graceful_shutdown_test.dart new file mode 100644 index 00000000..773cd8ae --- /dev/null +++ b/test/relic_server_graceful_shutdown_test.dart @@ -0,0 +1,276 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:http/http.dart' as http; +import 'package:relic/io_adapter.dart'; +import 'package:relic/relic.dart'; +import 'package:test/test.dart'; + +/// Creates a handler that delays for the specified duration before responding. +Handler _createDelayedHandler(final Duration delay) { + return (final req) async { + await Future.delayed(delay); + return Response.ok(body: Body.fromString('Completed')); + }; +} + +void main() { + group('Given a RelicServer with in-flight requests', () { + late RelicServer server; + bool serverClosed = false; + + setUp(() async { + serverClosed = false; + server = RelicServer( + () => IOAdapter.bind(InternetAddress.loopbackIPv4, port: 0), + ); + }); + + tearDown(() async { + // Server may already be closed by the test + if (!serverClosed) { + try { + await server.close(); + } catch (_) {} + } + }); + + test( + 'when server.close() is called during an in-flight request, ' + 'then the request completes successfully before server shuts down', + () async { + const requestDelay = Duration(milliseconds: 500); + await server.mountAndStart(_createDelayedHandler(requestDelay)); + + // Start a request that will take 500ms to complete + final responseFuture = http.get(Uri.http('localhost:${server.port}')); + + // Give the request time to start processing + await Future.delayed(const Duration(milliseconds: 50)); + + // Verify request is in-flight + final infoBeforeClose = await server.connectionsInfo(); + expect( + infoBeforeClose.active + infoBeforeClose.idle, + greaterThan(0), + reason: 'Expected at least one active or idle connection', + ); + + // Close the server while request is in-flight + final closeFuture = server.close(); + serverClosed = true; + + // Wait for both the response and server close to complete + final results = await Future.wait([responseFuture, closeFuture]); + + // Verify the in-flight request completed successfully + final response = results[0] as http.Response; + expect(response.statusCode, HttpStatus.ok); + expect(response.body, 'Completed'); + }, + ); + + test( + 'when server.close() is called with multiple concurrent in-flight requests, ' + 'then all requests complete successfully', + () async { + const requestDelay = Duration(milliseconds: 300); + const numberOfRequests = 5; + await server.mountAndStart(_createDelayedHandler(requestDelay)); + + // Start multiple concurrent requests + final responseFutures = List.generate( + numberOfRequests, + (_) => http.get(Uri.http('localhost:${server.port}')), + ); + + // Give requests time to start processing + await Future.delayed(const Duration(milliseconds: 50)); + + // Close the server while requests are in-flight + final closeFuture = server.close(); + serverClosed = true; + + // Wait for all responses + final responses = await Future.wait(responseFutures); + + // Wait for server to close + await closeFuture; + + // Verify all requests completed successfully + for (var i = 0; i < responses.length; i++) { + expect( + responses[i].statusCode, + HttpStatus.ok, + reason: 'Request $i should have completed with 200 OK', + ); + expect( + responses[i].body, + 'Completed', + reason: 'Request $i should have the expected body', + ); + } + }, + ); + + test( + 'when server.close() is called, ' + 'then new requests are not accepted after close begins', + () async { + const requestDelay = Duration(milliseconds: 200); + await server.mountAndStart(_createDelayedHandler(requestDelay)); + + // Start an in-flight request + final inFlightRequest = http.get(Uri.http('localhost:${server.port}')); + + // Give the request time to start processing + await Future.delayed(const Duration(milliseconds: 50)); + + // Close the server + final closeFuture = server.close(); + + // Try to start a new request after close is initiated + // (This should fail or be rejected) + late http.Response? newRequestResponse; + Object? newRequestError; + try { + newRequestResponse = await http.get( + Uri.http('localhost:${server.port}'), + ); + } catch (e) { + newRequestError = e; + } + + // Wait for close and in-flight request to complete + await Future.wait([inFlightRequest, closeFuture]); + + // New request should have either failed with an error + // or received a connection refused/reset error + // The exact behavior depends on timing and the underlying HTTP server + expect( + newRequestError != null || newRequestResponse?.statusCode != 200, + isTrue, + reason: 'New requests should be rejected after server begins closing', + ); + }, + skip: + 'This test depends on the timing of server shutdown ' + 'and may not reliably reproduce the rejection behavior', + ); + }); + + group('Given a RelicServer with multi-isolate configuration', () { + late RelicServer server; + bool serverClosed = false; + + setUp(() async { + serverClosed = false; + server = RelicServer( + () => IOAdapter.bind(InternetAddress.loopbackIPv4, port: 0), + noOfIsolates: 2, + ); + }); + + tearDown(() async { + if (!serverClosed) { + try { + await server.close(); + } catch (_) {} + } + }); + + test( + 'when server.close() is called during in-flight requests across isolates, ' + 'then all requests complete successfully', + () async { + const requestDelay = Duration(milliseconds: 300); + const numberOfRequests = 4; + await server.mountAndStart(_createDelayedHandler(requestDelay)); + + // Start multiple concurrent requests that will be distributed + // across isolates + final responseFutures = List.generate( + numberOfRequests, + (_) => http.get(Uri.http('localhost:${server.port}')), + ); + + // Give requests time to start processing + await Future.delayed(const Duration(milliseconds: 50)); + + // Close the server while requests are in-flight + final closeFuture = server.close(); + serverClosed = true; + + // Wait for all responses + final responses = await Future.wait(responseFutures); + + // Wait for server to close + await closeFuture; + + // Verify all requests completed successfully + for (var i = 0; i < responses.length; i++) { + expect( + responses[i].statusCode, + HttpStatus.ok, + reason: 'Request $i should have completed with 200 OK', + ); + } + }, + ); + }); + + group('Given a RelicServer shutdown timing', () { + late RelicServer server; + bool serverClosed = false; + + setUp(() async { + serverClosed = false; + server = RelicServer( + () => IOAdapter.bind(InternetAddress.loopbackIPv4, port: 0), + ); + }); + + tearDown(() async { + if (!serverClosed) { + try { + await server.close(); + } catch (_) {} + } + }); + + test('when server.close() is called with a long-running request, ' + 'then server waits for the request to complete', () async { + const requestDelay = Duration(milliseconds: 800); + await server.mountAndStart(_createDelayedHandler(requestDelay)); + + // Start a long-running request + final responseFuture = http.get(Uri.http('localhost:${server.port}')); + + // Give the request time to start processing + await Future.delayed(const Duration(milliseconds: 50)); + + // Record when we start closing + final closeStartTime = DateTime.now(); + + // Close the server and wait for response + serverClosed = true; + final results = await Future.wait([responseFuture, server.close()]); + + final closeEndTime = DateTime.now(); + final closeDuration = closeEndTime.difference(closeStartTime); + + // The response should have completed successfully + final response = results[0] as http.Response; + expect(response.statusCode, HttpStatus.ok); + + // The close should have taken approximately as long as the request + // needed to complete (accounting for some timing variance) + // The request should have taken most of its delay (minus the 50ms head start) + expect( + closeDuration.inMilliseconds, + greaterThan(requestDelay.inMilliseconds - 100), + reason: 'Server close should wait for in-flight request to complete', + ); + }); + }); +} From ccdf515e359d25c17c1309d7b12e7716356f2fe7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 28 Nov 2025 14:02:13 +0000 Subject: [PATCH 3/9] Address code review feedback: Add named constant for timing variance Co-authored-by: nielsenko <22237677+nielsenko@users.noreply.github.com> --- test/relic_server_graceful_shutdown_test.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/relic_server_graceful_shutdown_test.dart b/test/relic_server_graceful_shutdown_test.dart index 773cd8ae..b8ac9da2 100644 --- a/test/relic_server_graceful_shutdown_test.dart +++ b/test/relic_server_graceful_shutdown_test.dart @@ -223,6 +223,10 @@ void main() { late RelicServer server; bool serverClosed = false; + /// Acceptable timing variance in milliseconds for shutdown timing tests. + /// This accounts for system scheduling delays and HTTP overhead. + const timingVarianceMs = 100; + setUp(() async { serverClosed = false; server = RelicServer( @@ -268,7 +272,7 @@ void main() { // The request should have taken most of its delay (minus the 50ms head start) expect( closeDuration.inMilliseconds, - greaterThan(requestDelay.inMilliseconds - 100), + greaterThan(requestDelay.inMilliseconds - timingVarianceMs), reason: 'Server close should wait for in-flight request to complete', ); }); From 2c79a8cd8b24a56defd876d53ad52474827c116c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Dec 2025 10:34:47 +0000 Subject: [PATCH 4/9] Refactor graceful shutdown tests to use Completers for deterministic behavior Co-authored-by: nielsenko <22237677+nielsenko@users.noreply.github.com> --- test/relic_server_graceful_shutdown_test.dart | 141 +++++++++++++----- 1 file changed, 103 insertions(+), 38 deletions(-) diff --git a/test/relic_server_graceful_shutdown_test.dart b/test/relic_server_graceful_shutdown_test.dart index b8ac9da2..6d18c8fd 100644 --- a/test/relic_server_graceful_shutdown_test.dart +++ b/test/relic_server_graceful_shutdown_test.dart @@ -6,7 +6,24 @@ import 'package:relic/io_adapter.dart'; import 'package:relic/relic.dart'; import 'package:test/test.dart'; +/// Creates a handler that signals when processing starts and waits for +/// a completer before responding. +/// +/// [onRequestStarted] is called when the request starts processing. +/// [canComplete] is a completer that the handler waits for before responding. +Handler _createSignalingHandler({ + required final void Function() onRequestStarted, + required final Completer canComplete, +}) { + return (final req) async { + onRequestStarted(); + await canComplete.future; + return Response.ok(body: Body.fromString('Completed')); + }; +} + /// Creates a handler that delays for the specified duration before responding. +/// Used for multi-isolate tests where Completers cannot cross isolate boundaries. Handler _createDelayedHandler(final Duration delay) { return (final req) async { await Future.delayed(delay); @@ -39,14 +56,21 @@ void main() { 'when server.close() is called during an in-flight request, ' 'then the request completes successfully before server shuts down', () async { - const requestDelay = Duration(milliseconds: 500); - await server.mountAndStart(_createDelayedHandler(requestDelay)); + final requestStarted = Completer(); + final canComplete = Completer(); + + await server.mountAndStart( + _createSignalingHandler( + onRequestStarted: requestStarted.complete, + canComplete: canComplete, + ), + ); - // Start a request that will take 500ms to complete + // Start a request final responseFuture = http.get(Uri.http('localhost:${server.port}')); - // Give the request time to start processing - await Future.delayed(const Duration(milliseconds: 50)); + // Wait for the request to start processing + await requestStarted.future; // Verify request is in-flight final infoBeforeClose = await server.connectionsInfo(); @@ -60,6 +84,9 @@ void main() { final closeFuture = server.close(); serverClosed = true; + // Allow the request to complete + canComplete.complete(); + // Wait for both the response and server close to complete final results = await Future.wait([responseFuture, closeFuture]); @@ -74,9 +101,22 @@ void main() { 'when server.close() is called with multiple concurrent in-flight requests, ' 'then all requests complete successfully', () async { - const requestDelay = Duration(milliseconds: 300); const numberOfRequests = 5; - await server.mountAndStart(_createDelayedHandler(requestDelay)); + var requestsStarted = 0; + final allRequestsStarted = Completer(); + final canComplete = Completer(); + + await server.mountAndStart( + _createSignalingHandler( + onRequestStarted: () { + requestsStarted++; + if (requestsStarted == numberOfRequests) { + allRequestsStarted.complete(); + } + }, + canComplete: canComplete, + ), + ); // Start multiple concurrent requests final responseFutures = List.generate( @@ -84,13 +124,16 @@ void main() { (_) => http.get(Uri.http('localhost:${server.port}')), ); - // Give requests time to start processing - await Future.delayed(const Duration(milliseconds: 50)); + // Wait for all requests to start processing + await allRequestsStarted.future; // Close the server while requests are in-flight final closeFuture = server.close(); serverClosed = true; + // Allow the requests to complete + canComplete.complete(); + // Wait for all responses final responses = await Future.wait(responseFutures); @@ -117,14 +160,25 @@ void main() { 'when server.close() is called, ' 'then new requests are not accepted after close begins', () async { - const requestDelay = Duration(milliseconds: 200); - await server.mountAndStart(_createDelayedHandler(requestDelay)); + final requestStarted = Completer(); + final canComplete = Completer(); + + await server.mountAndStart( + _createSignalingHandler( + onRequestStarted: () { + if (!requestStarted.isCompleted) { + requestStarted.complete(); + } + }, + canComplete: canComplete, + ), + ); // Start an in-flight request final inFlightRequest = http.get(Uri.http('localhost:${server.port}')); - // Give the request time to start processing - await Future.delayed(const Duration(milliseconds: 50)); + // Wait for the request to start processing + await requestStarted.future; // Close the server final closeFuture = server.close(); @@ -141,6 +195,9 @@ void main() { newRequestError = e; } + // Allow the in-flight request to complete + canComplete.complete(); + // Wait for close and in-flight request to complete await Future.wait([inFlightRequest, closeFuture]); @@ -183,8 +240,11 @@ void main() { 'when server.close() is called during in-flight requests across isolates, ' 'then all requests complete successfully', () async { + // Use delay-based handler because Completers cannot cross isolate + // boundaries const requestDelay = Duration(milliseconds: 300); const numberOfRequests = 4; + await server.mountAndStart(_createDelayedHandler(requestDelay)); // Start multiple concurrent requests that will be distributed @@ -223,10 +283,6 @@ void main() { late RelicServer server; bool serverClosed = false; - /// Acceptable timing variance in milliseconds for shutdown timing tests. - /// This accounts for system scheduling delays and HTTP overhead. - const timingVarianceMs = 100; - setUp(() async { serverClosed = false; server = RelicServer( @@ -244,37 +300,46 @@ void main() { test('when server.close() is called with a long-running request, ' 'then server waits for the request to complete', () async { - const requestDelay = Duration(milliseconds: 800); - await server.mountAndStart(_createDelayedHandler(requestDelay)); + final requestStarted = Completer(); + final canComplete = Completer(); + var responseReceived = false; + + await server.mountAndStart( + _createSignalingHandler( + onRequestStarted: requestStarted.complete, + canComplete: canComplete, + ), + ); // Start a long-running request - final responseFuture = http.get(Uri.http('localhost:${server.port}')); - - // Give the request time to start processing - await Future.delayed(const Duration(milliseconds: 50)); + final responseFuture = http + .get(Uri.http('localhost:${server.port}')) + .then((final response) { + responseReceived = true; + return response; + }); - // Record when we start closing - final closeStartTime = DateTime.now(); + // Wait for the request to start processing + await requestStarted.future; - // Close the server and wait for response + // Close the server while request is in-flight serverClosed = true; - final results = await Future.wait([responseFuture, server.close()]); + final closeFuture = server.close(); + + // Now allow the request to complete + canComplete.complete(); - final closeEndTime = DateTime.now(); - final closeDuration = closeEndTime.difference(closeStartTime); + // Wait for both the response and server close to complete + final results = await Future.wait([responseFuture, closeFuture]); + + // Verify the response was received (meaning the request was allowed to + // complete despite the server closing) + expect(responseReceived, isTrue); // The response should have completed successfully final response = results[0] as http.Response; expect(response.statusCode, HttpStatus.ok); - - // The close should have taken approximately as long as the request - // needed to complete (accounting for some timing variance) - // The request should have taken most of its delay (minus the 50ms head start) - expect( - closeDuration.inMilliseconds, - greaterThan(requestDelay.inMilliseconds - timingVarianceMs), - reason: 'Server close should wait for in-flight request to complete', - ); + expect(response.body, 'Completed'); }); }); } From 232fcf58a74ba8292ee1919c073398114fddd242 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Dec 2025 10:48:19 +0000 Subject: [PATCH 5/9] Enable new request rejection test - no longer needs skip with Completer approach Co-authored-by: nielsenko <22237677+nielsenko@users.noreply.github.com> --- test/relic_server_graceful_shutdown_test.dart | 94 +++++++++---------- 1 file changed, 44 insertions(+), 50 deletions(-) diff --git a/test/relic_server_graceful_shutdown_test.dart b/test/relic_server_graceful_shutdown_test.dart index 6d18c8fd..274b6d07 100644 --- a/test/relic_server_graceful_shutdown_test.dart +++ b/test/relic_server_graceful_shutdown_test.dart @@ -156,64 +156,58 @@ void main() { }, ); - test( - 'when server.close() is called, ' - 'then new requests are not accepted after close begins', - () async { - final requestStarted = Completer(); - final canComplete = Completer(); + test('when server.close() is called, ' + 'then new requests are not accepted after close begins', () async { + final requestStarted = Completer(); + final canComplete = Completer(); - await server.mountAndStart( - _createSignalingHandler( - onRequestStarted: () { - if (!requestStarted.isCompleted) { - requestStarted.complete(); - } - }, - canComplete: canComplete, - ), - ); + await server.mountAndStart( + _createSignalingHandler( + onRequestStarted: () { + if (!requestStarted.isCompleted) { + requestStarted.complete(); + } + }, + canComplete: canComplete, + ), + ); - // Start an in-flight request - final inFlightRequest = http.get(Uri.http('localhost:${server.port}')); + // Start an in-flight request + final inFlightRequest = http.get(Uri.http('localhost:${server.port}')); - // Wait for the request to start processing - await requestStarted.future; + // Wait for the request to start processing + await requestStarted.future; - // Close the server - final closeFuture = server.close(); + // Close the server + final closeFuture = server.close(); - // Try to start a new request after close is initiated - // (This should fail or be rejected) - late http.Response? newRequestResponse; - Object? newRequestError; - try { - newRequestResponse = await http.get( - Uri.http('localhost:${server.port}'), - ); - } catch (e) { - newRequestError = e; - } + // Try to start a new request after close is initiated + // (This should fail or be rejected) + late http.Response? newRequestResponse; + Object? newRequestError; + try { + newRequestResponse = await http.get( + Uri.http('localhost:${server.port}'), + ); + } catch (e) { + newRequestError = e; + } - // Allow the in-flight request to complete - canComplete.complete(); + // Allow the in-flight request to complete + canComplete.complete(); - // Wait for close and in-flight request to complete - await Future.wait([inFlightRequest, closeFuture]); + // Wait for close and in-flight request to complete + await Future.wait([inFlightRequest, closeFuture]); - // New request should have either failed with an error - // or received a connection refused/reset error - // The exact behavior depends on timing and the underlying HTTP server - expect( - newRequestError != null || newRequestResponse?.statusCode != 200, - isTrue, - reason: 'New requests should be rejected after server begins closing', - ); - }, - skip: - 'This test depends on the timing of server shutdown ' - 'and may not reliably reproduce the rejection behavior', - ); + // New request should have either failed with an error + // or received a connection refused/reset error + // The exact behavior depends on timing and the underlying HTTP server + expect( + newRequestError != null || newRequestResponse?.statusCode != 200, + isTrue, + reason: 'New requests should be rejected after server begins closing', + ); + }); }); group('Given a RelicServer with multi-isolate configuration', () { From 39270bc8ed6df87f94a0dacb532062c652a5ba0d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Dec 2025 11:00:20 +0000 Subject: [PATCH 6/9] Remove duplicate test and fix un-awaited futures across async gaps Co-authored-by: nielsenko <22237677+nielsenko@users.noreply.github.com> --- test/relic_server_graceful_shutdown_test.dart | 80 ++----------------- 1 file changed, 6 insertions(+), 74 deletions(-) diff --git a/test/relic_server_graceful_shutdown_test.dart b/test/relic_server_graceful_shutdown_test.dart index 274b6d07..1982f22e 100644 --- a/test/relic_server_graceful_shutdown_test.dart +++ b/test/relic_server_graceful_shutdown_test.dart @@ -134,11 +134,9 @@ void main() { // Allow the requests to complete canComplete.complete(); - // Wait for all responses - final responses = await Future.wait(responseFutures); - - // Wait for server to close - await closeFuture; + // Wait for all responses and server close at the same time + final responsesFuture = Future.wait(responseFutures); + final (responses, _) = await (responsesFuture, closeFuture).wait; // Verify all requests completed successfully for (var i = 0; i < responses.length; i++) { @@ -255,11 +253,9 @@ void main() { final closeFuture = server.close(); serverClosed = true; - // Wait for all responses - final responses = await Future.wait(responseFutures); - - // Wait for server to close - await closeFuture; + // Wait for all responses and server close at the same time + final responsesFuture = Future.wait(responseFutures); + final (responses, _) = await (responsesFuture, closeFuture).wait; // Verify all requests completed successfully for (var i = 0; i < responses.length; i++) { @@ -272,68 +268,4 @@ void main() { }, ); }); - - group('Given a RelicServer shutdown timing', () { - late RelicServer server; - bool serverClosed = false; - - setUp(() async { - serverClosed = false; - server = RelicServer( - () => IOAdapter.bind(InternetAddress.loopbackIPv4, port: 0), - ); - }); - - tearDown(() async { - if (!serverClosed) { - try { - await server.close(); - } catch (_) {} - } - }); - - test('when server.close() is called with a long-running request, ' - 'then server waits for the request to complete', () async { - final requestStarted = Completer(); - final canComplete = Completer(); - var responseReceived = false; - - await server.mountAndStart( - _createSignalingHandler( - onRequestStarted: requestStarted.complete, - canComplete: canComplete, - ), - ); - - // Start a long-running request - final responseFuture = http - .get(Uri.http('localhost:${server.port}')) - .then((final response) { - responseReceived = true; - return response; - }); - - // Wait for the request to start processing - await requestStarted.future; - - // Close the server while request is in-flight - serverClosed = true; - final closeFuture = server.close(); - - // Now allow the request to complete - canComplete.complete(); - - // Wait for both the response and server close to complete - final results = await Future.wait([responseFuture, closeFuture]); - - // Verify the response was received (meaning the request was allowed to - // complete despite the server closing) - expect(responseReceived, isTrue); - - // The response should have completed successfully - final response = results[0] as http.Response; - expect(response.statusCode, HttpStatus.ok); - expect(response.body, 'Completed'); - }); - }); } From 9f5ca1582ea7bdb4f132a5ddfba5fea82bcddbbb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Dec 2025 11:08:48 +0000 Subject: [PATCH 7/9] Use (x, y).wait pattern instead of Future.wait for pairs Co-authored-by: nielsenko <22237677+nielsenko@users.noreply.github.com> --- test/relic_server_graceful_shutdown_test.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/relic_server_graceful_shutdown_test.dart b/test/relic_server_graceful_shutdown_test.dart index 1982f22e..6a5af3c8 100644 --- a/test/relic_server_graceful_shutdown_test.dart +++ b/test/relic_server_graceful_shutdown_test.dart @@ -88,10 +88,9 @@ void main() { canComplete.complete(); // Wait for both the response and server close to complete - final results = await Future.wait([responseFuture, closeFuture]); + final (response, _) = await (responseFuture, closeFuture).wait; // Verify the in-flight request completed successfully - final response = results[0] as http.Response; expect(response.statusCode, HttpStatus.ok); expect(response.body, 'Completed'); }, @@ -195,7 +194,7 @@ void main() { canComplete.complete(); // Wait for close and in-flight request to complete - await Future.wait([inFlightRequest, closeFuture]); + await (inFlightRequest, closeFuture).wait; // New request should have either failed with an error // or received a connection refused/reset error From 1b62d9cff5b2bd28b969189da5571d16bfafcf47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Mon, 1 Dec 2025 12:49:05 +0100 Subject: [PATCH 8/9] Prefer [].wait to Future.wait --- example/advanced/multi_isolate.dart | 13 ++++++------- .../isolated_object_evaluate_test.dart | 4 ++-- test/relic_server_graceful_shutdown_test.dart | 6 ++---- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/example/advanced/multi_isolate.dart b/example/advanced/multi_isolate.dart index abce96bc..81c83883 100644 --- a/example/advanced/multi_isolate.dart +++ b/example/advanced/multi_isolate.dart @@ -12,13 +12,12 @@ void main() async { // Spawn all isolates concurrently and wait for them to start. log('Starting $isolateCount isolates'); - final isolates = await Future.wait( - List.generate( - isolateCount, - (final index) => - Isolate.spawn((final _) => _serve(), null, debugName: '$index'), - ), - ); + final isolates = + await List.generate( + isolateCount, + (final index) => + Isolate.spawn((final _) => _serve(), null, debugName: '$index'), + ).wait; // Wait for SIGINT (Ctrl-C) signal before shutting down. await ProcessSignal.sigint.watch().first; diff --git a/test/isolated_object/isolated_object_evaluate_test.dart b/test/isolated_object/isolated_object_evaluate_test.dart index 3b06bc47..36bce4b3 100644 --- a/test/isolated_object/isolated_object_evaluate_test.dart +++ b/test/isolated_object/isolated_object_evaluate_test.dart @@ -54,7 +54,7 @@ void main() { (final i) => isolated.evaluate((final counter) => counter.increment()), ); - await Future.wait(futures); + await futures.wait; final result = await isolated.evaluate((final counter) => counter.value); expect(result, 10); @@ -190,7 +190,7 @@ void main() { ); } - final results = await Future.wait(futures); + final results = await futures.wait; // All operations should complete expect(results.length, 100); diff --git a/test/relic_server_graceful_shutdown_test.dart b/test/relic_server_graceful_shutdown_test.dart index 6a5af3c8..1790191e 100644 --- a/test/relic_server_graceful_shutdown_test.dart +++ b/test/relic_server_graceful_shutdown_test.dart @@ -134,8 +134,7 @@ void main() { canComplete.complete(); // Wait for all responses and server close at the same time - final responsesFuture = Future.wait(responseFutures); - final (responses, _) = await (responsesFuture, closeFuture).wait; + final (responses, _) = await (responseFutures.wait, closeFuture).wait; // Verify all requests completed successfully for (var i = 0; i < responses.length; i++) { @@ -253,8 +252,7 @@ void main() { serverClosed = true; // Wait for all responses and server close at the same time - final responsesFuture = Future.wait(responseFutures); - final (responses, _) = await (responsesFuture, closeFuture).wait; + final (responses, _) = await (responseFutures.wait, closeFuture).wait; // Verify all requests completed successfully for (var i = 0; i < responses.length; i++) { From 768de12f71542e425b9a083a38c8adbf833e89dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Tue, 2 Dec 2025 18:13:17 +0100 Subject: [PATCH 9/9] docs: Multi isolate example now listen for both SIGINT and SIGTERM (pr feedback) --- example/advanced/multi_isolate.dart | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/example/advanced/multi_isolate.dart b/example/advanced/multi_isolate.dart index 81c83883..663ef817 100644 --- a/example/advanced/multi_isolate.dart +++ b/example/advanced/multi_isolate.dart @@ -19,8 +19,13 @@ void main() async { Isolate.spawn((final _) => _serve(), null, debugName: '$index'), ).wait; - // Wait for SIGINT (Ctrl-C) signal before shutting down. - await ProcessSignal.sigint.watch().first; + // Wait for SIGINT (Ctrl-C) or SIGTERM signal before shutting down. + await Future.any( + [ + ProcessSignal.sigterm, + ProcessSignal.sigint, + ].map((final s) => s.watch().first), + ); // Gracefully terminate all spawned isolates. for (final i in isolates) {