hermes
hermes copied to clipboard
Hermes object property limit causes problems with real use cases
Problem
In development I want to load my source maps into my react-native app so I can parse and pretty print stacks with more useful locations. This works fine on JSC or V8 but cannot be done on hermes due to RangeError: Property storage exceeds 196607 properties
.
Is the properly limit an important hard and fast requirement for hermes goals? Could it be lifted higher or lifted only in development mode?
Solution
Raise the hard limit on object properties so that large data structures such as source maps are usable with hermes.
Additional Context
@tmikov this seems like it would be in your wheelhouse given your input on https://github.com/facebook/hermes/issues/139
As @tmikov has already pointed out in those discussions, that isn't how object properties are intended to be used. Using a Map
seems like the best option. I have a couple of follow up questions though.
- Why are you re-creating this source map in an object in the first place? To me, it seems unlikely that you would need to be using the source map yourself manually. Are you correctly passing in the source map to Hermes? Are you not getting locations resolved automatically in the workflow you're using?
- How are you creating this source map object?
JSON.parse
?
I'm consuming the maps output by metro with https://github.com/mozilla/source-map which I think is pretty much the standard source map consumer (v0.6.1 since later verisons use wasm which is unsupported on react-native).
I'm using it in conjunction with https://github.com/xpl/stacktracey and https://github.com/xpl/ololog to get really nice pretty-printed stacks and location tags for log output. This workflow is much nicer than the standard react-native logging + yellowbox. It works fine on JSC or v8, it's only hermes that fails due to the arbitrary limit.
It looks like metro (react-native bundler) actually uses mozilla/source-map
v0.5.6. I'm guessing that metro would probably crash if run on hermes due to this issue.
@evelant You can recompile Hermes with larger GC segment size, each higher size gives you double the properties. IIRC, 4MB is around 200K props. You could increase it to 32MB (or even more), which should work fine on desktop or 64-bit devices.
Thanks for the tip @tmikov, unfortunately there doesn't yet seem to be a simple way to build hermes from source as part of a react-native build.
This is a development mode only issue for me so it doesn't really have a huge impact although the dev experience was a lot nicer being able to leverage source maps from inside the app. React-native dev tooling is pretty painful in general (flipper is massively buggy, android crashes a lot, source maps don't work well, metro doesn't support symlinks, dependent upon millisecond clock sync between host and device, debugger usually doesn't work, docs are misleading and outdated, and so on) so anything to ease that a bit is pretty valuable at least to me.
I agree in non-dev situations it probably doesn't make sense to be building objects with > 200k properties, but for development mode in react-native it can be useful as I outlined above.
My team is running into this elsewhere now with react-native's built in component hot reload support. I guess react-native maintains some sort of internal map when hot reloading components and it can exceed this property limit and cause a crash.
Can you provide a repro with the RN crash?
@jpporto I can't provide my project where this is happening but I'll see if I can make a reproduction.
To add another real world example, where 3rd party code is internally using a plain object to track unique keys even though our own code is only using arrays.
For context our app handles large amounts of data, with multiple record types and some record types having ~300k records for certain customers.
import { uniqBy } from "lodash";
var largeArray = Array.from({ length: 200000 }).map(
() => Math.random(),
(item) => item
);
var result = uniqBy(largeArray);
globalThis.console?.log ? console.log(result) : print(result);
(bundled with npx esbuild --bundle in.js --outfile=out.js --target=es5
for testing and ran with the hermes cli)
This works fine in other JS engines but crashes in Hermes. I get that certain tradeoffs were made with this 4MB heap segment size but given React Native is pushing for Hermes to be the default it leaves us in a frustrating position where there's no easy path forward without rewriting large parts of our app, and then having to replace or patch a number of third party dependencies.
Of course one alternative is us maintaining custom Hermes builds with a larger segment size but we'd prefer to keep React Native as standard as possible to avoid future upgrade pain.
Just wanted to highlight that it's not always as simple as 'fix your code'. Sure we can do that and use Map
instead of plain objects wherever necessary, but external dependencies are the hidden part of the iceberg here.
I see this every morning when I come back to my simulator that was open all night, so it's only in the dev environment (iOS).
Update: this issue will be addressed by the next major release of Hermes.
@tmikov So what is the approximate time window of new release? Is it going to be this year?
I just want to add credence to the idea that sometimes there are real-world use cases that are impacted by this:
Our team is running into this issue because of our usage of URQL's graphcache. When we go to write the cache to the disk, it provides us the cache as an object and we often have more than 196k entities in the graphQL cache so we hit this limit.
With JSI being so common now in react-native libraries, using JavaScriptCore sucks because it means we can't run the app in a debugger anymore due to JSI calls. I really don't want to have to switch back to JSC but this deviation from the behavior of every other common JS runtime might force us to.
Update: this issue will be addressed by the next major release of Hermes.
Apologies for the semi-ignorance but which version of react-native are you targeting? My app uses 0.72.5 and runs into this issue on a regular basis. I leave some background tasks running that do REST calls to the backend and usually after a day of leaving them running this issue starts to appear.
@ArindamRayMukherjee Hermes development is independent from RN releases. We are not targeting any specific RN release. RN takes the latest version of Hermes when they cut a new release.
is there any chance this gets fixed any time soon @tmikov ? bumping for visibility
@seavan it is on our radar and will be addressed before the release of Static Hermes, meaning in 2024, but I can't really be more specific than that unfortunately.
We can't transfer images over the react-native-webview
bridge due to this issue unless we stringify them. Really frustrating and performance limiting.
Update: this issue will be addressed by the next major release of Hermes.
@tmikov is there any update on this?
@Nantris I don't quite follow. Can you please explain the use case?
@tmikov we need to send a buffer of an image over the bridge but it fails. Right now the only option seems to be to convert it to base64 or some other string, send it across, and then convert it back to a buffer, which is a real pain and performance killer.
Is there any update on what version of Hermes will resolve this issue?
@Nantris what issue? You haven't explained what the problem is or how it relates to the property limit. I don't see what sending an image over the bridge has to do with the limit on total of number of properties in an object.
A large enough buffer transferred across the bridge suffers the same error mentioned above (probably because all messages across the bridge undergo JSON.stringify
, at least in our case).
Is this still planned to be fixed in Hermes itself? Or is that no longer on the roadmap?
@Nantris I cannot answer your question before I understand exactly what problem you are having. I don't understand what you mean by "large buffer" or what it has to do with JSON.stringify(). I need a precise description so I can attempt to diagnose the problem.
Let's start here: are you creating an object with more than 200,000 properties and why? Have you considered using an array instead?
@tmikov you surely meant "have you considered Map" instead! yes we considered! but there are some libraries which rely on default behaviour of JavaScript Object as a hash, with no hard limit for amount of properties. this is important!
@seavan the problem that @Nantris seemed to drscribe was sending "an image across the RN bridge".
Without understanding the details of that, I can't really help, but two things stand out:
- Images probably shouldn't be transmitted as JS objects with thousands of properties.
- Serializing objects with hundreds of thousands of properties across the RN bridge will cause performance problems.
For the record, increasing the number of objects properties is definitely still on the roadmap for 2024.