We have embedded Spidermonkey 102.7 ESR in our product.
We run a pool of JS threads to execute JS code. Each JS thread allocates a single JSContext. We want to cache compiled scripts in memory and we don’t need to persist them across the process runs. Hence, we experimented with a singleton cache object of the type:
std::map<std::string, RefPtr<JS::Stencil>> cache;
When we don’t have a script in the cache we create a stencil with
RefPtr<JS::Stencil> st = JS::CompileGlobalScriptToStencil(_cx, opts, source);
and then create a JS::RootedScript script with
script = JS::InstantiateGlobalStencil(_cx, instantiateOptions, st);
at the same time, we store the stencil in our cache object.
When we have a cached stencil, we take it from the cache and call
JS::InstantiateGlobalStencil(_cx, instantiateOptions, st);
Everything works well. We tested the approach under a heavy load and saw no problems. However, our process crashes during the shutdown. Depending on how we access our JS pool of threads, the crash happens either in
~StringBox at firefox-102.7.0/js/src/vm/SharedImmutableStringsCache.h:265
JSRuntime::destroyRuntime at firefox-102.7.0/js/src/vm/Runtime.cpp:292
The former is in MOZ_RELEASE_ASSERT(refcount == 0) — refcount is 1, the latter is in MOZ_ASSERT(scriptDataTable(lock).empty());
So something, in Spidermonkey code, keeps holding shared objects.
We’ve rewritten our code by adding JS::EncodeStencil and JS::DecodeStencil pair of functions and saving
uint8_t * buffer instead of stencils in our cache.
The process stopped crashing.
Does our initial approach have flaws?
As we don’t need a persistent cache we want to avoid extra steps of encoding and decoding stencil. But maybe it is not possible because of the design of the stencils?