Skip to content

Commit 275cc54

Browse files
isaldanamraleph
andauthored
Fix concurrent modification error in GrpcWebClientChannel.terminate
Fixes #331 Co-authored-by: Vyacheslav Egorov <[email protected]>
1 parent f1c4756 commit 275cc54

File tree

3 files changed

+80
-31
lines changed

3 files changed

+80
-31
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@
77
newer of protobuf compiler plugin.
88
* `Client.$createCall` is deprecated because it does not invoke client
99
interceptors.
10-
* Fix an issue [#380](https://github.com/grpc/grpc-dart/issues/380) causing
10+
* Fix issue [#380](https://github.com/grpc/grpc-dart/issues/380) causing
1111
incorrect duplicated headers in gRPC-Web requests.
1212
* Change minimum required Dart SDK to 2.8 to enable access to Unix domain sockets.
1313
* Add support for Unix domain sockets in `Socket.serve` and `ClientChannel`.
14+
* Fix issue [#331](https://github.com/grpc/grpc-dart/issues/331) causing
15+
an exception in `GrpcWebClientChannel.terminate()`.
1416

1517
## 2.7.0
1618

lib/src/client/transport/xhr_transport.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ class XhrClientConnection extends ClientConnection {
214214

215215
@override
216216
Future<void> terminate() async {
217-
for (XhrTransportStream request in _requests) {
217+
for (var request in List.of(_requests)) {
218218
request.terminate();
219219
}
220220
}

test/grpc_web_test.dart

Lines changed: 76 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -11,42 +11,88 @@ import 'package:grpc/grpc_web.dart';
1111
import 'src/generated/echo.pbgrpc.dart';
1212

1313
void main() {
14+
GrpcWebServer server;
15+
16+
setUpAll(() async {
17+
server = await GrpcWebServer.start();
18+
});
19+
20+
tearDownAll(() async {
21+
await server.shutdown();
22+
});
23+
1424
// Test verifies that gRPC-web echo example works by talking to a gRPC
1525
// server (written in Dart) via gRPC-web protocol through a third party
1626
// gRPC-web proxy.
1727
test('gRPC-web echo test', () async {
18-
final server = await GrpcWebServer.start();
19-
try {
20-
final channel = GrpcWebClientChannel.xhr(server.uri);
21-
final service = EchoServiceClient(channel);
28+
final channel = GrpcWebClientChannel.xhr(server.uri);
29+
final service = EchoServiceClient(channel);
2230

23-
const testMessage = 'hello from gRPC-web';
31+
const testMessage = 'hello from gRPC-web';
2432

25-
// First test a simple echo request.
26-
final response = await service.echo(EchoRequest()..message = testMessage);
33+
// First test a simple echo request.
34+
final response = await service.echo(EchoRequest()..message = testMessage);
35+
expect(response.message, equals(testMessage));
36+
37+
// Now test that streaming requests also works by asking echo server
38+
// to send us a number of messages every 100 ms. Check that we receive
39+
// them fast enough (if streaming is broken we will receive all of them
40+
// in one go).
41+
final sw = Stopwatch()..start();
42+
final timings = await service
43+
.serverStreamingEcho(ServerStreamingEchoRequest()
44+
..message = testMessage
45+
..messageCount = 20
46+
..messageInterval = 100)
47+
.map((response) {
2748
expect(response.message, equals(testMessage));
49+
final timing = sw.elapsedMilliseconds;
50+
sw.reset();
51+
return timing;
52+
}).toList();
53+
final maxDelay = timings.reduce(math.max);
54+
expect(maxDelay, lessThan(500));
55+
});
56+
57+
// Verify that terminate does not cause an exception when terminating
58+
// channel with multiple active requests.
59+
test("terminate works", () async {
60+
final channel = GrpcWebClientChannel.xhr(server.uri);
61+
final service = EchoServiceClient(channel);
62+
63+
const testMessage = 'hello from gRPC-web';
64+
65+
// First test a simple echo request.
66+
final response = await service.echo(EchoRequest()..message = testMessage);
67+
expect(response.message, equals(testMessage));
68+
69+
var terminated = false;
70+
71+
service
72+
.serverStreamingEcho(ServerStreamingEchoRequest()
73+
..message = testMessage
74+
..messageCount = 20
75+
..messageInterval = 100)
76+
.listen((response) {
77+
expect(response.message, equals(testMessage));
78+
}, onError: (e) {
79+
expect(terminated, isTrue);
80+
});
81+
82+
service
83+
.serverStreamingEcho(ServerStreamingEchoRequest()
84+
..message = testMessage
85+
..messageCount = 20
86+
..messageInterval = 100)
87+
.listen((response) {
88+
expect(response.message, equals(testMessage));
89+
}, onError: (e) {
90+
expect(terminated, isTrue);
91+
});
2892

29-
// Now test that streaming requests also works by asking echo server
30-
// to send us a number of messages every 100 ms. Check that we receive
31-
// them fast enough (if streaming is broken we will receive all of them
32-
// in one go).
33-
final sw = Stopwatch()..start();
34-
final timings = await service
35-
.serverStreamingEcho(ServerStreamingEchoRequest()
36-
..message = testMessage
37-
..messageCount = 20
38-
..messageInterval = 100)
39-
.map((response) {
40-
expect(response.message, equals(testMessage));
41-
final timing = sw.elapsedMilliseconds;
42-
sw.reset();
43-
return timing;
44-
}).toList();
45-
final maxDelay = timings.reduce(math.max);
46-
expect(maxDelay, lessThan(500));
47-
} finally {
48-
await server.shutdown();
49-
}
93+
await Future.delayed(Duration(milliseconds: 500));
94+
terminated = true;
95+
await channel.terminate();
5096
});
5197
}
5298

@@ -72,7 +118,8 @@ class GrpcWebServer {
72118
static Future<GrpcWebServer> start() async {
73119
// Spawn the server code on the server side, it will send us back port
74120
// number we should be talking to.
75-
final serverChannel = spawnHybridUri('grpc_web_server.dart');
121+
final serverChannel =
122+
spawnHybridUri('grpc_web_server.dart', stayAlive: true);
76123
final portCompleter = Completer<int>();
77124
final exitCompleter = Completer<void>();
78125
serverChannel.stream.listen((event) {

0 commit comments

Comments
 (0)