16

We are in a situation where the production app is facing the following socket exception and not able to perform any other network operation after this. 

DioError [DioErrorType.DEFAULT]: SocketException: Failed host lookup: ‘xyz.abc.com’ (OS Error: nodename nor servname provided, or not known, errno = 8)

Note: Encountered repetitively with one user having iPhone X, iOS 14.4

We are using Dio as a network client, with Retrofit, which internally uses the HttpClient from the dart. With Dio the exception is not reproducible with the simulated environment but using HttpClient directly, the same exception can be reproduced with the following code in iOS simulator.

HttpClient userAgent = new HttpClient();
  bool run = true;
  while (run) {
    try {
      await userAgent.getUrl(Uri.parse('https://www.google.com'));
      print('Number of api executed');
    } catch (e) {
      print(e);
      if (e is SocketException) {
        if ((e as SocketException).osError.errorCode == 8)
          print('***** Exception Caught *****');
      }
    }
  }

Once the exception was thrown, the HttpClient was not able to recover from that stale state and all other API requests were started failing with the same error.

enter image description here

We were able to recover from that stale state by force closing all the previous connections and opening up a new HttpClient.

  HttpClient userAgent = new HttpClient();
  bool run = true;
  while (run) {
    try {
      await userAgent.getUrl(Uri.parse('https://www.google.com'));
      print('Number of api executed');
    } catch (e) {
      print(e);

      if (e is SocketException) {
        if ((e as SocketException).osError.errorCode == 8)
          print('***** Exception Caught *****');
      }
      userAgent.close(force: true);
      print('Force closing previous connections');
      userAgent = HttpClient();
      print('Creating new HttpClient instance');
    }
  }

enter image description here

One interesting fact is after every 236 requests the exception is raising. It could be because of file descriptors over usage but iOS has a limit of 256.

With a stable internet connection, this issue reproducible every time in iOS simulator.

Although I am not able to reproduce the issue with Dio client but as in production it is occurring. So I am seeking help to understand the root cause of this issue, also how we can prevent it?

Anyone who has come across this kind of situation and how you have overcome it, please help me.

Thanks in advance.

Tapas Pal
  • 7,073
  • 8
  • 39
  • 86

4 Answers4

1

That's a strange error.

This might not answer your question, but may push us towards figuring out what's going on.

The code snippet (copied from question) will open up a new stream with each .getUrl() call and will not close them. (I'm assuming this is intentional to create the socket exception?)

HttpClient userAgent = new HttpClient();
  bool run = true;
  while (run) {
    try {
      await userAgent.getUrl(Uri.parse('https://www.google.com'));
      print('Number of api executed');
    } catch (e) {
      print(e);
      if (e is SocketException) {
        if ((e as SocketException).osError.errorCode == 8)
          print('***** Exception Caught *****');
      }
    }
  }

At some point, a limit (of open streams) is hit. I guess that magic number is 236 in your case.

So at that point, is when you're seeing the nodename or servname provided exception?

(Btw, as an aside, I think that error is coming from the underlying host operating system's DNS service, although I'm not sure if it's due to the request spam, the number of open connections, etc. This may not be relevant info.)

So, if you used the HttpClient in a typical way, making requests & closing those open streams, such as this:

      var request = await userAgent.getUrl(Uri.parse('http://example.com/'));
      var response = await request.close(); // ← close the stream
      var body = await response.transform(utf8.decoder).join();
      // ↑ convert results to text
      // rinse, repeat... 

... Are you still seeing the same nodename or servname provided error pop up?

With this "typical usage" code immediately above, the userAgent can be reused until a userAgent.close() call is made (and the HttpClient is permanently closed. Trying to use it again would throw a Bad State exception).

I'd be interested to hear if the nodename error still occurs with this modified code.


Re: the second code snippet from the question.

In the catch block, the HttpClient is closed, then a new HttpClient is created. This effectively closes all the open streams that were opened in the try block (and I assume, resetting the limit of open streams.)

If you adjusted the 2nd code example to use:

      var req = await userAgent.getUrl(Uri.parse('https://www.google.com'));
      userAgent.close(force: true);
      userAgent = HttpClient();
      print('Number of api executed');

Could you run that indefinitely?

Baker
  • 24,730
  • 11
  • 100
  • 106
  • even after close on request, we are getting the same exception. – Tapas Pal Feb 11 '21 at 15:10
  • The `while (run)` block... does it create a bunch of connections simultaneously or it waits till each req/response is complete before doing the next loop? – Baker Feb 11 '21 at 18:08
  • Is that test a good representation of what a client app would actually do? If that loop is processed fast enough, maybe it's still hitting a limit of DNS requests or something similar. I'm guessing the original error was hit in the production app because connections were *never* being closed. If you put a two-second delay in each loop (in the `while (run)` test, where connections *are* being closed), could it run forever? – Baker Feb 11 '21 at 19:05
  • Just tested with having a short delay (2 sec.) in the while loop. Did 300 iterations without any problems. I assume it's good for an infinite number iterations. – Baker Feb 14 '21 at 21:05
  • Found one fact, ran the internet instruments with the same code base, with dart HttpClient and Dio. For httpClient it was opening the connection for every call and that's why after a limit it was throwing socket exception. On the other hand, Dio was smartly handling the connections and resuing 3-4 connections only. That's why we were not able to reproduce the same exception with Dio. – Tapas Pal Feb 16 '21 at 14:15
  • From this observation, we can omit one of the possibilities of opening multiple connections. – Tapas Pal Feb 16 '21 at 14:17
1

i have same issue resolve with this code:-

Exmaple

//Add This Class
    class MyHttpOverrides extends HttpOverrides{
      @override
      HttpClient createHttpClient(SecurityContext? context){
        return super.createHttpClient(context)
          ..badCertificateCallback = (X509Certificate cert, String host, int port)=> true;
      }
    }
    
    Future<void> main() async {
      HttpOverrides.global = MyHttpOverrides();      //call here
      runApp(const MyApp());
    }
Sachin Kumawat
  • 297
  • 1
  • 7
  • 1
    Tried this, doesn't solve it. Still getting hundreds of `SocketException: Failed host lookup` errors. – Pierre Jul 19 '22 at 05:18
1

1:Obtain the current limit of file descriptors

ulimit -n An example output: “256” or “10032”.

PROTIP: On MacOS, the maximum number that can be specified is 12288.

Obtain the current limit of processes

ulimit -u An example output: “1418”.

sudo launchctl limit maxfiles 65536 200000

  • 1
    Mac OS computers have 256 request limitations. If you run the above code, it will probably work. In case it worked. – Bahadır KALAY Mar 28 '22 at 09:16
  • This worked. I spent DAYS on this. Thank you! In debug mode the ulmit is set to 200k something, in release mode the ulimit was set to 256 and therefore more connections were not going through. – novas1r1 May 17 '23 at 15:48
0

I got exactly the same errors in production, it happens intermittently. Like Baker said, close the connections:

import 'package:http/http.dart' as http;

Future<http.Response> get(String url) async {
    var httpClient = http.Client() as http.BaseClient;
    
    Map<String, String> headers = {};
    headers['Content-Type'] = 'application/json; charset=UTF-8';

    var result = await httpClient
        .get(Uri.parse(url), headers: headers)
        .timeout(
            const Duration(seconds: 60),
            onTimeout: () => http.Response('Request Timeout', 408),
        );
    
    httpClient.close();
    
    return result;
}

I did 10x Future.Delayeds each doing a loop with 300 get requests at the same time, didn't find any issues.

The future delayeds was done like so:

Future.delayed(const Duration(milliseconds: 10), () async {
  for (var i = 0; i < 300; i++) {
    var pingResult = await Api.instance.ping();
    print('Delayed 1 Result (${i}): ${pingResult.success}');
  }
});

Future.delayed(const Duration(milliseconds: 10), () async {
  for (var i = 0; i < 300; i++) {
    var pingResult = await Api.instance.ping();
    print('Delayed 2 Result (${i}): ${pingResult.success}');
  }
});

//..
Pierre
  • 8,397
  • 4
  • 64
  • 80