Skip to content

Commit b39ae9f

Browse files
committed
Preserve runner compatibility with opt-in prepared runs
1 parent a027349 commit b39ae9f

4 files changed

Lines changed: 268 additions & 46 deletions

File tree

‎src/engine.rs‎

Lines changed: 96 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use anyhow::{anyhow, Result};
1+
use anyhow::{anyhow, ensure, Result};
22
use std::path::PathBuf;
33
use std::string::String;
44
use wasmtime::{AsContextMut, Cache, Config, Engine, Linker, Module, ResourceLimiter, Store};
@@ -25,7 +25,19 @@ pub fn uses_msgpack_provider(module: &Module) -> bool {
2525
})
2626
}
2727

28+
/// Parameters for a single run. The standard provider is compiled for each call.
2829
pub struct FunctionRunParams<'a> {
30+
pub function_path: PathBuf,
31+
pub input: BytesContainer,
32+
pub export: &'a str,
33+
pub profile_opts: Option<&'a ProfileOpts>,
34+
pub scale_factor: f64,
35+
pub module: Module,
36+
pub engine: Engine,
37+
}
38+
39+
/// Parameters for a run that reuses a Function and its compiled standard provider.
40+
pub struct PreparedFunctionRunParams<'a> {
2941
pub function_path: PathBuf,
3042
pub input: BytesContainer,
3143
pub export: &'a str,
@@ -89,6 +101,8 @@ impl ResourceLimiter for MemoryLimiter {
89101
}
90102
}
91103

104+
/// Runs a Function using the supplied engine and module.
105+
/// For repeated runs, use [`run_prepared`] to compile the standard provider once.
92106
pub fn run(params: FunctionRunParams) -> Result<FunctionRunResult> {
93107
let FunctionRunParams {
94108
function_path,
@@ -97,6 +111,67 @@ pub fn run(params: FunctionRunParams) -> Result<FunctionRunResult> {
97111
profile_opts,
98112
scale_factor,
99113
module,
114+
engine,
115+
} = params;
116+
let module = ValidatedModule::new(module)?;
117+
ensure!(
118+
Engine::same(&engine, module.inner().engine()),
119+
"cross-`Engine` instantiation is not currently supported"
120+
);
121+
122+
run_prepared(PreparedFunctionRunParams {
123+
function_path,
124+
input,
125+
export,
126+
profile_opts,
127+
scale_factor,
128+
module: &module,
129+
})
130+
}
131+
132+
/// Runs a prepared Function with fresh execution state, using its module's engine.
133+
///
134+
/// Compile the Function and its standard provider once, then reuse them for each input:
135+
///
136+
/// ```no_run
137+
/// use function_runner::{
138+
/// engine::{new_engine, run_prepared, PreparedFunctionRunParams, ValidatedModule},
139+
/// BytesContainer, BytesContainerType, Codec,
140+
/// };
141+
/// use wasmtime::Module;
142+
///
143+
/// # fn main() -> anyhow::Result<()> {
144+
/// let engine = new_engine()?;
145+
/// let path = std::path::PathBuf::from("function.wasm");
146+
/// let module = Module::from_file(&engine, &path)?;
147+
/// let codec = if function_runner::engine::uses_msgpack_provider(&module) {
148+
/// Codec::Messagepack
149+
/// } else {
150+
/// Codec::Json
151+
/// };
152+
/// let module = ValidatedModule::new(module)?;
153+
///
154+
/// for json in [br#"{"hello":"world"}"#, br#"{"hello":"again"}"#] {
155+
/// run_prepared(PreparedFunctionRunParams {
156+
/// function_path: path.clone(),
157+
/// input: BytesContainer::new(BytesContainerType::Input, codec, json.to_vec())?,
158+
/// export: "_start",
159+
/// profile_opts: None,
160+
/// scale_factor: 1.0,
161+
/// module: &module,
162+
/// })?;
163+
/// }
164+
/// # Ok(())
165+
/// # }
166+
/// ```
167+
pub fn run_prepared(params: PreparedFunctionRunParams) -> Result<FunctionRunResult> {
168+
let PreparedFunctionRunParams {
169+
function_path,
170+
input,
171+
export,
172+
profile_opts,
173+
scale_factor,
174+
module,
100175
} = params;
101176

102177
let engine = module.inner().engine();
@@ -236,7 +311,8 @@ mod tests {
236311
function_path: Path::new("tests/fixtures/build/js_function.wasm").to_path_buf(),
237312
input,
238313
export: DEFAULT_EXPORT,
239-
module: &ValidatedModule::new(module)?,
314+
module,
315+
engine,
240316
scale_factor: 1.0,
241317
profile_opts: None,
242318
})?;
@@ -260,7 +336,8 @@ mod tests {
260336
function_path: Path::new("tests/fixtures/build/js_function_v2.wasm").to_path_buf(),
261337
input,
262338
export: DEFAULT_EXPORT,
263-
module: &ValidatedModule::new(module)?,
339+
module,
340+
engine,
264341
scale_factor: 1.0,
265342
profile_opts: None,
266343
})?;
@@ -284,7 +361,8 @@ mod tests {
284361
function_path: Path::new("tests/fixtures/build/js_function_v3.wasm").to_path_buf(),
285362
input,
286363
export: DEFAULT_EXPORT,
287-
module: &ValidatedModule::new(module)?,
364+
module,
365+
engine,
288366
scale_factor: 1.0,
289367
profile_opts: None,
290368
})?;
@@ -293,37 +371,6 @@ mod tests {
293371
Ok(())
294372
}
295373

296-
#[test]
297-
fn test_validated_module_can_be_reused_across_runs() -> Result<()> {
298-
let path = Path::new("tests/fixtures/build/js_function_javy_plugin_v3.wasm");
299-
let engine = new_engine()?;
300-
let module = ValidatedModule::new(Module::from_file(&engine, path)?)?;
301-
302-
for _ in 0..3 {
303-
let input = BytesContainer::new(
304-
BytesContainerType::Input,
305-
Codec::Messagepack,
306-
serde_json::to_vec(&json!({ "hello": "world" }))?,
307-
)?;
308-
let function_run_result = run(FunctionRunParams {
309-
function_path: path.to_path_buf(),
310-
input,
311-
export: DEFAULT_EXPORT,
312-
module: &module,
313-
scale_factor: 1.0,
314-
profile_opts: None,
315-
})?;
316-
317-
assert!(function_run_result.success);
318-
assert_eq!(
319-
function_run_result.output.json_value,
320-
Some(json!({ "hello": "world output" }))
321-
);
322-
}
323-
324-
Ok(())
325-
}
326-
327374
#[test]
328375
fn test_js_functions_javy_v1() -> Result<()> {
329376
let engine = new_engine()?;
@@ -340,7 +387,8 @@ mod tests {
340387
.to_path_buf(),
341388
input,
342389
export: DEFAULT_EXPORT,
343-
module: &ValidatedModule::new(module)?,
390+
module,
391+
engine,
344392
scale_factor: 1.0,
345393
profile_opts: None,
346394
})?;
@@ -357,7 +405,8 @@ mod tests {
357405
function_path: Path::new("tests/fixtures/build/exit_code.wasm").to_path_buf(),
358406
input: json_input(&serde_json::to_vec(&json!({ "code": 0 }))?)?,
359407
export: DEFAULT_EXPORT,
360-
module: &ValidatedModule::new(module)?,
408+
module,
409+
engine,
361410
scale_factor: 1.0,
362411
profile_opts: None,
363412
})?;
@@ -374,7 +423,8 @@ mod tests {
374423
function_path: Path::new("tests/fixtures/build/exit_code.wasm").to_path_buf(),
375424
input: json_input(&serde_json::to_vec(&json!({ "code": 1 }))?)?,
376425
export: DEFAULT_EXPORT,
377-
module: &ValidatedModule::new(module)?,
426+
module,
427+
engine,
378428
scale_factor: 1.0,
379429
profile_opts: None,
380430
})?;
@@ -394,7 +444,8 @@ mod tests {
394444
function_path: Path::new("tests/fixtures/build/linear_memory.wasm").to_path_buf(),
395445
input: json_input(&serde_json::to_vec(&json!({}))?)?,
396446
export: DEFAULT_EXPORT,
397-
module: &ValidatedModule::new(module)?,
447+
module,
448+
engine,
398449
scale_factor: 1.0,
399450
profile_opts: None,
400451
})?;
@@ -415,7 +466,8 @@ mod tests {
415466
function_path: Path::new("tests/fixtures/build/log_truncation_function.wasm")
416467
.to_path_buf(),
417468
export: DEFAULT_EXPORT,
418-
module: &ValidatedModule::new(module)?,
469+
module,
470+
engine,
419471
scale_factor: 1.0,
420472
profile_opts: None,
421473
})?;
@@ -440,7 +492,8 @@ mod tests {
440492
function_path: file_path.to_path_buf(),
441493
input: json_input(&serde_json::to_vec(&json!({ "code": 0 }))?)?,
442494
export: DEFAULT_EXPORT,
443-
module: &ValidatedModule::new(module)?,
495+
module,
496+
engine,
444497
scale_factor: 1.0,
445498
profile_opts: None,
446499
})?;
@@ -468,7 +521,8 @@ mod tests {
468521
function_path: trampolined_path.to_path_buf(),
469522
input: input_bytes.unwrap(),
470523
export: DEFAULT_EXPORT,
471-
module: &ValidatedModule::new(module)?,
524+
module,
525+
engine,
472526
scale_factor: 1.0,
473527
profile_opts: None,
474528
});

‎src/main.rs‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use anyhow::{anyhow, Result};
1111
use clap::Parser;
1212
use function_runner::{
1313
bluejay_schema_analyzer::BluejaySchemaAnalyzer,
14-
engine::{run, FunctionRunParams, ProfileOpts, ValidatedModule},
14+
engine::{run, FunctionRunParams, ProfileOpts},
1515
};
1616

1717
use is_terminal::IsTerminal;
@@ -144,8 +144,6 @@ fn main() -> Result<()> {
144144
Codec::Json
145145
};
146146

147-
let module = ValidatedModule::new(module)?;
148-
149147
let input = BytesContainer::new(BytesContainerType::Input, codec, buffer)?;
150148
let scale_factor = if let (Some(schema_string), Some(query_string), Some(json_value)) =
151149
(schema_string, query_string, input.json_value.clone())
@@ -169,7 +167,8 @@ fn main() -> Result<()> {
169167
export: opts.export.as_ref(),
170168
profile_opts: profile_opts.as_ref(),
171169
scale_factor,
172-
module: &module,
170+
module,
171+
engine,
173172
})?;
174173

175174
if opts.json {

‎tests/integration_tests.rs‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,24 @@ mod tests {
4141
Ok(())
4242
}
4343

44+
#[test]
45+
fn invalid_json_input_precedes_invalid_imports() -> Result<()> {
46+
let mut cmd = Command::new(cargo_bin!());
47+
48+
cmd.args([
49+
"--function",
50+
"tests/fixtures/build/invalid_import_combination.wasm",
51+
"--input",
52+
"tests/fixtures/input/invalid_json.json",
53+
"--json",
54+
]);
55+
cmd.assert()
56+
.failure()
57+
.stderr("Error: Invalid input JSON: EOF while parsing an object at line 2 column 0\n");
58+
59+
Ok(())
60+
}
61+
4462
#[test]
4563
fn run_stdin() -> Result<()> {
4664
let mut cmd = Command::new(cargo_bin!());

0 commit comments

Comments
 (0)