http icon indicating copy to clipboard operation
http copied to clipboard

Frequent bad file descriptor errors on iOS

Open alanrussian opened this issue 6 years ago • 65 comments

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:

  • Since this exception is expected to occur on iOS, we were wondering if it might be a good idea to raise a more specific exception than a generic OS error.

  • We're noticing this error frequently enough that we wanted to double check that there isn't an issue with the way the sockets are being managed that would be causing it.

alanrussian avatar Sep 10 '18 23:09 alanrussian

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).

mulderpf avatar Dec 19 '18 06:12 mulderpf

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)?

kuhnroyal avatar Nov 09 '20 13:11 kuhnroyal

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).

willbryant avatar Aug 05 '21 02:08 willbryant

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

Empty stacktrace.

dmitry-fbm avatar Mar 22 '22 20:03 dmitry-fbm

Any update on this?

kdcyberdude avatar Aug 06 '22 05:08 kdcyberdude

up

nobrefelipe avatar Sep 22 '22 21:09 nobrefelipe

uuuuuup

DjordjeMancic97 avatar Oct 14 '22 13:10 DjordjeMancic97

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.

eric-khoury avatar Oct 30 '22 08:10 eric-khoury

@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)

html-rulez-d00d avatar Oct 31 '22 20:10 html-rulez-d00d

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

fkranenburg avatar Nov 14 '22 20:11 fkranenburg

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

GaelleJoubert avatar Nov 28 '22 08:11 GaelleJoubert

Same here. Frequently happens on IOS devices

KalinRangelovRangelov avatar Nov 30 '22 16:11 KalinRangelovRangelov

Facing this issue as well

thankiyash avatar Dec 01 '22 19:12 thankiyash

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

WiRight avatar Dec 05 '22 06:12 WiRight

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

MartinCastellon avatar Dec 12 '22 19:12 MartinCastellon

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>

dmitry-fbm avatar Dec 12 '22 19:12 dmitry-fbm

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?

ArkeshGKalathiya avatar Dec 28 '22 14:12 ArkeshGKalathiya

Same issue here!

tulioccalazans avatar Jan 18 '23 21:01 tulioccalazans

same issue here.

sikandernoori avatar Jan 20 '23 09:01 sikandernoori

same issue here

Surio89 avatar Jan 23 '23 08:01 Surio89

same issue here

AlexDochioiu avatar Jan 24 '23 19:01 AlexDochioiu

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

ch-muhammad-adil avatar Jan 31 '23 18:01 ch-muhammad-adil

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

dr0-dev avatar Feb 01 '23 11:02 dr0-dev

@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?

willbryant avatar Feb 03 '23 03:02 willbryant

Is there any update on this open issue please?

Frankdroid7 avatar Feb 13 '23 09:02 Frankdroid7

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 avatar Feb 13 '23 18:02 brianquinlan

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 avatar Feb 13 '23 22:02 brianquinlan

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?

natebosch avatar Feb 21 '23 20:02 natebosch

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.

brianquinlan avatar Mar 09 '23 21:03 brianquinlan

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!

patelnirav48 avatar May 29 '23 07:05 patelnirav48