You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
How to send SharedArrayBuffer from main process to Window processes #10409
I am working on an offline renderer for three.js for this project i need to share SharedArrayBuffer between main and window processes. I can create SharedArrayBuffer in main process and window processes but no way to share the reference to other processes, how can it be achieved?
OS : macOS Sierra 10.12.6 (16G29)
Electron : Built from master #cc666c7
👋 Thanks for opening your first issue here! If you're reporting a 🐞 bug, please make sure you include steps to reproduce it. We get a lot of issues on this repo, so please be patient and we will get back to you as soon as we can.
To help make it easier for us to investigate your issue, please follow the contributing guidelines.
Because we treat our issues list as the team's backlog, we close issues that are questions since they don't represent a task needing to be completed. For most questions about Electron there are a lot of options. Check out the Electron community. There are also a bunch of helpful people in this community forum that should be willing to point you in the right direction.
This may come in a bit late, but I did stumble upon this closed issue right here when doing researches on the exact same topic, so hopefully it could help other people looking for an answer.
TL;DR: you can't use SharedArrayBuffer between processes, only between threads. But there are other ways to share memory.
The MDN documentation
So, if we refer to the MDN documentation about SharedArrayBuffer, there's no mention at all about "processes". However, there's a lot of mentions about "threads", and of course "workers" since they are their abstraction in the JavaScript extended ecosystem (I say extended because workers are not a JavaScript thing, they belong to the platform embedding a JavaScript engine). They do mention a "main program", which is to me a very confusing term here:
To share memory using SharedArrayBuffer objects from one agent in the cluster to another (an agent is either the web page's main program or one of its web workers), postMessage and structured cloning is used.
So to clarify this description:
cluster: a single process with multiple threads
agent: a thread within that process
main program: the main thread within that process
web worker: a thread other than the main thread within that process
Now, after checking a bit the ECMAScript specifications, I realize "cluster" and "agent" are terms coming from there, and I may not completely understand those concepts yet. But given this description on MDN and my different tests, the mapping I made to "process" and "thread" should be correct in this context.
The ECMAScript specifications
Now if we look at the ECMAScript standard, first we see only a few mentions of threads, when describing execution Agents. Honestly, I didn't take the time to analyze that so I don't fully understand it.
Now, about the mentions of OS processes (by looking for the word "process" and excluding all of the irrelevant occurrences):
the Data Blocks section describes shared data blocks and does mention the concept of processes
Programs within different agents may share memory by unspecified means. At a minimum, the backing memory for SharedArrayBuffers can be shared among the agents in the cluster.
And regarding mentions of SharedArrayBuffer, I looked for sections which refer to it alone, not along with ArrayBuffer. Indeed, those two abstractions share a lot, they serve as backing buffers for TypedArray and they handle the allocation and deallocation of a buffer in memory. But what imports to us here is this particular aspect of shared buffer, which can't be linked to ArrayBuffer.
It's only when we reach the section about Memory Model that things could start getting interesting, but it's way too long to read right now, and the only thing I retain is: SharedArrayBuffer is meant to share memory between agents.
Unfortunately, as of right now I can't really provide an exact interpretation of these specifications.
How to share memory: using OS native APIs
So, nothing prevents you from writing a Node.js native addon (Node-API preferred) that would allocate and deallocate a block of shared memory. For instance on Windows:
You can load that native addon on the main process side of Electron, and using node integration options in WebPreferences you can load that same native addon on the renderer process and its threads (Web Workers) as well, even though they advise against it.
You would have several instances of your addon loaded, which is a duplication of data, but only for your addon state. The block of shared memory you would allocate, deallocate and pass around using your addon's exposed API would be the same and it would beshared memory.
Now, I didn't mention the access part: reading and writing from that block of memory. You theoretically have two options, but you will see that there's a HUGE limitation with Electron which may bum you.
Option 1: add custom APIs to read and write to the buffer. That's not standard, and only your code will know how to interact with it. Passing the buffer wrapper to 3rd-parties may not work unless they accept adapters.
Option 2: wrap it in an ArrayBuffer, to then be able to wrap with TypedArray and use its standard APIs. That is theoretically possible because the Node-API allows attaching external buffers (understand: buffers you manage on your own) to a new ArrayBuffer instance. But the corresponding documentation section does even tell you explicitly that you won't be able to use it in Electron:
Some runtimes other than Node.js have dropped support for external buffers.
On runtimes other than Node.js this method may return napi_no_external_buffers_allowed to indicate that external buffers are not supported. One such runtime is Electron as described in this issue electron/issues/35801.
Your only hope if you want to go that route is either to have issue #35241 resolved or to build Electron yourself without that mentioned memory cage.
It's unfortunate that Electron resembles more and more to a black box webview, while it's probably the only viable option to build applications a bit more interesting than yet another TODO list or documentation websites wrappers.
Of course, having SharedArrayBuffer directly usable across processes would be the best, and the most logical thing to do. But I don't even know if it would ever be planned.
The route towards using a native addon is more complex, and if you want compatibility with ArrayBuffer/TypedArray then it becomes WAY more complex since you have to build Electron yourself and maintain that build. To be honest I'm still suck at that stage but I have other projects preventing me from exploring this further right now. If your application use case permits it, you can accommodate with just a full custom native API.
Reacted by Michał D., Joona Tiinanen, phadkeSharanMatrixComsec, XTY, Jamie Birch, dhai, Elle Fiorentino-Lange and DF
Regarding the custom build of Electron, I managed to make one, but there are cons:
while electron/build-tools is a precious gem that automates almost everything, it was not complete for my needs (knowing I fully automated the build):
I had to trick to move some cache folders to another location (too big for my main drive), by patching the generated configuration
I had to try multiple times before properly syncing and building everything properly, because of gaps in the documentation/CLI help
I didn't find a way to properly use a custom Electron commit ID or even a custom fork, despite the documentation: so I have to patch the code and re-run a sync command which is quite long no matter what
the whole download took between 30 minutes and 1h with a fair network connection
the whole custom build took 3h on a reasonable computer spec
I am not sure the generated build is stable since changing some build variables may not be enough: the rest of the build, as well as the code itself, must make sure it's compatible with those variables changes
Otherwise the idea is fairly simple:
when downloading the code and preparing the build, all the content goes into one folder
under this folder, entry point build configurations are in src/electron/build/args
depending on how you created your configuration, it could point to release.gn or testing.gn
you need to either change one of those files or create a new one and point to it (update the configuration), which is what I did with a file I named custom.gn
Adapt the import depending on your needs. The important part here is the customization of the V8 options.
With the generated build, I could use the solution I wrote in my previous comment and wrap my instances in ArrayBuffer and that way use all the standards APIs. I still need to see how reliable this build will be on the longer term.
Reacted by Bruce Auyeung, CHENXIAOFENG, 孙运天 and roundsdr
What a crazy quest @ymeine is it still the case nowadays that you have to go through such hoops? Wanted to pass info to tensorflow running on the node process for it to read the canvas context data on the fly instead of copy/send 300kb every 10 frames
I haven't been working on that topic since then, since for me it was part of a "fail fast" check phase. Despite all the difficulties I encountered and the heavy process involved, I considered it to be a success, so I moved on to other parts of my project, independent of that so far.
However, I can safely assume that this hasn't changed: the trend is to always increase security, and if possible performance too. The memory cage aims to do both by using memory offsets instead of memory pointers:
making it impossible to point outside the V8 heap memory (since the offsets are checked against the heap memory boundaries)
cutting the memory usage by half by using 32 bits integers for offsets, instead of the 64 bits pointers
Summary of the process
In order to help, I can only try to summarize better the process here, which I made a bit more generic in order to accommodate broader use cases:
if you want shared memory between a main Node.js compatible process and its workers: use SharedArrayBuffer; stop here
otherwise you want shared memory between different processes with a Node.js compatible context (e.g. main process and renderer process in Electron, or a Node.js process and a Bun process, etc.):
you will have to:
write a Node-API (aka N-API) compliant native addon to allocate/free/wrap named shared memory: in C++ Boost.Interprocess can actually come in handy here
depending on the actual type of process you have, some prerequisites may be needed:
for an Electron renderer processes: enable Node.js integration (nodeIntegration)
for a Deno process: add the permission to load native addons (--allow-ffi)
load your addon in each process requiring shared memory
allocate shared memory with a given name in one process, pass that name to the other processes using any other IPC mechanism you want and have them wrap the shared memory; a clean code would also require one of the processes to free the memory and warn the others to drop their wrappers
if you don't need to access the shared memory using TypedArray based classes or DataView (or Buffer): the wrappers in your addon will have to provide their own read/write methods; stop here
otherwise, you will have to:
recompile Electron, disabling the V8 heap memory cage by setting v8_enable_sandbox = false and v8_enable_pointer_compression = false in a .gnV8 build configuration file
Sorry I haven't turned that into an actual flow chart, I hope this is clear enough.
I didn't detail again things like the recompilation of Electron, the goal was to give a clearer view of the process with the different options depending on the needs.
Also, some steps may vary a bit (e.g. the addon doesn't have to be Node-API compliant if you know you will only use it in a V8 compatible runtime - Deno uses V8, and Bun is adding V8 support), anyone can adapt.
Is this custom Electron build something you've been able to stay current with? Did it ever rise to the level of something worth documenting and/or maybe throwing up onto GitHub?
I'm just trying to figure out whether this is a long-term option for me and my team. Any additional information you can share would be tremendously useful.
Also, sorry anyone if the issues discussion is not the place for this conversation 🤷
Unfortunately I haven’t tried the build process since last time, because I just have too many other things to develop in the meantime. I don’t think the outcome should change though.
For documenting or open sourcing a tool taking care of it, I would always answer “yes it is worth it”, but also “I clearly don’t have time setting up such a thing”.
On the very long term, I do plan on sharing a lot of information, and possibly tools/applications, but it is for now impossible for me due to a lack of resources.
However, if someone were to take care of the setup and maintenance overhead (creating a repository, a wiki, etc.), I would be much more likely to contribute to the documentation content and/or tool implementation, through PRs for instance.
Activity
welcome commentedon Aug 31, 2017
👋 Thanks for opening your first issue here! If you're reporting a 🐞 bug, please make sure you include steps to reproduce it. We get a lot of issues on this repo, so please be patient and we will get back to you as soon as we can.
To help make it easier for us to investigate your issue, please follow the contributing guidelines.
codebytere commentedon Dec 2, 2017
Thanks for reaching out!
Because we treat our issues list as the team's backlog, we close issues that are questions since they don't represent a task needing to be completed. For most questions about Electron there are a lot of options. Check out the Electron community. There are also a bunch of helpful people in this community forum that should be willing to point you in the right direction.
asineth0 commentedon May 4, 2022
Did you ever end up figuring this out?
ymeine commentedon Dec 17, 2023
Hi,
This may come in a bit late, but I did stumble upon this closed issue right here when doing researches on the exact same topic, so hopefully it could help other people looking for an answer.
TL;DR: you can't use
SharedArrayBufferbetween processes, only between threads. But there are other ways to share memory.The MDN documentation
So, if we refer to the MDN documentation about
SharedArrayBuffer, there's no mention at all about "processes". However, there's a lot of mentions about "threads", and of course "workers" since they are their abstraction in the JavaScript extended ecosystem (I say extended because workers are not a JavaScript thing, they belong to the platform embedding a JavaScript engine). They do mention a "main program", which is to me a very confusing term here:So to clarify this description:
Now, after checking a bit the ECMAScript specifications, I realize "cluster" and "agent" are terms coming from there, and I may not completely understand those concepts yet. But given this description on MDN and my different tests, the mapping I made to "process" and "thread" should be correct in this context.
The ECMAScript specifications
Now if we look at the ECMAScript standard, first we see only a few mentions of threads, when describing execution Agents. Honestly, I didn't take the time to analyze that so I don't fully understand it.
Now, about the mentions of OS processes (by looking for the word "process" and excluding all of the irrelevant occurrences):
And regarding mentions of
SharedArrayBuffer, I looked for sections which refer to it alone, not along withArrayBuffer. Indeed, those two abstractions share a lot, they serve as backing buffers forTypedArrayand they handle the allocation and deallocation of a buffer in memory. But what imports to us here is this particular aspect of shared buffer, which can't be linked toArrayBuffer.The dedicated section about
SharedArrayBufferis really about the JavaScript API implementation, so it's not relevant here.It's only when we reach the section about Memory Model that things could start getting interesting, but it's way too long to read right now, and the only thing I retain is:
SharedArrayBufferis meant to share memory between agents.So we are back to the Agents section.
Unfortunately, as of right now I can't really provide an exact interpretation of these specifications.
How to share memory: using OS native APIs
So, nothing prevents you from writing a Node.js native addon (Node-API preferred) that would allocate and deallocate a block of shared memory. For instance on Windows:
You can load that native addon on the main process side of Electron, and using node integration options in WebPreferences you can load that same native addon on the renderer process and its threads (Web Workers) as well, even though they advise against it.
You would have several instances of your addon loaded, which is a duplication of data, but only for your addon state. The block of shared memory you would allocate, deallocate and pass around using your addon's exposed API would be the same and it would be shared memory.
Now, I didn't mention the access part: reading and writing from that block of memory. You theoretically have two options, but you will see that there's a HUGE limitation with Electron which may bum you.
Option 1: add custom APIs to read and write to the buffer. That's not standard, and only your code will know how to interact with it. Passing the buffer wrapper to 3rd-parties may not work unless they accept adapters.
Option 2: wrap it in an
ArrayBuffer, to then be able to wrap withTypedArrayand use its standard APIs. That is theoretically possible because the Node-API allows attaching external buffers (understand: buffers you manage on your own) to a newArrayBufferinstance. But the corresponding documentation section does even tell you explicitly that you won't be able to use it in Electron:Your only hope if you want to go that route is either to have issue #35241 resolved or to build Electron yourself without that mentioned memory cage.
References:
Conclusion
It's unfortunate that Electron resembles more and more to a black box webview, while it's probably the only viable option to build applications a bit more interesting than yet another TODO list or documentation websites wrappers.
Of course, having
SharedArrayBufferdirectly usable across processes would be the best, and the most logical thing to do. But I don't even know if it would ever be planned.The route towards using a native addon is more complex, and if you want compatibility with
ArrayBuffer/TypedArraythen it becomes WAY more complex since you have to build Electron yourself and maintain that build. To be honest I'm still suck at that stage but I have other projects preventing me from exploring this further right now. If your application use case permits it, you can accommodate with just a full custom native API.ymeine commentedon Dec 27, 2023
Regarding the custom build of Electron, I managed to make one, but there are cons:
synccommand which is quite long no matter whatOtherwise the idea is fairly simple:
src/electron/build/argsrelease.gnortesting.gncustom.gnThe content of
custom.gnis:Adapt the import depending on your needs. The important part here is the customization of the V8 options.
With the generated build, I could use the solution I wrote in my previous comment and wrap my instances in
ArrayBufferand that way use all the standards APIs. I still need to see how reliable this build will be on the longer term.Elyx0 commentedon Oct 16, 2024
What a crazy quest @ymeine is it still the case nowadays that you have to go through such hoops? Wanted to pass info to tensorflow running on the node process for it to read the canvas context data on the fly instead of copy/send 300kb every 10 frames
ymeine commentedon Oct 16, 2024
Hi,
I haven't been working on that topic since then, since for me it was part of a "fail fast" check phase. Despite all the difficulties I encountered and the heavy process involved, I considered it to be a success, so I moved on to other parts of my project, independent of that so far.
However, I can safely assume that this hasn't changed: the trend is to always increase security, and if possible performance too. The memory cage aims to do both by using memory offsets instead of memory pointers:
Summary of the process
In order to help, I can only try to summarize better the process here, which I made a bit more generic in order to accommodate broader use cases:
SharedArrayBuffer; stop herenodeIntegration)--allow-ffi)TypedArraybased classes orDataView(orBuffer): the wrappers in your addon will have to provide their own read/write methods; stop herev8_enable_sandbox = falseandv8_enable_pointer_compression = falsein a.gnV8 build configuration fileArrayBufferinstance (napi_create_external_arraybuffer/Napi::ArrayBuffer::New(napi_env env, void* externalData, size_t byteLength);/env.create_arraybuffer_with_borrowed_data/ Zig'snapi_create_external_arraybuffer); allocating and freeing memory are still needed separatelySorry I haven't turned that into an actual flow chart, I hope this is clear enough.
I didn't detail again things like the recompilation of Electron, the goal was to give a clearer view of the process with the different options depending on the needs.
Also, some steps may vary a bit (e.g. the addon doesn't have to be Node-API compliant if you know you will only use it in a V8 compatible runtime - Deno uses V8, and Bun is adding V8 support), anyone can adapt.
roundsdr commentedon Nov 15, 2024
@ymeine,
Is this custom Electron build something you've been able to stay current with? Did it ever rise to the level of something worth documenting and/or maybe throwing up onto GitHub?
I'm just trying to figure out whether this is a long-term option for me and my team. Any additional information you can share would be tremendously useful.
Also, sorry anyone if the issues discussion is not the place for this conversation 🤷
ymeine commentedon Jan 10, 2025
@roundsdr
Hi,
Unfortunately I haven’t tried the build process since last time, because I just have too many other things to develop in the meantime. I don’t think the outcome should change though.
For documenting or open sourcing a tool taking care of it, I would always answer “yes it is worth it”, but also “I clearly don’t have time setting up such a thing”.
On the very long term, I do plan on sharing a lot of information, and possibly tools/applications, but it is for now impossible for me due to a lack of resources.
However, if someone were to take care of the setup and maintenance overhead (creating a repository, a wiki, etc.), I would be much more likely to contribute to the documentation content and/or tool implementation, through PRs for instance.