fix: make V8 runtime init idempotent and auto-initialize before isolate creation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -313,6 +313,9 @@ pub fn spawn_test_worker(
|
||||
conn: &Connection,
|
||||
port: u16,
|
||||
) -> (KillpillSender, tokio::task::JoinHandle<()>) {
|
||||
#[cfg(feature = "deno_core")]
|
||||
windmill_runtime_nativets::setup_deno_runtime().expect("V8 init failed");
|
||||
|
||||
std::fs::DirBuilder::new()
|
||||
.recursive(true)
|
||||
.create(windmill_worker::GO_BIN_CACHE_DIR)
|
||||
|
||||
@@ -28,15 +28,6 @@ use windmill_common::jobs::{JobPayload, RawCode};
|
||||
#[cfg(feature = "deno_core")]
|
||||
use windmill_common::scripts::ScriptLang;
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
fn init_nativets_runtime() {
|
||||
static INIT: std::sync::Once = std::sync::Once::new();
|
||||
INIT.call_once(|| {
|
||||
let _ = rustls::crypto::ring::default_provider().install_default();
|
||||
windmill_runtime_nativets::setup_deno_runtime().expect("V8 init failed");
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
fn nativets_code(content: &str) -> JobPayload {
|
||||
JobPayload::Code(RawCode {
|
||||
@@ -82,7 +73,6 @@ async fn push_and_wait(
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_nativets_jobs(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
init_nativets_runtime();
|
||||
set_jwt_secret().await;
|
||||
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
|
||||
@@ -260,13 +260,6 @@ async fn test_parallel_nativets_stress(db: Pool<Postgres>) -> anyhow::Result<()>
|
||||
initialize_tracing().await;
|
||||
set_jwt_secret().await;
|
||||
|
||||
// V8 and rustls must be initialized before any JsRuntime is created
|
||||
static RUNTIME_INIT: std::sync::Once = std::sync::Once::new();
|
||||
RUNTIME_INIT.call_once(|| {
|
||||
let _ = rustls::crypto::ring::default_provider().install_default();
|
||||
windmill_runtime_nativets::setup_deno_runtime().expect("V8 init failed");
|
||||
});
|
||||
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
let port = server.addr.port();
|
||||
let conn = Connection::Sql(db.clone());
|
||||
|
||||
@@ -46,6 +46,7 @@ lazy_static.workspace = true
|
||||
const_format.workspace = true
|
||||
futures.workspace = true
|
||||
sqlx.workspace = true
|
||||
rustls.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
deno_fetch.workspace = true
|
||||
|
||||
@@ -156,28 +156,43 @@ lazy_static! {
|
||||
|
||||
// ── Public interface ─────────────────────────────────────────────────
|
||||
|
||||
/// Set up the deno_core/V8 runtime. Must be called once before creating any JsRuntime.
|
||||
/// Set up the deno_core/V8 runtime. Idempotent — safe to call multiple times.
|
||||
/// Called automatically before JsRuntime creation, but can also be called
|
||||
/// eagerly at startup for predictable initialization order.
|
||||
pub fn setup_deno_runtime() -> anyhow::Result<()> {
|
||||
let unrecognized_v8_flags = deno_core::v8_set_flags(vec![
|
||||
"--stack-size=1024".to_string(),
|
||||
"--no-harmony-import-assertions".to_string(),
|
||||
])
|
||||
.into_iter()
|
||||
.skip(1)
|
||||
.collect::<Vec<_>>();
|
||||
use std::sync::Once;
|
||||
static INIT: Once = Once::new();
|
||||
|
||||
if !unrecognized_v8_flags.is_empty() {
|
||||
println!("Unrecognized V8 flags: {:?}", unrecognized_v8_flags);
|
||||
let mut init_err: Option<String> = None;
|
||||
INIT.call_once(|| {
|
||||
// deno_fetch requires a TLS provider; install ring as default (idempotent).
|
||||
let _ = rustls::crypto::ring::default_provider().install_default();
|
||||
|
||||
let unrecognized_v8_flags = deno_core::v8_set_flags(vec![
|
||||
"--stack-size=1024".to_string(),
|
||||
"--no-harmony-import-assertions".to_string(),
|
||||
])
|
||||
.into_iter()
|
||||
.skip(1)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if !unrecognized_v8_flags.is_empty() {
|
||||
init_err = Some(format!("Unrecognized V8 flags: {:?}", unrecognized_v8_flags));
|
||||
}
|
||||
|
||||
// Use an unprotected platform that doesn't enforce thread-isolated allocations
|
||||
// via Memory Protection Keys (pkeys). The default platform requires all V8-using
|
||||
// threads to be descendants of the thread that called v8::Initialize, but tokio's
|
||||
// spawn_blocking pool threads don't satisfy this. Without this, V8 crashes with
|
||||
// SIGSEGV in WasmCodePointerTable::AllocateUninitializedEntry() on x86_64 Linux.
|
||||
// See: https://github.com/denoland/deno_core/issues/952
|
||||
let platform = deno_core::v8::new_unprotected_default_platform(0, false).make_shared();
|
||||
deno_core::JsRuntime::init_platform(Some(platform), false);
|
||||
});
|
||||
|
||||
if let Some(msg) = init_err {
|
||||
println!("{msg}");
|
||||
}
|
||||
|
||||
// Use an unprotected platform that doesn't enforce thread-isolated allocations
|
||||
// via Memory Protection Keys (pkeys). The default platform requires all V8-using
|
||||
// threads to be descendants of the thread that called v8::Initialize, but tokio's
|
||||
// spawn_blocking pool threads don't satisfy this. Without this, V8 crashes with
|
||||
// SIGSEGV in WasmCodePointerTable::AllocateUninitializedEntry() on x86_64 Linux.
|
||||
// See: https://github.com/denoland/deno_core/issues/952
|
||||
let platform = deno_core::v8::new_unprotected_default_platform(0, false).make_shared();
|
||||
deno_core::JsRuntime::init_platform(Some(platform), false);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -464,6 +479,9 @@ pub async fn eval_fetch_timeout(
|
||||
|
||||
let (memory_limit_tx, mut memory_limit_rx) = mpsc::unbounded_channel::<()>();
|
||||
|
||||
// Ensure V8 platform is initialized (idempotent, no-op if already done).
|
||||
setup_deno_runtime().expect("V8 platform init failed");
|
||||
|
||||
// Serialize isolate creation as extra safety net against concurrent V8
|
||||
// isolate creation races. The main fix is the unprotected platform in
|
||||
// setup_deno_runtime(), but this provides defense in depth.
|
||||
|
||||
Reference in New Issue
Block a user