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> =
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.
pub static INSECURE_FORWARD_ACCESS_ERRORS: Lazy<bool> =
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",
))]
/// 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> =
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> =
lazy_env_parse!("SURREAL_EXPERIMENTAL_GRAPHQL", bool, false);
@ -63,7 +71,8 @@ pub static GRAPHQL_ENABLE: Lazy<bool> =
#[cfg(not(test))]
pub static EXPERIMENTAL_BEARER_ACCESS: Lazy<bool> =
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)]
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::surrealdb::query::QueryContext;
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::dbs::Options;
use crate::doc::CursorDoc;
@ -57,12 +59,12 @@ pub async fn run(
if context.is_done() {
return Ok(Value::None);
}
// Create an JavaScript context
// Create a JavaScript context
let run = js::AsyncRuntime::new().unwrap();
// 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
run.set_memory_limit(2_000_000).await;
run.set_memory_limit(*SCRIPTING_MAX_MEMORY_LIMIT).await;
// Ensure scripts are cancelled with context
let cancellation = context.cancellation();
let handler = Box::new(move || cancellation.is_done());
@ -75,43 +77,39 @@ pub async fn run(
let src = format!(
"export default async function() {{ try {{ {src} }} catch(e) {{ return (e instanceof Error) ? e : new Error(e); }} }}"
);
// Attempt to execute the script
async_with!(ctx => |ctx|{
let res = async{
// register all classes to the runtime.
async_with!(ctx => |ctx| {
let res = async {
// Get the context global object
let global = ctx.globals();
// 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.
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
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::<()>()?;
global.set(
"surrealdb",
global.set("surrealdb",
module.get::<_, js::Value>("default")?,
)?;
fetch::register(&ctx)?;
// Register the console module as a global object
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
classes::init(&ctx)?;
let (module,promise) = Module::declare(ctx.clone(),"script", src)?.eval()?;
// Load the script as a module and evaluate it
let (module, promise) = Module::declare(ctx.clone(),"script", src)?.eval()?;
promise.into_future::<()>().await?;
// Attempt to fetch the main export
let fnc = module.get::<_, Function>("default")?;
// Extract the doc if any
let doc = doc.map(|v|v.doc.as_ref());
let doc = doc.map(|v| v.doc.as_ref());
// Execute the main function
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
}.await;
// Catch and convert any errors
res.catch(&ctx).map_err(Error::from)
})
.await