fix(download): await temp-file unlink + cp() to stop ENOENT race + un…#1019
Open
NadeemIqbal wants to merge 1 commit into
Open
fix(download): await temp-file unlink + cp() to stop ENOENT race + un…#1019NadeemIqbal wants to merge 1 commit into
NadeemIqbal wants to merge 1 commit into
Conversation
…handled rejection `_downloadFile` had two latent bugs that surface together on Android 14 + New Architecture (wonday#1018): 1. The pre-fetch `_unlinkFile(tempCacheFile)` was fire-and-forget, so ReactNativeBlobUtil.config(...).fetch(...) raced the in-flight delete and could open(path) before the unlink completed, surfacing as `ENOENT (No such file or directory)` on the `.pdf.tmp` file. 2. The success path was structured as `.fetch(...).catch(handler).then(...)` and the inner `cp(tempCacheFile, cacheFile).catch(err => { throw err })` was never awaited. Any cp rejection escaped the chain as an "Uncaught (in promise)" instead of flowing through `_onError`, so the `onError` prop was never called. Rewrite `_downloadFile` to use proper async/await throughout: - await the temp-file unlink before starting the fetch (closes the race that triggers ENOENT) - await the fetch task, the stat check, the cacheFile unlink, and the cp into the final cache location, all inside a single try/catch - single failure path that cleans up both files and calls `_onError` exactly once, so unhandled rejections can no longer escape No public API change. The existing `lastRNBFTask` reference is still assigned to the in-flight fetch task before `await`, so the cancellation paths in `setNativeProps` / `componentWillUnmount` continue to work. Closes wonday#1018.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1018.
Summary
_downloadFilehad two latent bugs that surface together on Android 14 + New Architecture. Rewriting the function with properasync/awaitthroughout closes both — no public API change.Root cause
1. ENOENT race on
.pdf.tmpThe pre-fetch cleanup was fire-and-forget:
_unlinkFileisasync, so the deletion oftempCacheFileruns concurrently with the nativeopen(path)insideReactNativeBlobUtil.fetch. On Android 14 + New Architecture the timing flips andopenlands while the file is mid-delete, surfacing as:2. Uncaught rejection bypassing
onErrorThe success path was structured as:
The inner
cp(...).then(...).catch(err => { throw err })is never awaited from the outer.then. Anycprejection re-thrown from the inner.catchbecomes an unhandled promise rejection instead of propagating to the outer.catch→_onErroris never called → theonErrorprop never fires.Fix
Rewrite
_downloadFilewithasync/awaitthroughout — single try/catch covering the whole flow:awaitthe temp-file unlink before starting the fetch (closes the ENOENT race)awaitthe fetch,statcheck,cacheFileunlink, andcpinto the final location, all inside onetrycatchcleans up both temp + final files and calls_onErrorexactly oncethis.lastRNBFTaskis still assigned to the in-flight fetch task beforeawait, so the cancellation paths insetNativeProps/componentWillUnmountstill find a cancellable task.API impact
None. Public component API unchanged. No new dependencies.
Test plan
newArchEnabled=true—<Pdf source={{ uri: '<remote-url>', cache: true }} />on fresh install no longer crashes withENOENTonErrorprop (was: escaped asUncaught (in promise))cancel()still aborts the fetch correctlySyntax verified locally via
node --check index.js.