Description of behaviour
I have a React application which has a component where the user can upload multiple CSV files. The following happens on the client side:
- Redux
dispatch
is used to trigger an async thunk action. Inside that action:- An additional nested
dispatch
is used to trigger a reducer function which puts all files in the Redux state, so that they are visible in the UI. - Files are then processed sequentially. Using a while loop until we have no more files, the following happens:
- The Papa parse library's
parse
function processes the file inside a Promise:- As part of the
step
callback, the CSV data is read into an array of objects. Validation ensures that the CSV data cannot result in this array exceeding a limit of 1,000 elements. - As part of the
complete
callback, this array of 1,000 elements is mapped so that each array element translates to an HTTPPUT
request to a back-end web service, which are then dispatched concurrently via the JavaScriptfetch
function and a wrappingPromise.all
on which we await. - Once the
complete
function is finished, the outer Promise that wrapped theparse
function is resolved.
- As part of the
- Finally, a second nested
dispatch
call is triggered to store the result of the upload in Redux state (a simple status, with a list of errors). This ensures the UI can see the result of the individual file upload as soon as it's available. At this point, we no longer need to remember the file content itself, so this is also removed from the state.
- The Papa parse library's
- An additional nested
Load Requirements
There is a requirement for users to upload possibly up to 100 files at a time through this component. Assuming the worst case, each file will translate to 1,000 fetch
requests to the back-end.
I have load tested my code to these requirements and observed the following setups have no issues:
Chrome + starting up the application locally on my machine, with a mock back-end.
Firefox + deploying the application to the testing environment (so it's talking to the real back-end WS).
In these setups, all files are uploaded, and the UI remains fairly responsive whilst the browser gradually churns through the network requests (as long as the network tab of Developer Tools is not left open!)
The Problem
However, when using the following setup:
- Chrome + deploying the application to the testing environment (so it's talking to the real back-end WS).
The following behaviour is observed:
- The first 16 files are processed with no problems.
- The 17th file seems to get through approximately a third of the requests (oddly it is almost always exactly at the 381st request where the problems begin - but it can't be a file-specific problem, because all 100 files in the test have identical content), and then the
fetch
function seems to error all of the remaining requests with a rather unhelpful generic message:
TypeError: Failed to fetch
However, when looking in the Developer Tools network tab, there is no evidence that any requests have failed, which leads me to believe the requests are not even "getting out of the door".
Once that 17th file has finished processing, all of the React components in my application will then disappear, as if the page has just gone blank.
When trying to refresh the browser tab, I then see an error:
Debugging connection was closed. Reason: Render process gone
If I then reload the page I can start the test all over again, and it will fail in almost exactly the same place.
It does seem to be a browser-specific problem because Firefox doesn't have this issue. However, I cannot explain why the issue is not reproducible when the React app is running on my local machine instead of deployed to our testing environment.
I suspected the issue was perhaps some kind of memory leak, but I also wondered if I could be exhausting Chrome's network cache (I tried setting the fetch
cache setting to no-store
but it didn't seem to make a difference). Or perhaps there is some hard limit to the total number of network requests a Chrome tab can make? However, none of these causes would explain why it works locally, so I'm feeling pretty stumped. Any advice or insight would be greatly appreciated!