Add ability to configure JavaScript runtime limits (#4669)

This commit is contained in:
Tobie Morgan Hitchcock 2024-09-03 14:19:12 +01:00 committed by GitHub
parent 6e09684e4c
commit 33aa5da3cc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 30 additions and 23 deletions

View file

@ -39,6 +39,14 @@ pub static MAX_STREAM_BATCH_SIZE: Lazy<u32> =
pub static INDEXING_BATCH_SIZE: Lazy<u32> = pub static INDEXING_BATCH_SIZE: Lazy<u32> =
lazy_env_parse!("SURREAL_INDEXING_BATCH_SIZE", u32, 250); lazy_env_parse!("SURREAL_INDEXING_BATCH_SIZE", u32, 250);
/// The maximum stack size of the JavaScript function runtime (defaults to 256 KiB)
pub static SCRIPTING_MAX_STACK_SIZE: Lazy<usize> =
lazy_env_parse!("SURREAL_SCRIPTING_MAX_STACK_SIZE", usize, 256 * 1024);
/// The maximum memory limit of the JavaScript function runtime (defaults to 2 MiB).
pub static SCRIPTING_MAX_MEMORY_LIMIT: Lazy<usize> =
lazy_env_parse!("SURREAL_SCRIPTING_MAX_MEMORY_LIMIT", usize, 2 << 20);
/// Forward all signup/signin/authenticate query errors to a client performing authentication. Do not use in production. /// Forward all signup/signin/authenticate query errors to a client performing authentication. Do not use in production.
pub static INSECURE_FORWARD_ACCESS_ERRORS: Lazy<bool> = pub static INSECURE_FORWARD_ACCESS_ERRORS: Lazy<bool> =
lazy_env_parse!("SURREAL_INSECURE_FORWARD_ACCESS_ERRORS", bool, false); lazy_env_parse!("SURREAL_INSECURE_FORWARD_ACCESS_ERRORS", bool, false);
@ -51,10 +59,10 @@ pub static INSECURE_FORWARD_ACCESS_ERRORS: Lazy<bool> =
feature = "kv-tikv", feature = "kv-tikv",
))] ))]
/// Specifies the buffer limit for external sorting. /// Specifies the buffer limit for external sorting.
/// If the environment variable is not present or cannot be parsed, a default value of 50,000 is used.
pub static EXTERNAL_SORTING_BUFFER_LIMIT: Lazy<usize> = pub static EXTERNAL_SORTING_BUFFER_LIMIT: Lazy<usize> =
lazy_env_parse!("SURREAL_EXTERNAL_SORTING_BUFFER_LIMIT", usize, 50_000); lazy_env_parse!("SURREAL_EXTERNAL_SORTING_BUFFER_LIMIT", usize, 50_000);
/// Specifies whether GraphQL querying and schema definition is enabled.
pub static GRAPHQL_ENABLE: Lazy<bool> = pub static GRAPHQL_ENABLE: Lazy<bool> =
lazy_env_parse!("SURREAL_EXPERIMENTAL_GRAPHQL", bool, false); lazy_env_parse!("SURREAL_EXPERIMENTAL_GRAPHQL", bool, false);
@ -63,7 +71,8 @@ pub static GRAPHQL_ENABLE: Lazy<bool> =
#[cfg(not(test))] #[cfg(not(test))]
pub static EXPERIMENTAL_BEARER_ACCESS: Lazy<bool> = pub static EXPERIMENTAL_BEARER_ACCESS: Lazy<bool> =
lazy_env_parse!("SURREAL_EXPERIMENTAL_BEARER_ACCESS", bool, false); lazy_env_parse!("SURREAL_EXPERIMENTAL_BEARER_ACCESS", bool, false);
// Run tests with bearer access enabled as it introduces new functionality that needs to be tested.
/// Run tests with bearer access enabled as it introduces new functionality that needs to be tested.
#[cfg(test)] #[cfg(test)]
pub static EXPERIMENTAL_BEARER_ACCESS: Lazy<bool> = Lazy::new(|| true); pub static EXPERIMENTAL_BEARER_ACCESS: Lazy<bool> = Lazy::new(|| true);

View file

@ -6,6 +6,8 @@ use super::modules::loader;
use super::modules::resolver; use super::modules::resolver;
use super::modules::surrealdb::query::QueryContext; use super::modules::surrealdb::query::QueryContext;
use super::modules::surrealdb::query::QUERY_DATA_PROP_NAME; use super::modules::surrealdb::query::QUERY_DATA_PROP_NAME;
use crate::cnf::SCRIPTING_MAX_MEMORY_LIMIT;
use crate::cnf::SCRIPTING_MAX_STACK_SIZE;
use crate::ctx::Context; use crate::ctx::Context;
use crate::dbs::Options; use crate::dbs::Options;
use crate::doc::CursorDoc; use crate::doc::CursorDoc;
@ -57,12 +59,12 @@ pub async fn run(
if context.is_done() { if context.is_done() {
return Ok(Value::None); return Ok(Value::None);
} }
// Create an JavaScript context // Create a JavaScript context
let run = js::AsyncRuntime::new().unwrap(); let run = js::AsyncRuntime::new().unwrap();
// Explicitly set max stack size to 256 KiB // Explicitly set max stack size to 256 KiB
run.set_max_stack_size(262_144).await; run.set_max_stack_size(*SCRIPTING_MAX_STACK_SIZE).await;
// Explicitly set max memory size to 2 MB // Explicitly set max memory size to 2 MB
run.set_memory_limit(2_000_000).await; run.set_memory_limit(*SCRIPTING_MAX_MEMORY_LIMIT).await;
// Ensure scripts are cancelled with context // Ensure scripts are cancelled with context
let cancellation = context.cancellation(); let cancellation = context.cancellation();
let handler = Box::new(move || cancellation.is_done()); let handler = Box::new(move || cancellation.is_done());
@ -75,34 +77,30 @@ pub async fn run(
let src = format!( let src = format!(
"export default async function() {{ try {{ {src} }} catch(e) {{ return (e instanceof Error) ? e : new Error(e); }} }}" "export default async function() {{ try {{ {src} }} catch(e) {{ return (e instanceof Error) ? e : new Error(e); }} }}"
); );
// Attempt to execute the script // Attempt to execute the script
async_with!(ctx => |ctx| { async_with!(ctx => |ctx| {
let res = async { let res = async {
// register all classes to the runtime.
// Get the context global object // Get the context global object
let global = ctx.globals(); let global = ctx.globals();
// SAFETY: This is safe because the runtime only lives for the duration of this // SAFETY: This is safe because the runtime only lives for the duration of this
// function. For the entire duration of which context, opt, txn and doc are valid. // function. For the entire duration of which context, opt, txn and doc are valid.
unsafe{ create_query_data(context, opt, doc, &ctx) }?; unsafe{ create_query_data(context, opt, doc, &ctx) }?;
// Register the fetch module as a global function
fetch::register(&ctx)?;
// Register the surrealdb module as a global object // Register the surrealdb module as a global object
let (module, promise) = Module::evaluate_def::<modules::surrealdb::Package, _>(ctx.clone(), "surrealdb")?; let (module, promise) = Module::evaluate_def::<modules::surrealdb::Package, _>(ctx.clone(), "surrealdb")?;
promise.finish::<()>()?; promise.finish::<()>()?;
global.set( global.set("surrealdb",
"surrealdb",
module.get::<_, js::Value>("default")?, module.get::<_, js::Value>("default")?,
)?; )?;
fetch::register(&ctx)?; // Register the console module as a global object
let console = globals::console::console(&ctx)?; let console = globals::console::console(&ctx)?;
// Register the console function to the globals
global.set("console", console)?; global.set("console", console)?;
// Register the special SurrealDB types as classes // Register the special SurrealDB types as classes
classes::init(&ctx)?; classes::init(&ctx)?;
// Load the script as a module and evaluate it
let (module, promise) = Module::declare(ctx.clone(),"script", src)?.eval()?; let (module, promise) = Module::declare(ctx.clone(),"script", src)?.eval()?;
promise.into_future::<()>().await?; promise.into_future::<()>().await?;
// Attempt to fetch the main export // Attempt to fetch the main export
let fnc = module.get::<_, Function>("default")?; let fnc = module.get::<_, Function>("default")?;
// Extract the doc if any // Extract the doc if any
@ -111,7 +109,7 @@ pub async fn run(
let promise = fnc.call::<_, Promise>((This(doc), Rest(arg)))?.into_future::<Value>(); let promise = fnc.call::<_, Promise>((This(doc), Rest(arg)))?.into_future::<Value>();
promise.await promise.await
}.await; }.await;
// Catch and convert any errors
res.catch(&ctx).map_err(Error::from) res.catch(&ctx).map_err(Error::from)
}) })
.await .await