Deflake keepalive tests (#645)

* Deflake keepalive tests

* Add timer

* Increase wait timer

* Only send ping on open connection

* Switch server keepalive to fakeasync

* Tiny refactor

* Add changelog entry
This commit is contained in:
Moritz 2023-06-22 10:30:53 -04:00 committed by GitHub
parent 517cde91a6
commit b7df4c8290
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 76 additions and 58 deletions

View File

@ -2,6 +2,7 @@
* Add const constructor to `GrpcError` fixing #606. * Add const constructor to `GrpcError` fixing #606.
* Make `GrpcError` non-final to allow implementations. * Make `GrpcError` non-final to allow implementations.
* Only send keepalive pings on open connections.
## 3.2.2 ## 3.2.2

View File

@ -106,7 +106,11 @@ class Http2ClientConnection implements connection.ClientConnection {
if (options.keepAlive.shouldSendPings) { if (options.keepAlive.shouldSendPings) {
keepAliveManager = ClientKeepAlive( keepAliveManager = ClientKeepAlive(
options: options.keepAlive, options: options.keepAlive,
ping: () => transport.ping(), ping: () {
if (transport.isOpen) {
transport.ping();
}
},
onPingTimeout: () => shutdown(), onPingTimeout: () => shutdown(),
); );
transport.onFrameReceived transport.onFrameReceived

View File

@ -56,7 +56,7 @@ void main() {
}); });
tearDown(() async { tearDown(() async {
await fakeChannel.terminate(); await fakeChannel.shutdown();
await server.shutdown(); await server.shutdown();
}); });
@ -73,8 +73,12 @@ void main() {
test('Server doesnt terminate connection after pings, as data is sent', test('Server doesnt terminate connection after pings, as data is sent',
() async { () async {
final timer = Timer.periodic( final timer = Timer.periodic(
Duration(milliseconds: 30), (timer) => fakeClient.echo(EchoRequest())); Duration(milliseconds: 10), (timer) => fakeClient.echo(EchoRequest()));
await Future.delayed(Duration(milliseconds: 200), () => timer.cancel()); await Future.delayed(Duration(milliseconds: 200), () => timer.cancel());
// Wait for last request to be sent
await Future.delayed(Duration(milliseconds: 20));
// Check that the server never closed the connection // Check that the server never closed the connection
expect(fakeChannel.newConnectionCounter, 1); expect(fakeChannel.newConnectionCounter, 1);
}); });
@ -90,7 +94,7 @@ void main() {
/// A wrapper around a [FakeHttp2ClientConnection] /// A wrapper around a [FakeHttp2ClientConnection]
class FakeClientChannel extends ClientChannel { class FakeClientChannel extends ClientChannel {
late FakeHttp2ClientConnection fakeHttp2ClientConnection; FakeHttp2ClientConnection? fakeHttp2ClientConnection;
FakeClientChannel( FakeClientChannel(
super.host, { super.host, {
super.port = 443, super.port = 443,
@ -101,11 +105,11 @@ class FakeClientChannel extends ClientChannel {
@override @override
ClientConnection createConnection() { ClientConnection createConnection() {
fakeHttp2ClientConnection = FakeHttp2ClientConnection(host, port, options); fakeHttp2ClientConnection = FakeHttp2ClientConnection(host, port, options);
return fakeHttp2ClientConnection; return fakeHttp2ClientConnection!;
} }
int get newConnectionCounter => int get newConnectionCounter =>
fakeHttp2ClientConnection.newConnectionCounter; fakeHttp2ClientConnection?.newConnectionCounter ?? 0;
} }
/// A [Http2ClientConnection] exposing a counter for new connections /// A [Http2ClientConnection] exposing a counter for new connections

View File

@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:fake_async/fake_async.dart';
import 'package:grpc/src/server/server_keepalive.dart'; import 'package:grpc/src/server/server_keepalive.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
@ -33,75 +34,83 @@ void main() {
dataStream.close(); dataStream.close();
}); });
final timeAfterPing = Duration(milliseconds: 10);
test('Sending too many pings without data kills connection', () async { test('Sending too many pings without data kills connection', () async {
initServer(); FakeAsync().run((async) {
// Send good ping initServer();
pingStream.sink.add(null); // Send good ping
await Future.delayed(Duration(milliseconds: 10));
// Send [maxBadPings] bad pings, that's still ok
for (var i = 0; i < maxBadPings; i++) {
pingStream.sink.add(null); pingStream.sink.add(null);
} async.elapse(timeAfterPing);
await Future.delayed(Duration(milliseconds: 10));
expect(goAway, false);
// Send another bad ping; that's one too many! // Send [maxBadPings] bad pings, that's still ok
pingStream.sink.add(null); for (var i = 0; i < maxBadPings; i++) {
await Future.delayed(Duration(milliseconds: 10)); pingStream.sink.add(null);
expect(goAway, true); }
async.elapse(timeAfterPing);
expect(goAway, false);
// Send another bad ping; that's one too many!
pingStream.sink.add(null);
async.elapse(timeAfterPing);
expect(goAway, true);
});
}); });
test( test(
'Sending too many pings without data doesn`t kill connection if the server doesn`t care', 'Sending too many pings without data doesn`t kill connection if the server doesn`t care',
() async { () async {
initServer(ServerKeepAliveOptions( FakeAsync().run((async) {
maxBadPings: null, initServer(ServerKeepAliveOptions(
minIntervalBetweenPingsWithoutData: Duration(milliseconds: 5), maxBadPings: null,
)); minIntervalBetweenPingsWithoutData: Duration(milliseconds: 5),
// Send good ping ));
pingStream.sink.add(null); // Send good ping
await Future.delayed(Duration(milliseconds: 10));
// Send a lot of bad pings, that's still ok.
for (var i = 0; i < 50; i++) {
pingStream.sink.add(null); pingStream.sink.add(null);
} async.elapse(timeAfterPing);
await Future.delayed(Duration(milliseconds: 10));
expect(goAway, false); // Send a lot of bad pings, that's still ok.
for (var i = 0; i < 50; i++) {
pingStream.sink.add(null);
}
async.elapse(timeAfterPing);
expect(goAway, false);
});
}); });
test('Sending many pings with data doesn`t kill connection', () async { test('Sending many pings with data doesn`t kill connection', () async {
initServer(); FakeAsync().run((async) {
initServer();
// Send good ping // Send good ping
pingStream.sink.add(null);
await Future.delayed(Duration(milliseconds: 10));
// Send [maxBadPings] bad pings, that's still ok
for (var i = 0; i < maxBadPings; i++) {
pingStream.sink.add(null); pingStream.sink.add(null);
} async.elapse(timeAfterPing);
await Future.delayed(Duration(milliseconds: 10));
expect(goAway, false);
// Sending data resets the bad ping count // Send [maxBadPings] bad pings, that's still ok
dataStream.add(null); for (var i = 0; i < maxBadPings; i++) {
await Future.delayed(Duration(milliseconds: 10)); pingStream.sink.add(null);
}
async.elapse(timeAfterPing);
expect(goAway, false);
// Send good ping // Sending data resets the bad ping count
pingStream.sink.add(null); dataStream.add(null);
await Future.delayed(Duration(milliseconds: 10)); async.elapse(timeAfterPing);
// Send [maxBadPings] bad pings, that's still ok // Send good ping
for (var i = 0; i < maxBadPings; i++) {
pingStream.sink.add(null); pingStream.sink.add(null);
} async.elapse(timeAfterPing);
await Future.delayed(Duration(milliseconds: 10));
expect(goAway, false);
// Send another bad ping; that's one too many! // Send [maxBadPings] bad pings, that's still ok
pingStream.sink.add(null); for (var i = 0; i < maxBadPings; i++) {
await Future.delayed(Duration(milliseconds: 10)); pingStream.sink.add(null);
expect(goAway, true); }
async.elapse(timeAfterPing);
expect(goAway, false);
// Send another bad ping; that's one too many!
pingStream.sink.add(null);
async.elapse(timeAfterPing);
expect(goAway, true);
});
}); });
} }