dart-lang / http

A composable API for making HTTP requests in Dart.
https://pub.dev/packages/http
BSD 3-Clause "New" or "Revised" License
1.01k stars 351 forks source link

Frequent bad file descriptor errors on iOS #197

Open alanrussian opened 5 years ago

alanrussian commented 5 years ago

We deploy a Flutter app and have been noticing frequent exceptions from our HTTP requests:

SocketException: OS Error: Bad file descriptor, errno = 9, address = <redacted domain>, port = 64436

From my discussion with @johnfesa, it is sometimes expected for iOS to throw these bad file descriptor errors. See Apple's Networking and Multitasking documentation. However, we're filing this issue for a few reasons:

mulderpf commented 5 years ago

I deployed my iPhone Flutter app two days ago and saw this exact same issue yesterday (out of about 10 users).

What is of note, is that this is a request which happened after the application was unpaused. (I suspend any attempts to get more data while the app is in the background and then resume when the app comes back).

kuhnroyal commented 3 years ago

I am seeing this as well. How do you guys handle this when it occurs? Catch the exception, recreate the http client and re-run the request(s)?

willbryant commented 3 years ago

Thanks @alanrussian for that link. This is a terrifying error for people who know Unix syscalls and not that special behavior :).

It'd be great if Flutter on iOS could map this to a less scary error as my read of that link is that we should consider this like any other connection closed type event (except that this one happened due to the program's execution being suspended not the peer closing the connection).

dmitry-fbm commented 2 years ago

HttpException: Bad file descriptor, uri = https://domain/i_18409528_1647229044.jpg

Empty stacktrace.

kdcyberdude commented 2 years ago

Any update on this?

nobrefelipe commented 1 year ago

up

DjordjeMancic97 commented 1 year ago

uuuuuup

eric-khoury commented 1 year ago

Same issue here:

SocketException: Bad file descriptor (OS Error: Bad file descriptor, errno = 9), address = firebasestorage.googleapis.com, port = 51490 Seems to be happening using the Image.network in our case.

html-rulez-d00d commented 1 year ago

@eric-khoury Exactly the same for us. Seems to be on a failed NetworkImage.load in our case: (Reporting via Firebase Crashlytics)

Fatal Exception: FlutterError
0  ???                            0x0 _HttpClient.getUrl (dart:_http)
1  ???                            0x0 NetworkImage._loadAsync + 86 (_network_image_io.dart:86)
2  ???                            0x0 NetworkImage.load + 49 (_network_image_io.dart:49)
3  ???                            0x0 ImageProvider.resolveStreamForKey.<fn> + 488 (image_provider.dart:488)
4  ???                            0x0 ImageCache.putIfAbsent + 379 (image_cache.dart:379)
5  ???                            0x0 ImageProvider.resolveStreamForKey + 486 (image_provider.dart:486)
6  ???                            0x0 ScrollAwareImageProvider.resolveStreamForKey + 106 (scroll_aware_image_provider.dart:106)
7  ???                            0x0 ImageProvider.resolve.<fn> + 333 (image_provider.dart:333)
8  ???                            0x0 ImageProvider._createErrorHandlerAndKey.<fn> + 448 (image_provider.dart:448)
9  ???                            0x0 SynchronousFuture.then + 41 (synchronous_future.dart:41)
10 ???                            0x0 ImageProvider._createErrorHandlerAndKey + 445 (image_provider.dart:445)
11 ???                            0x0 ImageProvider.resolve + 330 (image_provider.dart:330)
12 ???                            0x0 _ImageState._resolveImage + 1119 (image.dart:1119)
13 ???                            0x0 _ImageState.didChangeDependencies + 1071 (image.dart:1071)
14 ???                            0x0 StatefulElement.performRebuild + 4974 (framework.dart:4974)
15 ???                            0x0 Element.rebuild + 4529 (framework.dart:4529)
16 ???                            0x0 BuildOwner.buildScope + 2659 (framework.dart:2659)
17 ???                            0x0 WidgetsBinding.drawFrame + 891 (binding.dart:891)
18 ???                            0x0 RendererBinding._handlePersistentFrameCallback + 370 (binding.dart:370)
19 ???                            0x0 SchedulerBinding._invokeFrameCallback + 1146 (binding.dart:1146)
20 ???                            0x0 SchedulerBinding.handleDrawFrame + 1083 (binding.dart:1083)
21 ???                            0x0 SchedulerBinding._handleDrawFrame + 997 (binding.dart:997)
fkranenburg commented 1 year ago

This should be fixed because it happens to frequently on IOS devices.

GaelleJoubert commented 1 year ago

I have this error a lot as well since all the Iphones did the last update, what can be done about it ?

KalinRangelovRangelov commented 1 year ago

Same here. Frequently happens on IOS devices

thankiyash commented 1 year ago

Facing this issue as well

WiRight commented 1 year ago

Same here

iOS 16.1 Flutter 3.3.9

SocketException: Bad file descriptor (OS Error: Bad file descriptor, errno = 9) while connecting to WS on GoLang

MartinCastellon commented 1 year ago

I am seeing the same issue on an iPhone X, running iOS 15.0. Any ideas on how to fix this?

dmitry-fbm commented 1 year ago

Our new logger revealed a bit more detail on this, here is stacktrace:

0   IOClient.send (package:http/src/io_client.dart:88)
1   <asynchronous suspension>
2   BaseClient._sendUnstreamed (package:http/src/base_client.dart:93)
3   <asynchronous suspension>
4   _withClient (package:http/http.dart:164)
5   <asynchronous suspension>
ArkeshGKalathiya commented 1 year ago

I deployed my iPhone Flutter app two days ago and saw this exact same issue yesterday (out of about 10 users).

What is of note, is that this is a request which happened after the application was unpaused. (I suspend any attempts to get more data while the app is in the background and then resume when the app comes back).

Same issue here, were you able to solve this issue?

tulioccalazans commented 1 year ago

Same issue here!

sikandernoori commented 1 year ago

same issue here.

Surio89 commented 1 year ago

same issue here

AlexDochioiu commented 1 year ago

same issue here

ch-muhammad-adil commented 1 year ago

Happening with me too when application goes in background and brought back in foreground after sometime, not sure if this is http or network?

dr0-dev commented 1 year ago

why is this ticket still open and not assigned to anyone?

willbryant commented 1 year ago

@dr0-dev Because it's normal for iOS to do this and there's nothing to suggest anything is wrong on the Flutter side, other than to think about overriding the OS error message to something more specific so people stop worrying about it?

Frankdroid7 commented 1 year ago

Is there any update on this open issue please?

brianquinlan commented 1 year ago

Hi,

As @alanrussian said, it is not unexpected for sockets to be invalidated when the app is suspended. From Apple's Documentation:

If you do leave your data socket open when going into the background, you must correctly handle errors on that socket. Handling errors is not a new requirement, but it is particularly important in this case because, if your app gets suspended, the socket's resources might get reclaimed by the kernel, after which all networking operations on the socket will fail. The only thing you can do with the socket at this point is to close it.

Note: When your app resumes execution the actual error returned by a socket's whose resources have been reclaimed is purposely not specified here to allow for future refinements. However, in many cases the error will be EBADF, which is probably not what you were expecting! Under normal circumstances EBADF means that the app has passed an invalid file descriptor to a system call. However, in the case of a socket whose resources have been reclaimed, it does not mean that the file descriptor was invalid, just that the socket is no longer usable.

Wrapping your Client in RetryClient should mask the symptoms i.e. RetryClient(client, whenError: (o, s) => o is SocketError)

Reducing idleTimeout might reduce the frequency of this issue.

You could also consider using package:cupertino_http (which is compatible with package:http), where this issue will likely occur less because the OS manages the connection pool. But this failure can still happen:

If you're using NSURLConnection, the connection will call your -connection:didFailWithError: delegate method to signal the error.

I'm open to ideas on what else we can do e.g.

  1. improve the documentation on this
  2. automatically retry (that's a bit dangerous - it might mask real errors because EBADF isn't just for reclaimed sockets
  3. invalidate the HttpClient connection pool when the application is suspected - will reduce performance in some cases, would require coordination with flutter

Something else?

@natebosch Any ideas?

brianquinlan commented 1 year ago

Someone on the Flutter team had a suggestion: we add a method to clear the connection pool on HttpClient and we ask developers to do that before their apps are suspended. So something like this:

HttpClient client;
...
  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    super.didChangeAppLifecycleState(state);
    if (state == AppLifecycleState.paused) {
     client.clearConnectionPool();  // Think of a better name.
    }
  }
natebosch commented 1 year ago

2. that's a bit dangerous - it might mask real errors because EBADF isn't just for reclaimed sockets

What other situations cause this error? If we could reliably detect the situation I'd be tempted to retry automatically, because I think that's the right solution for any usage scenario I can image.

If we can't reliably detect it, how would we frame the documentation - when would our users want to retry or not?

brianquinlan commented 1 year ago

The situations that I can think of were this error might occur are:

  1. Bugs in the Dart Sockets/HttpClient implementation (hopefully rare)
  2. Incorrect implementation of a user-defined HttpClient.connectionFactory

But, AFAIK, there is no canonical source for what failures can result in EBADF - obviously Apple is using it for a scenario not described by the POSIX specification.

patelnirav48 commented 1 year ago

Someone on the Flutter team had a suggestion: we add a method to clear the connection pool on HttpClient and we ask developers to do that before their apps are suspended. So something like this:

HttpClient client;
...
  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    super.didChangeAppLifecycleState(state);
    if (state == AppLifecycleState.paused) {
     client.clearConnectionPool();  // Think of a better name.
    }
  }

@brianquinlan By doing this it will not give bad file descriptor error but what to do with stucked api call? how it will re-call again and give response once un-lock device. thanks!

arualana commented 1 year ago

Is this ever going to be addressed

malbolged commented 1 year ago

Is this ever going to be addressed

image

brianquinlan commented 1 year ago

I'm not convinced that https://github.com/dart-lang/http/issues/197#issuecomment-1428812398 is the best solution:

  1. I don't have any evidence that it will work
  2. it will only work with dart:io HttpClient
  3. it is a breaking change

Using package:cupertino_http should mitigate this issue.

But I'd love to get more feedback on possible approaches!

Surio89 commented 1 year ago

cupertino_http works for me! If you are using Dio i can also recommend the native_dio_adapter package.

sha3rawi33 commented 1 year ago

up^

dmitry-fbm commented 1 year ago

If you are using Dio i can also recommend the native_dio_adapter package.

Thanks @Surio89, that worked. No errors anymore.

hls-app commented 7 months ago
Screenshot 2024-01-31 at 11 23 30 PM

Can confirm this is happening on http: ^1.2.0

brianquinlan commented 7 months ago

@hls-app You are using IOClient, which is not package:cupertino_http.

hls-app commented 7 months ago

Thanks @brianquinlan. As mentioned here https://api.flutter.dev/flutter/dart-io/HttpClient-class.html requires you to close the client. What would be the best practise here? Closing after every requests or closing when the app exits?

utopicnarwhal commented 7 months ago

What would be the best practise here? Closing after every requests or closing when the app exits?

AFAIK, it's better to keep the client if you're planning to make requests to the same host later. In this case, in certain cases, the connection is kept alive, which decreases the amount of time needed for the next request.

hls-app commented 7 months ago

Thanks @utopicnarwhal. In my case, they are mostly serverless API calls so I assume there is no added benefit with keeping the connection alive?

Also, do you have any examples on closing the client when the app is not in use? I was using http until now and all of these seems a bit of extra work in flutter world.

utopicnarwhal commented 7 months ago

In my case, they are mostly serverless API calls so I assume there is no added benefit with keeping the connection alive?

Even though it's "serverless" there is still a server but not your own. So you may want to check the support of this feature on your backend. You can read more here

Also, do you have any examples on closing the client when the app is not in use?

No, I don't. I'am just creating one http client per API server in the initialisation stage of the app. And keeping them until the app is killed by the OS (whcih also frees all the resources).

hls-app commented 7 months ago

@utopicnarwhal Thanks for the clarification. Does that mean, you don't manually close the client? I read about people complaining about potential leaks. So just want to make sure if this is an accepted approach.

hls-app commented 7 months ago

Even though it's "serverless" there is still a server but not your own. So you may want to check the support of this feature on your backend. You can read more here

I am not sure if I agree with this. Lambdas for example are stateless so I am not sure if there are any benefits to keep the connections alive or if that matter anyway.

utopicnarwhal commented 7 months ago

Does that mean, you don't manually close the client?

Yes, because I had no cases when I'm sure that certain http client isn't going to be used in the app anymore at all.

I read about people complaining about potential leaks

You may get the memory leak only if you create a new instance of the http client for every request without disposing the previous.

Even though it's "serverless" there is still a server but not your own

I am not sure if I agree with this Lambdas for example are stateless

Do you disagree that there is a computer instance (server) that processes your request and runs your code when you use Lambda? 🤔

hiteshsapra-bt commented 7 months ago

The same issue was reproduced on Flutter latest version

flutter_error_exception

SocketException: Connection reset by peer (OS Error: Connection reset by peer, errno = 54), address = 192.168.10.34, port = 63558
-- | --
[✓] Flutter (Channel stable, 3.16.3, on macOS 14.2.1 23C71 darwin-arm64, locale en-IN)
[✓] Android toolchain - develop for Android devices (Android SDK version 33.0.2)
[✓] Xcode - develop for iOS and macOS (Xcode 15.0.1)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2023.1)
[✓] VS Code (version 1.81.1)
[✓] Connected device (5 available)
brianquinlan commented 6 months ago

For anyone experiencing this issue, I'd strongly suggest that you use package:cupertino_http.

hls-app commented 4 months ago

@brianquinlan this is still happening with package:cupertino_http.

ClientException with SocketException: Bad file descriptor (OSError: OS Error: Bad file descriptor, errno = 9)

PS: I am not closing the http client leaving it for the system to handle and using serverless API calls.

brianquinlan commented 4 months ago

@hls-app Are you sure that you are using cupertino_http in the configuration that is generating the exception?

Because only the IOClient generates that exception

hls-app commented 3 months ago

@brianquinlan yes, using cupertino_http

_ClientSocketException: ClientException with SocketException: Bad file descriptor (OS Error: Bad file descriptor, errno =...

  HttpApiClient({Client? httpClient})
      : _httpClient = httpClient ??
            (Platform.isIOS
                ? CupertinoClient.defaultSessionConfiguration()
                : CronetClient.defaultCronetEngine());

The Client is from http

The URI link was a 404.