grpc / grpc-dart

The Dart language implementation of gRPC.
https://pub.dev/packages/grpc
Apache License 2.0
857 stars 270 forks source link

Question about restore connection #180

Closed VladimirLuzhin closed 4 years ago

VladimirLuzhin commented 5 years ago

grpc_dart: 1.0.2

Hello! My team decided to try gRPS in our new project, but we had a question about how to properly restore the connection in the following situation: 1) There was a short-term loss of communication with the server (for example, the Wi-Fi router was restarted) 2) There has been a long loss of communication with the server 3) The server has become unavailable

Now we have written a test application, a server in C # and, as a client, a mobile application on flutter. When we reproduced the situations described above, we received the following error codes: 2, 4, 14. Client code:

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:grpc/grpc.dart' as grpc;
import 'package:grpc/service_api.dart';
import 'package:grpc_test/gen/lib/hello.pbgrpc.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'gRPC test',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'gRPC test page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  TestServiceClient _client;
  String _counter = "";

  var _controller;
  Stream<RpcRequest> _stream;

  ResponseStream<RpcResponse> _responseStream;

  ClientChannel _channel;

  _MyHomePageState() {
    _channel = ClientChannel(
        "10.10.230.110",
        port: 50051,
        options: grpc.ChannelOptions(
            credentials: grpc.ChannelCredentials.insecure(),
            idleTimeout: Duration(seconds: 5),
            backoffStrategy: (_) => Duration(seconds: 5)
        ));

    _client = TestServiceClient(_channel);
    _controller = StreamController<RpcRequest>.broadcast();
    _stream = _controller.stream;
    _stream.listen((data) {
      print("Stream data $data");
    },
        onError: (error) {
          print("Stream error ${error.toString()}");
        },
        onDone: () {
          print("Stream done}");
        }
    );
    _controller.onCancel = () {
      print("Controller onCancel");
    };

    createCall();
  }

  void _incrementCounter() {
    setState(() {
      _counter = "Started";
    });
  }

  @override
  void dispose() {
    _controller.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(

        title: Text(widget.title),
      ),
      body: Center(

        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            TextField(
              onSubmitted: (text) {
                _controller.add(RpcRequest()..info = text..id = "qwerty");
              },
            ),
            Text(
              'Messages from server',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }

  Future<void> createCall() async {
    final currentState = (await _channel.getConnection()).state;
    print("State ${currentState.toString()}");

    final meta = new Map<String, String>();
    meta.addEntries([new MapEntry<String, String>("id", "mobileId")]);
    _responseStream = _client.sendReuqest(_stream, options: CallOptions(metadata: meta));
    _responseStream.listen((RpcResponse response) {
      print("${response.response}");
      setState(() {
        _counter = "${response.response}";
      });
    },
      onError: (error) {
        print("${error.toString()}");
        if (error is grpc.GrpcError) {
          setState(() {
            _counter = error.toString();
          });
          if (error.code == 2 || error.code == 4 || error.code == 14) {
            Future.delayed(Duration(seconds: 1), () => createCall());
          }
        }
      },
      onDone: () {
        print("connection done");
      }
    );
  }
}

And actually the question itself, we have to completely re-create the connection with such errors? Or should it recover itself? Thanks in advance for your reply.

quetool commented 5 years ago

I would like to know this as well

lucaslcode commented 4 years ago

@VladimirLuzhin This was possibly fixed in #231, is the issue still present?

zs-dima commented 4 years ago

is the issue still present?

Yes

mraleph commented 4 years ago

We believe that connection handling is now more robust. If it is not (e.g. you have examples where client does not gracefully reconnect and reissue requests) please report separate issues. Thanks.