diff --git a/lib/internal/bootstrap/realm.js b/lib/internal/bootstrap/realm.js index 2ccceb493e68..1b47027fd63b 100644 --- a/lib/internal/bootstrap/realm.js +++ b/lib/internal/bootstrap/realm.js @@ -88,6 +88,10 @@ ObjectDefineProperty(process, 'moduleLoadList', { // more, we just implement them as legacy wrappers instead. See the // legacyWrapperList. const processBindingAllowList = new SafeSet([ + // Qualia: node-fibers (fibers_sync.js setupAsyncHacks) saves/restores the async id stack across + // fiber switches through process.binding('async_wrap'). Upstream dropped it from this list; without + // it the hack silently disables itself and the stack corrupts on the first yield. + 'async_wrap', 'buffer', 'cares_wrap', 'config', diff --git a/lib/internal/main/watch_mode.js b/lib/internal/main/watch_mode.js index e864dfad1b84..0b3d792cdeb5 100644 --- a/lib/internal/main/watch_mode.js +++ b/lib/internal/main/watch_mode.js @@ -178,6 +178,11 @@ async function restart(child, trigger) { process.stdout.write(`${blue}Change detected in ${inspect(trigger)}${white}\n`); } process.stdout.write(`${green}Restarting ${kCommandStr}${white}\n`); + if (process.send) { + // A parent process (e.g. meteor-lite dev-run) uses this to know the child is going down + // before it starts seeing the new child's watch:require / watch:import messages. + process.send({ 'watch:restarting': {} }); + } await stop(child); return start(); } finally { diff --git a/src/crypto/crypto_context.cc b/src/crypto/crypto_context.cc index af8d52fbe699..eb07bba8e605 100644 --- a/src/crypto/crypto_context.cc +++ b/src/crypto/crypto_context.cc @@ -28,6 +28,7 @@ #endif #include +#include namespace node { @@ -92,16 +93,38 @@ struct X509Less { }; using X509Set = std::set; -// Per-thread root cert store. See NewRootCertStore() on what it contains. -static thread_local X509_STORE* root_cert_store = nullptr; +// Process-wide root cert store. See NewRootCertStore() on what it contains. +// +// Upstream keeps the store in thread_local storage (one per worker thread, +// freed by that thread's Environment cleanup hook). Qualia runs Meteor on +// node-fibers with thread-backed coroutines (CORO_PTHREAD), so the JavaScript +// of a single Environment executes on many OS threads, one per fiber. With +// thread_local storage every fiber thread that first performs a TLS handshake +// parses its own copy of the root store and registers the same +// (callback, argument) cleanup hook on the shared Environment again, which +// trips the CHECK in CleanupQueue::Add. The store is therefore process-wide, +// as it was through node 18, guarded by a mutex; the cleanup hook is +// registered once per Environment and the store is freed when the last +// Environment that used it is torn down. +static Mutex root_cert_store_mutex; +static X509_STORE* root_cert_store = nullptr; // If the user calls tls.setDefaultCACertificates() this will be used // to hold the user-provided certificates, the root_cert_store and any new // copy generated by NewRootCertStore() will then contain the certificates // from this set. -static thread_local std::unique_ptr root_certs_from_users; -static thread_local bool has_cleanup_hook = false; +static std::unique_ptr root_certs_from_users; +static std::unordered_set root_cert_store_envs; + +static X509_STORE* NewRootCertStoreLocked(Environment* env); + +static void CleanupRootCertStore(void* arg) { + Mutex::ScopedLock lock(root_cert_store_mutex); + root_cert_store_envs.erase(static_cast(arg)); + if (!root_cert_store_envs.empty()) { + // Another Environment still shares the store. + return; + } -static void CleanupRootCertStore(void*) { if (root_cert_store != nullptr) { X509_STORE_free(root_cert_store); root_cert_store = nullptr; @@ -113,25 +136,27 @@ static void CleanupRootCertStore(void*) { } root_certs_from_users.reset(); } - - has_cleanup_hook = false; } static void EnsureRootCertStoreCleanupHook(Environment* env) { - if (env == nullptr || has_cleanup_hook) { + if (env == nullptr) { return; } - env->AddCleanupHook(CleanupRootCertStore, nullptr); - has_cleanup_hook = true; + Mutex::ScopedLock lock(root_cert_store_mutex); + if (!root_cert_store_envs.insert(env).second) { + return; + } + env->AddCleanupHook(CleanupRootCertStore, env); } X509_STORE* GetOrCreateRootCertStore(Environment* env) { EnsureRootCertStoreCleanupHook(env); + Mutex::ScopedLock lock(root_cert_store_mutex); if (root_cert_store != nullptr) { return root_cert_store; } - root_cert_store = NewRootCertStore(env); + root_cert_store = NewRootCertStoreLocked(env); return root_cert_store; } @@ -1061,6 +1086,12 @@ void StartLoadingCertificatesOffThread( // the certificates provided by users. // TODO(joyeecheung): maybe these rules need a bit of consolidation? X509_STORE* NewRootCertStore(Environment* env) { + Mutex::ScopedLock lock(root_cert_store_mutex); + return NewRootCertStoreLocked(env); +} + +// Caller holds root_cert_store_mutex. +static X509_STORE* NewRootCertStoreLocked(Environment* env) { X509_STORE* store = X509_STORE_new(); CHECK_NOT_NULL(store); @@ -1246,6 +1277,7 @@ MaybeLocal X509sToArrayOfStrings(Environment* env, void GetUserRootCertificates(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); + Mutex::ScopedLock lock(root_cert_store_mutex); CHECK_NOT_NULL(root_certs_from_users); Local results; if (X509sToArrayOfStrings(env, @@ -1266,6 +1298,7 @@ void ResetRootCertStore(const FunctionCallbackInfo& args) { if (cert_array->Length() == 0) { // If the array is empty, just clear the user certs and reset the store. + Mutex::ScopedLock lock(root_cert_store_mutex); if (root_cert_store != nullptr) { X509_STORE_free(root_cert_store); root_cert_store = nullptr; @@ -1303,6 +1336,8 @@ void ResetRootCertStore(const FunctionCallbackInfo& args) { } } + Mutex::ScopedLock lock(root_cert_store_mutex); + // Free any existing certificates in the old set. if (root_certs_from_users != nullptr) { for (X509* cert : *root_certs_from_users) {