diff --git a/crates/base/src/deno_runtime.rs b/crates/base/src/deno_runtime.rs index be2b9ce7..5e9b277a 100644 --- a/crates/base/src/deno_runtime.rs +++ b/crates/base/src/deno_runtime.rs @@ -6,7 +6,7 @@ use crate::utils::json; use crate::utils::path::find_up; use crate::utils::units::{bytes_to_display, mib_to_bytes, percentage_value}; -use anyhow::{anyhow, bail, Error}; +use anyhow::{anyhow, bail, Context, Error}; use arc_swap::ArcSwapOption; use base_mem_check::{MemCheckState, WorkerHeapStatistics}; use base_rt::DenoRuntimeDropToken; @@ -18,8 +18,8 @@ use deno_core::error::{AnyError, JsError}; use deno_core::url::Url; use deno_core::v8::{self, GCCallbackFlags, GCType, HeapStatistics, Isolate}; use deno_core::{ - located_script_name, serde_json, JsRuntime, ModuleCodeString, ModuleId, ModuleLoader, - ModuleSpecifier, OpState, PollEventLoopOptions, ResolutionKind, RuntimeOptions, + serde_json, JsRuntime, ModuleId, ModuleLoader, ModuleSpecifier, OpState, PollEventLoopOptions, + ResolutionKind, RuntimeOptions, }; use deno_http::DefaultHttpPropertyExtractor; use deno_tls::deno_native_certs::load_native_certs; @@ -27,6 +27,7 @@ use deno_tls::rustls::RootCertStore; use deno_tls::RootCertStoreProvider; use futures_util::future::poll_fn; use futures_util::task::AtomicWaker; +use futures_util::FutureExt; use log::error; use once_cell::sync::{Lazy, OnceCell}; use sb_core::http::sb_core_http; @@ -205,15 +206,45 @@ impl MemCheck { } pub trait GetRuntimeContext { - fn get_runtime_context() -> impl Serialize; -} + fn get_runtime_context( + conf: &WorkerRuntimeOpts, + use_inspector: bool, + version: Option<&str>, + ) -> impl Serialize { + serde_json::json!({ + "target": env!("TARGET"), + "kind": conf.to_worker_kind().to_string(), + "debug": cfg!(debug_assertions), + "inspector": use_inspector, + "version": { + "runtime": version.unwrap_or("0.1.0"), + "deno": MAYBE_DENO_VERSION + .get() + .map(|it| &**it) + .unwrap_or("UNKNOWN"), + }, + "flags": { + "SHOULD_DISABLE_DEPRECATED_API_WARNING": SHOULD_DISABLE_DEPRECATED_API_WARNING + .get() + .copied() + .unwrap_or_default(), + "SHOULD_USE_VERBOSE_DEPRECATED_API_WARNING": SHOULD_USE_VERBOSE_DEPRECATED_API_WARNING + .get() + .copied() + .unwrap_or_default() + } + }) + } -impl GetRuntimeContext for () { - fn get_runtime_context() -> impl Serialize { - serde_json::json!(null) + fn get_extra_context() -> impl Serialize { + serde_json::json!({}) } } +type DefaultRuntimeContext = (); + +impl GetRuntimeContext for DefaultRuntimeContext {} + #[derive(Debug, Clone)] struct GlobalMainContext(v8::Global); @@ -240,7 +271,7 @@ pub enum WillTerminateReason { WallClock, } -pub struct DenoRuntime { +pub struct DenoRuntime { pub js_runtime: ManuallyDrop, pub drop_token: CancellationToken, pub env_vars: HashMap, // TODO: does this need to be pub? @@ -282,7 +313,7 @@ impl Drop for DenoRuntime { } } -impl DenoRuntime<()> { +impl DenoRuntime { pub async fn acquire() -> OwnedSemaphorePermit { RUNTIME_CREATION_SEM .with(|v| v.clone()) @@ -668,10 +699,10 @@ where let global_obj = context_local.global(scope); let bootstrap_str = v8::String::new_external_onebyte_static(scope, b"bootstrap").unwrap(); - let bootstrap_ns: v8::Local = global_obj + let bootstrap_ns = global_obj .get(scope, bootstrap_str.into()) .unwrap() - .try_into() + .to_object(scope) .unwrap(); let dispatch_load_event_fn_str = @@ -717,56 +748,17 @@ where op_state.put(dispatch_fns); op_state.put(promise_metrics.clone()); op_state.put(GlobalMainContext(main_context)); + } + + { + let op_state_rc = js_runtime.op_state(); + let mut op_state = op_state_rc.borrow_mut(); // NOTE(Andreespirela): We do this because "NODE_DEBUG" is trying to be read during // initialization, But we need the gotham state to be up-to-date. op_state.put(sb_env::EnvVars::default()); } - // Bootstrapping stage - let script = format!( - "globalThis.bootstrapSBEdge({}, {})", - serde_json::json!([ - // 0: target - env!("TARGET"), - // 1: isUserWorker - conf.is_user_worker(), - // 2: isEventsWorker - conf.is_events_worker(), - // 3: edgeRuntimeVersion - option_env!("GIT_V_TAG").unwrap_or("0.1.0"), - // 4: denoVersion - MAYBE_DENO_VERSION - .get() - .map(|it| &**it) - .unwrap_or("UNKNOWN"), - // 5: shouldDisableDeprecatedApiWarning - SHOULD_DISABLE_DEPRECATED_API_WARNING - .get() - .copied() - .unwrap_or_default(), - // 6: shouldUseVerboseDeprecatedApiWarning - SHOULD_USE_VERBOSE_DEPRECATED_API_WARNING - .get() - .copied() - .unwrap_or_default(), - ]), - { - let mut runtime_context = serde_json::json!(RuntimeContext::get_runtime_context()); - - json::merge_object( - &mut runtime_context, - &conf - .as_user_worker() - .and_then(|it| it.context.clone()) - .map(serde_json::Value::Object) - .unwrap_or_else(|| serde_json::json!({})), - ); - - runtime_context - } - ); - if let Some(inspector) = maybe_inspector.clone() { inspector.server.register_inspector( main_module_url.to_string(), @@ -788,9 +780,60 @@ where .put(MemCheckWaker::from(mem_check.waker.clone())); } + // Bootstrapping stage + let (runtime_context, extra_context, bootstrap_fn) = { + let runtime_context = serde_json::json!(RuntimeContext::get_runtime_context( + &conf, + has_inspector, + option_env!("GIT_V_TAG"), + )); + + let extra_context = { + let mut context = serde_json::json!(RuntimeContext::get_extra_context()); + + json::merge_object( + &mut context, + &conf + .as_user_worker() + .and_then(|it| it.context.clone()) + .map(serde_json::Value::Object) + .unwrap_or_else(|| serde_json::json!({})), + ); + + context + }; + + let context = js_runtime.main_context(); + let scope = &mut js_runtime.handle_scope(); + let context_local = v8::Local::new(scope, context); + let global_obj = context_local.global(scope); + let bootstrap_str = + v8::String::new_external_onebyte_static(scope, b"bootstrapSBEdge").unwrap(); + let bootstrap_fn = v8::Local::::try_from( + global_obj.get(scope, bootstrap_str.into()).unwrap(), + ) + .unwrap(); + + let runtime_context_local = deno_core::serde_v8::to_v8(scope, runtime_context) + .context("failed to convert to v8 value")?; + let runtime_context_global = v8::Global::new(scope, runtime_context_local); + let extra_context_local = deno_core::serde_v8::to_v8(scope, extra_context) + .context("failed to convert to v8 value")?; + let extra_context_global = v8::Global::new(scope, extra_context_local); + let bootstrap_fn_global = v8::Global::new(scope, bootstrap_fn); + + ( + runtime_context_global, + extra_context_global, + bootstrap_fn_global, + ) + }; + js_runtime - .execute_script(located_script_name!(), ModuleCodeString::from(script)) - .expect("Failed to execute bootstrap script"); + .call_with_args(&bootstrap_fn, &[runtime_context, extra_context]) + .now_or_never() + .transpose() + .context("failed to execute bootstrap script")?; { // run inside a closure, so op_state_rc is released @@ -1813,7 +1856,7 @@ mod test { struct WithSyncFileAPI; impl GetRuntimeContext for WithSyncFileAPI { - fn get_runtime_context() -> impl Serialize { + fn get_extra_context() -> impl Serialize { serde_json::json!({ "useReadSyncFileAPI": true, }) @@ -2559,7 +2602,7 @@ mod test { struct Ctx; impl GetRuntimeContext for Ctx { - fn get_runtime_context() -> impl Serialize { + fn get_extra_context() -> impl Serialize { serde_json::json!({ "useReadSyncFileAPI": true, "shouldBootstrapMockFnThrowError": true, diff --git a/crates/sb_ai/js/onnxruntime/cache_adapter.js b/crates/sb_ai/js/onnxruntime/cache_adapter.js index e427684e..9765cf62 100644 --- a/crates/sb_ai/js/onnxruntime/cache_adapter.js +++ b/crates/sb_ai/js/onnxruntime/cache_adapter.js @@ -1,4 +1,4 @@ -import { primordials } from 'ext:core/mod.js'; +import { primordials, internals } from 'ext:core/mod.js'; import * as webidl from 'ext:deno_webidl/00_webidl.js'; import * as DenoCaches from 'ext:deno_cache/01_cache.js'; @@ -9,7 +9,7 @@ const { async function open(cacheName, _next) { if (!ALLOWED_CACHE_NAMES.includes(cacheName)) { - throw new Error("Web Cache is not available in this context"); + return await notAvailable(); } // NOTE(kallebysantos): Since default `Web Cache` is not alloed we need to manually create a new @@ -56,6 +56,11 @@ CacheStoragePrototype.open = async function (args) { // scenario where `Web Cache` is free to use, the `sb_ai: Cache Adapter` should act as request // middleware ie. add new behaviour on top of `next()` async function notAvailable() { + // Ignore errors when `debug = true` + if(internals,internals.bootstrapArgs.opts.debug) { + return; + } + throw new Error("Web Cache is not available in this context"); } diff --git a/crates/sb_core/js/bootstrap.js b/crates/sb_core/js/bootstrap.js index c3eef82c..099cf3fa 100644 --- a/crates/sb_core/js/bootstrap.js +++ b/crates/sb_core/js/bootstrap.js @@ -1,3 +1,5 @@ +import { core, internals, primordials } from 'ext:core/mod.js'; + import * as abortSignal from 'ext:deno_web/03_abort_signal.js'; import * as base64 from 'ext:deno_web/05_base64.js'; import * as console from 'ext:deno_console/01_console.js'; @@ -21,11 +23,27 @@ import * as webSocket from 'ext:deno_websocket/01_websocket.js'; import * as response from 'ext:deno_fetch/23_response.js'; import * as request from 'ext:deno_fetch/23_request.js'; import * as globalInterfaces from 'ext:deno_web/04_global_interfaces.js'; +import * as imageData from 'ext:deno_web/16_image_data.js'; +import * as broadcastChannel from 'ext:deno_broadcast_channel/01_broadcast_channel.js'; +import * as performance from 'ext:deno_web/15_performance.js'; +import * as messagePort from 'ext:deno_web/13_message_port.js'; +import * as DenoWSStream from 'ext:deno_websocket/02_websocketstream.js'; +import * as eventSource from 'ext:deno_fetch/27_eventsource.js'; +import * as WebGPU from 'ext:deno_webgpu/00_init.js'; +import * as WebGPUSurface from 'ext:deno_webgpu/02_surface.js'; + +import * as MainWorker from 'ext:sb_core_main_js/js/main_worker.js'; + import { SUPABASE_ENV } from 'ext:sb_env/env.js'; import { USER_WORKER_API as ai } from 'ext:sb_ai/js/ai.js'; import 'ext:sb_ai/js/onnxruntime/cache_adapter.js'; + import { waitUntil, installPromiseHook } from 'ext:sb_core_main_js/js/async_hook.js'; import { registerErrors } from 'ext:sb_core_main_js/js/errors.js'; +import { promiseRejectMacrotaskCallback } from 'ext:sb_core_main_js/js/promises.js'; +import { denoOverrides, fsVars } from 'ext:sb_core_main_js/js/denoOverrides.js'; +import { registerDeclarativeServer } from 'ext:sb_core_main_js/js/00_serve.js'; +import { SupabaseEventListener } from 'ext:sb_user_event_worker/event_worker.js'; import { formatException, getterOnly, @@ -33,8 +51,6 @@ import { readOnly, writable, } from 'ext:sb_core_main_js/js/fieldUtils.js'; -import * as imageData from "ext:deno_web/16_image_data.js"; -import * as broadcastChannel from "ext:deno_broadcast_channel/01_broadcast_channel.js"; import { Navigator, @@ -44,23 +60,13 @@ import { setUserAgent, } from 'ext:sb_core_main_js/js/navigator.js'; -import { promiseRejectMacrotaskCallback } from 'ext:sb_core_main_js/js/promises.js'; -import { denoOverrides, fsVars } from 'ext:sb_core_main_js/js/denoOverrides.js'; -import { registerDeclarativeServer } from 'ext:sb_core_main_js/js/00_serve.js'; -import * as performance from 'ext:deno_web/15_performance.js'; -import * as messagePort from 'ext:deno_web/13_message_port.js'; -import { SupabaseEventListener } from 'ext:sb_user_event_worker/event_worker.js'; -import * as MainWorker from 'ext:sb_core_main_js/js/main_worker.js'; -import * as DenoWSStream from 'ext:deno_websocket/02_websocketstream.js'; -import * as eventSource from 'ext:deno_fetch/27_eventsource.js'; -import * as WebGPU from 'ext:deno_webgpu/00_init.js'; -import * as WebGPUSurface from 'ext:deno_webgpu/02_surface.js'; - -import { core, internals, primordials } from 'ext:core/mod.js'; +import 'ext:sb_ai/js/onnxruntime/cache_adapter.js'; let globalThis_; const ops = core.ops; +const v8Console = globalThis.console; + const { Error, ArrayPrototypePop, @@ -471,7 +477,7 @@ function processRejectionHandled(promise, reason) { } } -globalThis.bootstrapSBEdge = (opts, extraCtx) => { +globalThis.bootstrapSBEdge = (opts, ctx) => { globalThis_ = globalThis; // We should delete this after initialization, @@ -496,24 +502,40 @@ globalThis.bootstrapSBEdge = (opts, extraCtx) => { // Nothing listens to this, but it warms up the code paths for event dispatch (new event.EventTarget()).dispatchEvent(new Event("warmup")); + /** + * @type {{ + * target: string, + * kind: 'user' | 'main' | 'event', + * inspector: boolean, + * debug: boolean, + * version: { + * runtime: string, + * deno: string, + * }, + * flags: { + * SHOULD_DISABLE_DEPRECATED_API_WARNING: boolean, + * SHOULD_USE_VERBOSE_DEPRECATED_API_WARNING: boolean + * } + * }} + */ const { - 0: target, - 1: isUserWorker, - 2: isEventsWorker, - 3: edgeRuntimeVersion, - 4: denoVersion, - 5: shouldDisableDeprecatedApiWarning, - 6: shouldUseVerboseDeprecatedApiWarning + target, + kind, + version, + inspector, + flags } = opts; - deprecatedApiWarningDisabled = shouldDisableDeprecatedApiWarning; - verboseDeprecatedApiWarning = shouldUseVerboseDeprecatedApiWarning; - bootstrapMockFnThrowError = extraCtx?.shouldBootstrapMockFnThrowError ?? false; + + deprecatedApiWarningDisabled = flags['SHOULD_DISABLE_DEPRECATED_API_WARNING']; + verboseDeprecatedApiWarning = flags['SHOULD_USE_VERBOSE_DEPRECATED_API_WARNING']; + bootstrapMockFnThrowError = ctx?.shouldBootstrapMockFnThrowError ?? false; runtimeStart(target); - ObjectDefineProperty(globalThis, 'SUPABASE_VERSION', readOnly(String(edgeRuntimeVersion))); - ObjectDefineProperty(globalThis, 'DENO_VERSION', readOnly(denoVersion)); + ObjectAssign(internals, { bootstrapArgs: { opts, ctx } }); + ObjectDefineProperty(globalThis, 'SUPABASE_VERSION', readOnly(String(version.runtime))); + ObjectDefineProperty(globalThis, 'DENO_VERSION', readOnly(version.deno)); // set these overrides after runtimeStart ObjectDefineProperties(denoOverrides, { @@ -556,15 +578,13 @@ globalThis.bootstrapSBEdge = (opts, extraCtx) => { /// DISABLE SHARED MEMORY AND INSTALL MEM CHECK TIMING - // NOTE: We should not allow user workers to use shared memory. This is - // because they are not counted in the external memory statistics of the - // individual isolates. + // NOTE: We should not allow user workers to use shared memory. This is because they are not + // counted in the external memory statistics of the individual isolates. - // NOTE(Nyannyacha): Put below inside `isUserWorker` block if we have the - // plan to support a shared array buffer across the isolates. But for now, - // we explicitly disabled the shared buffer option between isolate globally - // in `deno_runtime.rs`, so this patch also applies regardless of worker - // type. + // NOTE(Nyannyacha): Put below inside `kind === 'user'` block if we have the plan to support a + // shared array buffer across the isolates. But for now, we explicitly disabled the shared + // buffer option between isolate globally in `deno_runtime.rs`, so this patch also applies + // regardless of worker type. const wasmMemoryCtor = globalThis.WebAssembly.Memory; const wasmMemoryPrototypeGrow = wasmMemoryCtor.prototype.grow; @@ -589,9 +609,15 @@ globalThis.bootstrapSBEdge = (opts, extraCtx) => { delete globalThis.SharedArrayBuffer; globalThis.WebAssembly.Memory = patchedWasmMemoryCtor; + if (inspector) { + ObjectDefineProperties(globalThis, { + console: nonEnumerable(v8Console), + }); + } + /// DISABLE SHARED MEMORY INSTALL MEM CHECK TIMING - if (isUserWorker) { + if (kind === 'user') { ObjectDefineProperties(globalThis, { EdgeRuntime: { value: { @@ -599,13 +625,19 @@ globalThis.bootstrapSBEdge = (opts, extraCtx) => { }, configurable: true, }, - console: nonEnumerable( - new console.Console((msg, level) => { - return ops.op_user_worker_log(msg, level > 1); - }), - ), }); + // override console + if (!inspector) { + ObjectDefineProperties(globalThis, { + console: nonEnumerable( + new console.Console((msg, level) => { + return ops.op_user_worker_log(msg, level > 1); + }), + ), + }); + } + const apisToBeOverridden = { ...DENIED_DENO_FS_API_LIST, @@ -634,7 +666,7 @@ globalThis.bootstrapSBEdge = (opts, extraCtx) => { 'memoryUsage': () => ops.op_runtime_memory_usage(), }; - if (extraCtx?.useReadSyncFileAPI) { + if (ctx?.useReadSyncFileAPI) { apisToBeOverridden['readFileSync'] = true; apisToBeOverridden['readTextFileSync'] = true; } @@ -652,7 +684,7 @@ globalThis.bootstrapSBEdge = (opts, extraCtx) => { } } - if (isEventsWorker) { + if (kind === 'event') { // Event Manager should have the same as the `main` except it can't create workers (that would be catastrophic) delete globalThis.EdgeRuntime; ObjectDefineProperties(globalThis, {