Skip to main content
Glama
udf_runtime.rs7.81 kB
use std::{ borrow::Cow, sync::OnceLock, }; use anyhow::Context as _; use common::knobs::{ ISOLATE_MAX_HEAP_EXTRA_SIZE, ISOLATE_MAX_USER_HEAP_SIZE, }; use deno_core::{ v8::{ self, scope, MapFnTo, }, ModuleSpecifier, }; use crate::{ bundled_js::system_udf_file, helpers, isolate::SETUP_URL, strings, }; static BASE_SNAPSHOT: OnceLock<Vec<u8>> = OnceLock::new(); /// Creates a snapshot containing the UDF runtime in its default context and /// saves it. /// /// This must be called once per process, prior to calling /// `create_isolate_with_udf_runtime`. pub(crate) fn initialize() -> anyhow::Result<()> { let snapshot = create_base_snapshot()?.to_vec(); BASE_SNAPSHOT .set(snapshot) .map_err(|_| anyhow::anyhow!("can't initialize more than once"))?; Ok(()) } /// Set a 64KB initial heap size const INITIAL_HEAP_SIZE: usize = 1 << 16; /// Creates a new V8 isolate from the saved snapshot. Contexts created in this /// isolate will have the UDF runtime already loaded. pub(crate) fn create_isolate_with_udf_runtime(create_params: v8::CreateParams) -> v8::OwnedIsolate { let snapshot = BASE_SNAPSHOT .get() .expect("udf_runtime::initialize not called"); v8::Isolate::new( create_params .heap_limits( INITIAL_HEAP_SIZE, *ISOLATE_MAX_USER_HEAP_SIZE + *ISOLATE_MAX_HEAP_EXTRA_SIZE, ) .snapshot_blob(Cow::Borrowed(&snapshot[..]).into()) .external_references(external_references().into()), ) } fn illegal_constructor<'s>( scope: &mut v8::PinScope<'s, '_>, _args: v8::FunctionCallbackArguments<'s>, mut rv: v8::ReturnValue<v8::Value>, ) { if let Some(msg) = v8::String::new(scope, "Illegal constructor") { let exception = v8::Exception::type_error(scope, msg); rv.set(scope.throw_exception(exception)); } } fn external_references() -> Vec<v8::ExternalReference> { // TODO: make sure that everything is included in the list of external // references vec![v8::ExternalReference { function: illegal_constructor.map_fn_to(), }] } fn create_base_snapshot() -> anyhow::Result<v8::StartupData> { // TODO: set external references. For now we: // 1. do not reuse snapshot blobs across processes, // 2. only use 'static external references // so this is OK. let mut isolate = v8::Isolate::snapshot_creator(Some(Cow::Owned(external_references())), None); { scope!(let scope, &mut isolate); let context = v8::Context::new(scope, v8::ContextOptions::default()); let crypto_key = v8::FunctionTemplate::new(scope, illegal_constructor); crypto_key.set_class_name(strings::CryptoKey.create(scope)?); assert!(crypto_key .instance_template(scope) .set_internal_field_count(1)); let crypto_key_prototype = crypto_key.prototype_template(scope); let symbol_tostringtag = v8::Symbol::get_to_string_tag(scope); crypto_key_prototype.set_with_attr( symbol_tostringtag.into(), strings::CryptoKey.create(scope)?.into(), v8::PropertyAttribute::DONT_ENUM, ); let crypto_key_private = v8::ObjectTemplate::new(scope); assert!(crypto_key_private.set_internal_field_count(1)); let context_scope = &mut v8::ContextScope::new(scope, context); // Create `global.Convex`, so that `setup.js` can populate `Convex.jsSyscall` let convex_value = v8::Object::new(context_scope); let convex_key = strings::Convex.create(context_scope)?; let global = context.global(context_scope); global.set(context_scope, convex_key.into(), convex_value.into()); { let crypto_key_instance = crypto_key .get_function(context_scope) .context("instantiate CryptoKey")?; let crypto_key_key = strings::CryptoKey.create(context_scope)?; global.set( context_scope, crypto_key_key.into(), crypto_key_instance.into(), ); // Stash the reference to CryptoKey in case `global.CryptoKey` is overwritten let private_crypto_key_key = v8::Private::for_api(context_scope, Some(crypto_key_key)); let crypto_key_private_instance = crypto_key_private .new_instance(context_scope) .context("instantiate CryptoKeyPrivate")?; assert!(crypto_key_private_instance.set_internal_field(0, crypto_key.into())); global.set_private( context_scope, private_crypto_key_key, crypto_key_private_instance.into(), ); } run_setup_module(context_scope)?; // Mark the context we created as the "default context", so that every // new context created from the snapshot will include this // runtime. context_scope.set_default_context(context); } let data = isolate .create_blob(v8::FunctionCodeHandling::Keep) .context("Failed to create snapshot")?; Ok(data) } /// Go through all the V8 boilerplate to compile, instantiate, evaluate, and run /// the setup code. This is all inlined to avoid any dependencies on context /// state that isn't set up in the snapshot creation code path. fn run_setup_module(scope: &mut v8::PinScope<'_, '_>) -> anyhow::Result<()> { let setup_url = ModuleSpecifier::parse(SETUP_URL)?; let (source, _source_map) = system_udf_file("setup.js").context("Setup module not found")?; let name_str = v8::String::new(scope, setup_url.as_str()).context("Failed to create name_str")?; let source_str = v8::String::new(scope, source).context("Failed to create source_str")?; let origin = helpers::module_origin(scope, name_str); let mut v8_source = v8::script_compiler::Source::new(source_str, Some(&origin)); let module = v8::script_compiler::compile_module(scope, &mut v8_source) .context("Failed to compile setup module")?; // setup.js is bundled into a single module and so it doesn't need to import // anything. fn noop_resolve_module<'a>( _context: v8::Local<'a, v8::Context>, _specifier: v8::Local<'a, v8::String>, _import_assertions: v8::Local<'a, v8::FixedArray>, _referrer: v8::Local<'a, v8::Module>, ) -> Option<v8::Local<'a, v8::Module>> { None } anyhow::ensure!( module.instantiate_module(scope, noop_resolve_module) == Some(true), "Failed to instantiate setup module" ); let evaluation_result = module.evaluate(scope).context("evaluate returned None")?; let status = module.get_status(); anyhow::ensure!( status == v8::ModuleStatus::Evaluated, "Evaluating setup module failed with status {:?}", status ); let promise = v8::Local::<v8::Promise>::try_from(evaluation_result) .context("Setup module didn't evaluate to a promise")?; anyhow::ensure!( promise.state() == v8::PromiseState::Fulfilled, "Setup module promise failed with state {:?}", promise.state() ); let namespace = module .get_module_namespace() .to_object(scope) .context("Module namespace wasn't an object?")?; let function_str = strings::setup.create(scope)?; let function: v8::Local<v8::Function> = namespace .get(scope, function_str.into()) .context("Missing setup function")? .try_into()?; let global = scope.get_current_context().global(scope); function .call(scope, global.into(), &[global.into()]) .context("calling setup failed")?; Ok(()) }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/get-convex/convex-backend'

If you have feedback or need assistance with the MCP directory API, please join our Discord server