birdofpreyru / react-native-fs

File system access for React Native
https://dr.pogodin.studio/docs/react-native-file-system
Other
121 stars 8 forks source link

downloadFile randomly hanging in release mode iOS #24

Closed zabojad closed 1 month ago

zabojad commented 5 months ago

I'm observing random hanging on downloadFile. It seems to happen in release mode only (and iOS only).

There is no error, no crash. Just the downloadFile promise than never resolves nor reject. And absolutely no connection is made to the server (I've checked the server access logs).

Adding a timeout in downloadFile options make the promise reject but that's the only improvment...

What could cause that and what should fix it?

birdofpreyru commented 5 months ago

Hi @zabojad ,

Random, release mode only, iOS only.

This reads like a tricky issue to debug. Also, I don't use downloadFile() myself in any of my projects, thus I had no chance to notice anything.

My first thoughts on it:

zabojad commented 5 months ago

Hi there!

Below my answers :

Do I get it right, that you experience it on the same device (simulator) you own? I mean, in contrast to capturing this issue from end-user devices using some kind of remote error tracker, like Sentry?

I experience it on my own iPhone SE as well as on my colleague's iPhone 12 pro. However, there is no crash nor error that could be captured with Sentry. It just randomly hangs on NSURLSession downloadTaskWithURL and I don't know why... It seems to happen only on release builds (I haven't seen it in debug mode) and not always. Sometime it will work, some other time it won't...

I've found these:

I need more time to check it out...

For now, to workaround the problem, I've installed react-native-blob-util and use it for file download. The problem does not happen at all with that lib.

Are you sure there is nothing funny going on with the remote server you download from, or with the network connectivity? I am wondering, might it be that the library fails to correctly handle some sort of connection error?

Yes I'm sure about this. We've checked it again and again. When the problem happens, the server doesn't even receive the request. Besides, the problem does not happen with react-native-blob-util.

I believe, example app has a test for downloadFile(), but I did not run it in release mode on iOS. Should try it, whether error is reproducible in the example app.

It is not easy to reproduce it. For now, I've seen it only with release builds and it is not systematic...

I'll keep you updated on this if I find some more time to spend on it.

relaxxpls commented 5 months ago

Im facing this in debug mode aswell. downloadFile never resolves.

console.log("hello")
const response = await downloadFile({
  fromUrl: "ANY_URL",
  toFile: "DOWNLOAD_PATH",
  // background: true,
  // cacheable: false,
  begin: (res) => {
    console.log('begin', res);
  },
  // progressDivider: 2,
  // progressInterval: 1000,
  progress: ({ bytesWritten, contentLength }) => {
    console.log('progress', bytesWritten, contentLength);
    setProgress?.((bytesWritten / contentLength) * 100);
  },
});
console.log("done", response);

output is just hello on ios, v2.22.1 of this library

relaxxpls commented 5 months ago

Update, I kept waiting on the download screen and after about 1-2mins, it randomly began downloading. and triggered the console logs in begin & progress.

zabojad commented 5 months ago

Thank you @relaxxpls , I feel less alone now :D...

relaxxpls commented 5 months ago

@zabojad I used @kesha-antonov/react-native-background-downloader as it supports background downloads

birdofpreyru commented 5 months ago

I wouldn't intermix the issue #27 with background downloads on iOS, and this one.

zabojad commented 5 months ago

Just a side note: in my case, the donwloads are not done in background. They are very light pdf files downloads that - when they work - are performed in less than a sec...

birdofpreyru commented 5 months ago

@zabojad and do you use any other options beside fromUrl and toFile?

zabojad commented 5 months ago

@birdofpreyru yep, I was using the headers param to pass a authentication header in the form of Authorization: Bearer ${token}

liamjones commented 5 months ago

I was using the headers param to pass a authentication header in the form of Authorization: Bearer ${token}

Is the module not handling that properly? Authorization is one of the special headers you can't just set directly on an NSURLRequest: https://developer.apple.com/documentation/foundation/nsurlrequest#1776617

It has to be handled via a delegate: https://developer.apple.com/documentation/foundation/url_loading_system/handling_an_authentication_challenge?language=objc

(React Native itself also doesn't do this correctly atm in RCTNetworking: https://github.com/facebook/react-native/issues/34883)

birdofpreyru commented 5 months ago

Thanks @liamjones , this is a very good point, and at the first glance you might be right. I don't see the iOS code doing anything special with the headers beside just https://github.com/birdofpreyru/react-native-fs/blob/3849493cd23ce8d18bfcb886ec44ff9bb53fa32b/ios/Downloader.mm#L64-L68

And docs for HTTPAdditionalHeaders used there state similarly

image

Thus the handling of headers by iOS downloadFile() implementation should be re-worked.

Pipi144 commented 3 months ago

downloadFile() only works with wifi, i recently encountered this problem! And i tested in different connection mode and be able to reproduce this issue. The hanging only happens when your phone is not connected to wifi

birdofpreyru commented 3 months ago

@Pipi144 can you provide a minimal reproducer of the issue?

Pipi144 commented 3 months ago

@Pipi144 can you provide a minimal reproducer of the issue?

I don't know how can i do it now, because without connecting to wifi i can't debug, it can only see when i deploy to testflight and get the app run without wifi, then the issue will happen! But when i connecting to wifi everything is fine! I was forced to switch back to the original package. I'm sorry, i tried to think of a way reproduce it

birdofpreyru commented 3 months ago

@Pipi144 are you saying you get no error when you deploy the release build directly to a device, and then run without wifi? That would be a nice reproducer — a minimal app that downloads smth and shows a success msg in its UI on completion, and it would only work on wifi, not on on mobile network, when deployed directly to the device.

Pipi144 commented 3 months ago

Yes, my app was able to be deployed to Testflight, then it runs smoothly with wifi, but without wifi it failed! took me 2,3 days to figure out this! Btw did you mean that i should make a small reproduce app with your requirements?

birdofpreyru commented 3 months ago

Yeah, smth that is simple, and I can just run and see how it breaks, without going to troubles of (re-)uploading anything to Testflight to troubleshoot it.

Ericaudran commented 3 months ago

Ho god thanks, this issue does not exist on the "main" repo. I have the exact same issue.

Download randomly doesn't start and just timeout (I set it to 60s). It never goes to "begin". I have 120 files to download, one after the other one. I have only two cases for ALL files to download, I think it may help :

The download never block in the middle or at the second file, I find it very weird, maybe this could help you.

zabojad commented 3 months ago

@Ericaudran could you confirm that the issue happens actually only when your device is not connected to Wifi ?

Ericaudran commented 3 months ago

@zabojad My coworker works only WITH wifi and he doesnt have any 4G. Sometimes switching wifi made it work, but it was not deterministic

birdofpreyru commented 3 months ago

For record,

I've checked that downloadFile() test in the example app https://github.com/birdofpreyru/react-native-fs/blob/2c90ad118025c305c33e79c9e921792998583cb1/example/src/TestBaseMethods.tsx#L331-L358 works just fine for me when the example is build in the release mode, and deployed to real device (iPhone), both on WiFi and Mobile networks.

My best guess at this point, perhaps if the network connection is unstable, and is briefly interrupted during a large file download (which is perhaps way more likely on mobile network) probably the lib does not handle that correctly and just waits indefinitely, rather than failing or re-trying.

zabojad commented 3 months ago

My best guess at this point, perhaps if the network connection is unstable, and is briefly interrupted during a large file download (which is perhaps way more likely on mobile network) probably the lib does not handle that correctly and just waits indefinitely, rather than failing or re-trying.

In my case it was happening with very light pdf files that are downloaded in less than a second.

Download randomly doesn't start and just timeout (I set it to 60s). It never goes to "begin". I have 120 files to download, one after the other one. I have only two cases for ALL files to download, I think it may help :

It download 120files, one after one, properly OR it doesn't start the first one

The download never block in the middle or at the second file, I find it very weird, maybe this could help you.

This is exactly what I was observing... It was either working for all files, or not working for no file at all.

I think the Authorization header hypothesis is more plausible...

birdofpreyru commented 3 months ago

@zabojad I just can't wrap my head around how misuse of Authorization header will cause problems so selective as only in release build on mobile network... I'd expect such thing to either always work or always fail. When something fails so selectively and randomly, that feels like synchronization problems in some logic, exposed by network quality / execution performance in Debug / Release builds, or something like that.

For a record, I checked the error handler seems to work correctly... so still waiting for somebody to come up with a minimal reproducer of the problem.

@Ericaudran

maybe this could help you.

Seeing some code would help. Downloading 120 files on after another still can be implemented differently, some additional download options (like that Auth header) can be used, etc.

Note, I am not using downloadFile() for any of my own projects, and my effort on maintaining this library got zero financial support so far, thus I am not much interested in actively looking for this issue, until somebody makes it very clear what causes the problem, or at least comes with a reliable reproducer that can be easily debugged.

wdjunaidi commented 1 month ago

We are having the same issue, for small files roughly 5kb. It works fine on Android, but on iOS it only works with WiFi.

cristianserban-dometic commented 1 month ago

Having the same issue for two small files

stetbern commented 1 month ago

I think I have found the error. The problem was due to the conversion of boolean values from RN to ObjC. As a result, all boolean values in ObjC were automatically true in the context of downloadFile. Therefore, the download was always started in background mode, with the “discretionary” flag active. “discretionary” allows iOS not to perform downloads in background mode on mobile internet connections or when the battery is low. As a result, some users were also unable to download via Wi-Fi.

The fix is here https://github.com/birdofpreyru/react-native-fs/pull/47

birdofpreyru commented 1 month ago

Presumably, fixed in v2.26.0

Alexa-Green commented 1 week ago

Am still experiencing this even after update to v2.26.0 (and am currently using 2.27.0). Only seems to be happening in release mode. What's strange is that downloading a lot in a row works very quickly (using the same code), but downloading a single file and writing it to disk, the downloadFile is hanging. Progress never begins with the following:

        begin: () => {
          // see https://github.com/itinance/react-native-fs/issues/788
          consoleLog?.('Begin download...');
        },
        background: true,
        discretionary: false,
        progress: res => {
          let progressPercent = (res.bytesWritten / res.contentLength) * 100; // to calculate in percentage
          consoleLog?.('\n\nprogress===', progressPercent);
        },

Any ideas?

birdofpreyru commented 1 week ago

@Alexa-Green can you reproduce the issue within Example App, by modifying the downloadFile() test there (or, perhaps, by modifying the background download test for iOS)? It will help a lot to troubleshoot and solve it!