electron: The fs API doesn't work in a renderer process unless the BrowserWindow has been reloaded once

Preflight Checklist

  • I have read the Contributing Guidelines for this project.
  • I agree to follow the Code of Conduct that this project adheres to.
  • I have searched the issue tracker for an issue that matches the one I want to file, without success.

Issue Details

  • Electron Version: 5.0.8
  • Operating System: Windows 10 (1809)
  • Last Known Working Electron version: 3.1.12

Expected Behavior

Calling the asynchronous fs APIs should work in the renderer process in the same way that it works in the main process.

Actual Behavior

Calls to the asynchronous fs APIs in the renderer process will randomly fail. No exception is thrown, rather, it seems like the callback is never called when using the old asynchronous API and the awaited promise will never complete when using the newer fs.promises API. The synchronous API seems to work consistently.

I have observed this error with code like:

const fs = require("fs");
// Sometimes this callback will never be called
fs.stat("C:\\", () => {});
// Sometimes this will await forever
await fs.promises.stat("C:\\");
// This always seems to work
fs.statSync("C:\\");

While I use stat in the example above, I’ve also observed it with readFile and writeFile. I’ve also observed the issue on a variety of paths, not just “C:\”. The issue seems to go away if I refresh the browser window. So I’ve refactored my application in the short term to refresh the browser window after initial load. I’ve added a branch to renderer.js similar to:

const electron = require("electron");
if (electron.remote.getCurrentWindow().hasReloaded) {
  // Run renderer logic
} else {
  electron.ipcRenderer.send("needs-reload");
}

Then in the main process I do:

const electron = require("electron");
let browserWindow;
electron.ipcMain.on(
  "needs-reload",
  () => {
    browserWindow.hasReloaded = true;
    browserWindow.reload();
  }
);
// Run main logic, including creating browserWindow.

To Reproduce

See my notes above, let me know if you’d like more information.

Additional Information

It looks like this issue in nodejs is also observing the problem https://github.com/nodejs/node/issues/14889.

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 15
  • Comments: 38 (5 by maintainers)

Commits related to this issue

Most upvoted comments

I’m gonna go ahead and close this out - if there are new issues please open them in a new issue!

This issue has been driving me crazy after upgrading my app from Electron 3.0.14 to 6.0.12, and I’ve been trying unsuccessfully for days to get a minimal reproduction. It is not limited to dev mode, it also shows in the final packaged app.

However, after reading the thread and doing some experiments, it definitely seems like a BrowserWindow.loadURL race condition / timing issue.

The issue reliably appears on my dev machine with the following code in main.ts:

mainWindow.loadURL('http://localhost:1212/');

and reliably disappears when changing the line to the following, or after reloading:

setTimeout(() => {
  mainWindow.loadURL('http://localhost:1212/');
}, 100);

Environment: Electron 6.0.12, Typescript, Windows 10

This is a problem for me to running on electron 9.1.0

Using Visual Studio code and Windows 10

setTimeout() fixed the problem for me.

So glad this issue was here, without it I probably would have just given up on 20+ hours worth of app coding.

I recently ran into this issue while using Spectron with Electron(v11.2.1). Our application loaded fine on its own but had this(or a similar) issue when loaded with Spectron. It was odd because everything had been working fine and nothing in our codebase had changed(i.e. literally 0 changes).

I tried upgrading to v12.0.10 but had no luck. However, I noticed there was an update to malware definitions in Windows defender.

What ended up fixing it for us was turning off Real-time protection in Windows Defender. Hopefully, this is helpful to someone.

@codebytere Could you please explain why you’re closing this issue?

As far as I can say, the issue is not fixed and your proposed fix is not related to the initial issue at all.

None of the suggested workarounds worked for me:

  • Increasing UV_THREADPOOL_SIZE to 16
  • setTimeout(() => { win.loadURL(process.env.WEBPACK_DEV_SERVER_URL) }, 100);
  • Reloading manually

I am testing with fs.promises.lstat. Starting from the second invocation, the promise does not resolve. This reproduces reliably. Tested with Electron 9.0.0. Same behavior with Electron 10.1.0.

What does work for me is to use node APIs only in the main process. I use the messaging mechanism to invoke node APIs from the renderer process:

// main process
ipcMain.handle('lstat', async (event, filename) => await fs.promises.lstat(filename));

// preload.js
window.lstat= function(filename) { return ipcRenderer.invoke('lstat', filename); };

// renderer process
let st = await window.lstat(filename);

This is more work, but it is well worth the trouble. It works reliably and without workarounds.

The Electron documentation advertises the use of Node.js APIs in the renderer process:

Electron exposes full access to Node.js both in the main and the renderer process.

According to this issue, at least the fs API has been broken for at least a year. How about changing the documentation to say that the recommended, reliable practice is to call Node.js APIs (or at least the fs API, if the issue is limited to that) only from the main process, and to expose APIs to the renderer process as shown above? This is recommended for security reasons, anyways, right? Maybe we could even deprecate the use of Node.js APIs from the renderer process altogether?

What do you think?

Issue persists with Electron 8.2.2 on Windows 10 with electron-forge @Doccrazy 's timeout trick solved it for me.

Some additional observations for the devs:

app.allowRendererProcessReuse = (bool);
  • true = the renderer readFile callback fires after window unload, seen by using console.log in the callback, Preserve log in the console, and reloading the app.
  • false = the renderer readFile callback requires an initial reload to work at all.
setTimeout(() => {
    mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY);
}, 0);

Calling loadURL on the very next tick results in expected callback behavior, no matter what allowRendererProcessReuse is set to. Race condition indeed.

The resulting executables in this Electron version work without the timeout trick at all.

Occurs also in Electron v14.0.0. Hangs indefinitely on fs.promises.readFile until the page is reloaded. Occurs randomly but very often, about 50% of the time.

Yeah, we also experienced issues when using Axios. The request just disappears.

But I’M NOT AbLE TO reprODucE IT oN any PLaTfOrM, And HAVE NoT SEen A rePro wHIch HAs ALLOwed mE TO Do so.

¯_(ツ)_/¯

The whole point of this issue is that it’s notoriously difficult to reproduce and the cause cannot easily be isolated.