fix: lock the remaining process-global state shared across isolates
The caches #2020 named (Console timers, ArgConverter, MetadataNode /
MetadataReader) were already covered by #2013: Console and ArgConverter moved to
per-runtime RuntimeState, MetadataNode got s_nodeCacheMutex, MetadataReader its
own StateMutex, and JEnv / MethodCache a shared_mutex each. Auditing the runtime
for what actually remained in that class turned up four more.
File::Buffer was a single process-wide 1MB scratch buffer that ReadText filled
and returned a pointer into. Main and worker runtimes read modules concurrently
from their own threads, so one thread's fread overwrote bytes another was still
copying out - silent module-source corruption rather than a crash, which is why
it never showed up in a tombstone. The buffer also never saved the allocation it
appears to save: every caller goes through the std::string overload, which
copies out of it on the next line. Reads now go straight into the returned
string, and the borrowing overload, which had no callers, is gone.
JType::EnsureInstance published *instance before Init had filled clazz, ctor and
valueMethodId, so a second thread could find the pointer non-null and call
through uninitialised JNI ids - on the hot boxing path.
CallbackHandlers::Init runs once per runtime from PrepareV8Runtime, so every
worker start rewrote the process-global class and method-id statics, and re-ran
MethodCache::Init, while the isolates already running were reading them.
MetadataNode::IsJavascriptKeyword filled a function-local static set behind an
empty() check, racing reads from every runtime's thread.
Also adds the missing ftell < 0 guard on the read path.
Verified: arm64-v8a native build, full test suite 1038/1038.