New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Better support for --js-flags and other CLI arguments in a packaged app #22705
Comments
It's probably not a great idea to do a lot of heavy work in the main process, as it's involved in coordinating a lot of things and all JS in the main process currently runs on the UI thread. You mention use of child processes—would moving this work to a child process with the heap size increased be possible for your use case? Consider also the recently-merged MessagePort functionality, which supports direct IPC between renderers. |
Thanks very much for the response @nornagon!
It's definitely not great, but in our case the extra cost of serialization here between another potential child process hop is high and we've built a custom scheduler that yields frequently back to renderers to keep the UI responsive. (In addition to the engineering challenges with moving this into child processes below)
Possible, yes, but just a lot of extra engineering. In our experience, working with node_modules has been much, much smoother in the electron-node land compared to custom node child processes (asar path inconsistencies, native module recompilation, etc). If there's something we're missing here that makes headless electron child processes that function like node with custom memory limits much easier, I'm all ears! :) EDIT: To add on to whether it's possible. While I believe it to be possible, I'm still hesitant that the serialization and transient coordination in the main process with many long-lived and concurrent jobs could reach the 2GB limit and still lead to OOMs, so even in this scenario I'd still like to be able to increase the heap size there. |
I've seen several apps use hidden |
Ah, yes! That's a decent option with MessagePort now. It would require nodeIntegration and disabled sandbox in the BrowserWindow but that's basically the security model we'd be getting in a node child process anyhow. Am I right in assuming that there is not a significant performance difference between utilizing node fs APIs and working with buffers in a BrowserWindow vs. the main process? Up until now we've used a single BrowserWindow just for UI, so I'm a little fuzzy on the thread model here for multiple windows. It was surprising to us when we first discovered that the work in the main process blocks the main UI thread of the renderer process. If we're launching another "renderer" process as a worker would its main thread be tied together with with the other renderer and the main process? If all that's true and we've established a workaround for my specific situation, just curious for your thoughts on a mechanism for passing CLI arguments in packaged apps. Many of the other linked issues have different use cases that probably wouldn't be solved by this approach that works for me. |
Thanks very much for your guidance here @nornagon! It's been immensely helpful already :) |
I'm not aware of any performance differences after process startup. It's unclear to me whether it's faster to spin up a renderer process or a node child process. But once the process is started, they should have similar performance characteristics. I've heard some rumblings about suggestions for "blessing" the idea of using a hidden BrowserWindow as a worker process by creating a new kind of child process that's somewhere between a node child process and a renderer. TBD whether that will ever become a thing, but if it does, then I would expect
The main process only has one UI thread. This is true regardless of how many windows are open.
In general I think that it's preferable to support as much as we can via JS, rather than by static configuration. In production scenarios, it's often the case that flags should be applied only in certain situations, for instance, depending on the specific OS version. Many flags are possible to set this way; JS flags in the main process is one of the few exceptions. It might be worth exploring a way to enable this for |
@patrickhulce We spawn a hidden BrowserWindow to handle our 'non-ui' tasks. VSCode uses this method as well. We use a simple binary protocol to communicate between the UI and the hidden BrowserWindow to minimise serialisation / deserialisation costs. Using named pipes we're able to communicate directly, however we found problems with Windows sometimes not connecting properly so we switched to using the ipcRenderer.sendTo method. |
I think that's a great idea 👍 When first starting out in Electron I had a hard time figuring out how to actually get work totally off the UI thread and bundling a node binary along with Electron led to a lot of issues later with notarization, publishing, etc. Thanks for the extra validation @Mike-Dax ! We'll probably go that route as well then. |
For another use case, our app has to detect if it was launched with I guess these aren't technically Electron CLI arguments, but it's something to think about that is in the spirit of this issue. |
Frankly, I'd like this to be a little easier to do in order to reduce max heap size; Electron chat apps like Slack and Teams are infamous for using up a lot more RAM than they need to because the default V8 heap size is somewhere around 1.5GB and it doesn't collect in the old space until it's full, so those apps just baloon up until they fill it (needlessly). I can run Slack and Teams happily with --max-old-space-size=256 (which doesn't percolate down to their helper apps, which also appear to be Electron, which then balloon up to 1.5 GB), but passing those flags to the app requires executing from the terminal, which most Slack users aren't going to do (for the record, on the Mac: |
Would also love this feature request gets accepted and implemented. I need to store some data in memory in the main process and time to time this data gets written to the file system. I prefer not to keep the data in the renderer process due to the extra serialization and increased code complexity. Normally the data doesn't hit the default 2GB limit but sometimes users have to pass |
@patrickhulce, I use hacky workaround for now, embedding the CLI args into the desktop shortcuts (primary change + minor fix) but I didn't find yet how to apply it for macOS/DMG package too. |
Another request for being able to bundle in commandline args to built electron apps. I have a webGL based 3d medical image renderer that is struggling to allocate enough memory for some large image sets. We were hoping we'd have the control in Electron to do so, but... |
BrowserWindow has an option |
@fridgerator, the issue is about applying the |
@vladimiry You're right, I was mistaken. |
Any news in Electron v11 on how to add more memory capacity? Our app crashes after feeding a large dataset. (~2.1GB of PDF files) |
You might be running into the same problem I am, unable to allocate a single array object at basically that size. I think it's a hard limit in chromium |
How can I enable experimental web assembly features in Node or Chrome? |
I am trying to pass an argument when starting the app image on linux from terminal, down into the electron main, but it's not showing in process.argv. I would suppose this ticket is requesting similar support. |
Ignore my comment. It is working as expected, had to send explicitly process.argv for the library for parsing it (command-line-args). |
Another use-case in support of the proposed enhancement: So I try to package an electron AppImage and send it to my customer's mysterious centos7 server and when they run it hangs with:
So everywhere on google says something about increasing the address space (but no docs on how to?) or the virtual memory, which in part can be done by increasing the volatile memory I think via the
But then that has seemingly no effect and you read this:
So am I completely blocked from deploying a packaged electron app to environments that have insufficient virtual memory allocated to the main process? Seems like a great use case to enable support for js-flags if so. |
For AppImage case it's actually easily solvable, simply adjust your build pipeline by modifying the AppRun sh script located in the AppImage file container. |
@vladimiry wow, that sounds awesome thank you. Can you please explain just a tad bit more? I am using electron builder and so the actual pipeline is abstracted away. I am simply setting some high level config in my package.json and it does the rest. How could I intercept it and where would your suggested changes go? |
A few notes on this:
Aside from all this though, changing the maximum V8 heap size to a constant is probably not what you want to do.
There are a few other V8 options which an app might plausibly want to set in the main process, but most of them are quite marginal, & by far the most common request we've seen is to change the max heap size. |
Hi. I have a question. |
After searching around for a decent solution, I think that pointer compression causes it. Now we can compile Electron without pointer compression by ourselves or trouble the team to provide it or downgrade it (< v9.0.0). |
@heylying if the cause truly is pointer compression, that was added in V8 v.8.0, which was added to Electron with Electron v.8.0.0. That would mean the latest version you could downgrade to would be Electron 7.3.3. But this line of thinking does not quite jive with when the more severe Electron memory limitations were introduced with Electron 12 and then worsened in Electron 14 as per my findings detailed in #31330. |
Electron disabled it in v8 and enabled it in v9. |
Preflight Checklist
Problem Description
Packaged apps need a lower-touch method of leveraging
--js-flags
and other electron CLI options that can only be passed in as arguments currently.Specific use case
--max-old-space-size
to increase V8 heap size of the main process in production.Additional Context
Our company builds a photo processing application that moves as much analysis out of the renderer process as possible (IPC to main process for all non-renderer work and heavy utilization of node child processes). We can avoid OOMs in the renderer with
app.commandLine.appendSwitch('js-flags', '--max-old-space-size=XXXX');
but that does us no good for the main process that's already running. This creates a perverse incentive to disable sandboxing and force more logic into the renderer process. Similarlyv8.setFlagsFromString
(and the electron built-inv8Flags
package.json property) does no good because the isolate has already been created and--max-old-space-size
is a noop once the isolate has already been created.Importance
This is a very common userland request that is frequently misunderstood and dismissed by userland maintainers as technically infeasible. Furthermore, all of the common requests for how to increase memory limit in electron core respond with "use --js-flags" but no mention of how to make this actually work in production.
Additionally, lowering the barrier to experimenting with new flags in packaged apps could reduce the burden of transitioning features like sandbox.
Proposed Solution
Electron reads additional flags from a known filename pattern (
electron.opts
perhaps? naming very much open to suggestion) on startup that can be processed before the v8 isolate is created/other initialization occurs that render setting flags from within the main process "too little too late".While on first blush this seemed potentially insecure to me, if the user can already pass these exact flags to the application, a malicious user with filesystem access could already modify the shortcut/plist to the same effect.
Relevant Change Locations
Not entirely sure, maybe here?
electron/shell/browser/javascript_environment.cc
Lines 48 to 54 in 19314d3
or here?
electron/shell/app/node_main.cc
Lines 102 to 141 in 19314d3
Alternatives Considered
These are the only two current solutions I'm aware of. If there's a way to achieve what I'm looking for that I missed, I'd love to be wrong here :)
Resourcing
I'm willing to work on this if there's consensus on a solution and someone willing to point me in the right direction. I used to work on Chromium and am familiar enough with the electron architecture to get this far.
Related Issues
The text was updated successfully, but these errors were encountered: