http
http copied to clipboard
Frequent bad file descriptor errors on iOS
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.
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).
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)?
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).
HttpException: Bad file descriptor, uri = https://domain/i_18409528_1647229044.jpg
Empty stacktrace.
Any update on this?
up
uuuuuup
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 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)
This should be fixed because it happens to frequently on IOS devices.
I have this error a lot as well since all the Iphones did the last update, what can be done about it ?
Same here. Frequently happens on IOS devices
Facing this issue as well
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
I am seeing the same issue on an iPhone X, running iOS 15.0. Any ideas on how to fix this?
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>
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?
Same issue here!
same issue here.
same issue here
same issue here
Happening with me too when application goes in background and brought back in foreground after sometime, not sure if this is http or network?
why is this ticket still open and not assigned to anyone?
@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?
Is there any update on this open issue please?
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.
- improve the documentation on this
- automatically retry (that's a bit dangerous - it might mask real errors because
EBADF
isn't just for reclaimed sockets - 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?
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.
}
}
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?
The situations that I can think of were this error might occur are:
- Bugs in the Dart Sockets/HttpClient implementation (hopefully rare)
- 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.
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!