From 4f6fe9b1114aa5bd9d97e5d3b5ae2cb354804a1f Mon Sep 17 00:00:00 2001 From: c-lucera-pvotal <91328643+c-lucera-pvotal@users.noreply.github.com> Date: Wed, 28 Aug 2024 08:18:15 +0200 Subject: [PATCH] fix: fix headers not completing when call is terminated (#728) Fixes #727 --- CHANGELOG.md | 4 ++ lib/src/client/call.dart | 6 +++ pubspec.yaml | 2 +- test/client_tests/call_test.dart | 69 ++++++++++++++++++++++++++++++++ test/keepalive_test.dart | 2 +- 5 files changed, 81 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 398f2c81..6e1faa37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 4.0.1 + +* Fix header and trailing not completing if the call is terminated. Fixes [#727](https://github.com/grpc/grpc-dart/issues/727) + ## 4.0.0 * Set compressed flag correctly for grpc-encoding = identity. Fixes [#669](https://github.com/grpc/grpc-dart/issues/669) (https://github.com/grpc/grpc-dart/pull/693) diff --git a/lib/src/client/call.dart b/lib/src/client/call.dart index 8d5918f8..7010313a 100644 --- a/lib/src/client/call.dart +++ b/lib/src/client/call.dart @@ -483,6 +483,12 @@ class ClientCall implements Response { if (_responseSubscription != null) { futures.add(_responseSubscription!.cancel()); } + if (!_headers.isCompleted) { + _headers.complete({}); + } + if (!_trailers.isCompleted) { + _trailers.complete({}); + } await Future.wait(futures); } diff --git a/pubspec.yaml b/pubspec.yaml index de16e0fa..3ada1c62 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: grpc description: Dart implementation of gRPC, a high performance, open-source universal RPC framework. -version: 4.0.0 +version: 4.0.1 repository: https://github.com/grpc/grpc-dart diff --git a/test/client_tests/call_test.dart b/test/client_tests/call_test.dart index c519f6e8..9ce832b5 100644 --- a/test/client_tests/call_test.dart +++ b/test/client_tests/call_test.dart @@ -13,10 +13,26 @@ // See the License for the specific language governing permissions and // limitations under the License. +import 'package:grpc/grpc.dart'; import 'package:grpc/src/client/call.dart'; import 'package:test/test.dart'; +import '../src/client_utils.dart'; + void main() { + const dummyValue = 0; + const cancelDurationMillis = 300; + + late ClientHarness harness; + + setUp(() { + harness = ClientHarness()..setUp(); + }); + + tearDown(() { + harness.tearDown(); + }); + test('WebCallOptions mergeWith CallOptions returns WebCallOptions', () { final options = WebCallOptions(bypassCorsPreflight: true, withCredentials: true); @@ -28,4 +44,57 @@ void main() { expect(mergedOptions.bypassCorsPreflight, true); expect(mergedOptions.withCredentials, true); }); + + test( + 'Cancelling a call correctly complete headers future', + () async { + final clientCall = harness.client.unary(dummyValue); + + Future.delayed( + Duration(milliseconds: cancelDurationMillis), + ).then((_) => clientCall.cancel()); + + expect(await clientCall.headers, isEmpty); + + await expectLater( + clientCall, + throwsA( + isA().having( + (e) => e.codeName, + 'Test codename', + contains('CANCELLED'), + ), + ), + ); + }, + ); + + test( + 'Cancelling a call correctly complete trailers futures', + () async { + final clientCall = harness.client.unary(dummyValue); + + Future.delayed( + Duration(milliseconds: cancelDurationMillis), + ).then((_) { + clientCall.cancel(); + }); + + expect( + await clientCall.trailers, + isEmpty, + ); + + await expectLater( + clientCall, + throwsA( + isA().having( + (e) => e.codeName, + 'Test codename', + contains('CANCELLED'), + ), + ), + ); + }, + ); } diff --git a/test/keepalive_test.dart b/test/keepalive_test.dart index 7ffa1156..4e710811 100644 --- a/test/keepalive_test.dart +++ b/test/keepalive_test.dart @@ -49,7 +49,7 @@ void main() { services: [FakeEchoService()], keepAliveOptions: serverOptions, ); - await server.serve(address: 'localhost', port: 8081); + await server.serve(address: 'localhost', port: 0); fakeChannel = FakeClientChannel( 'localhost', port: server.port!,