@wasm-oj/server 0.2.3 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/crates/runtime-core/src/deterministic.rs +27 -1
- package/crates/runtime-core/src/interactive.rs +365 -110
- package/crates/runtime-core/src/meter.rs +736 -66
- package/crates/runtime-core/src/run/mod.rs +2 -0
- package/crates/runtime-core/src/run/native.rs +5 -2
- package/crates/runtime-core/src/run/web.rs +39 -13
- package/crates/runtime-core/src/run/web_interactive.rs +375 -0
- package/crates/runtime-core/src/web.rs +52 -6
- package/dist/chunks/process-keepalive-BaXpMTap.js +191 -0
- package/dist/index.js +107 -18
- package/dist/server-build-stage.mjs +11 -94
- package/dist/server-runner-stage.mjs +73 -4
- package/package.json +3 -3
- package/dist/chunks/process-keepalive-BWLUz55N.js +0 -16
|
@@ -4,8 +4,7 @@ use crate::filesystem::{
|
|
|
4
4
|
RuntimeProjectFilesystem, is_normalized_guest_path, runtime_project_files,
|
|
5
5
|
};
|
|
6
6
|
use crate::meter::{
|
|
7
|
-
|
|
8
|
-
instrument_wasm_with_host_meter,
|
|
7
|
+
CostPoints, MeterState, attach_safepoint, instrument_wasm, meter_state, remaining_points,
|
|
9
8
|
};
|
|
10
9
|
use crate::module_imports::attach_declared_memory_imports;
|
|
11
10
|
use crate::module_policy::{
|
|
@@ -17,6 +16,7 @@ use crate::{
|
|
|
17
16
|
ExecutionTermination, InteractiveMetrics, InteractiveProcessResult, InteractiveProgram,
|
|
18
17
|
InteractiveRequest, InteractiveResult, RunError,
|
|
19
18
|
};
|
|
19
|
+
use futures::channel::oneshot;
|
|
20
20
|
use std::collections::{BTreeMap, HashMap};
|
|
21
21
|
use std::io;
|
|
22
22
|
use std::pin::Pin;
|
|
@@ -24,75 +24,17 @@ use std::sync::{Arc, Mutex};
|
|
|
24
24
|
use std::task::{Context, Poll};
|
|
25
25
|
use tokio::io::{AsyncRead, AsyncSeek, AsyncWrite, ReadBuf};
|
|
26
26
|
use virtual_fs::{FsError, Pipe, PipeRx, PipeTx, VirtualFile};
|
|
27
|
-
use wasmer::{
|
|
28
|
-
AsStoreMut, Engine, Function, FunctionEnv, FunctionEnvMut, Imports, Memory, RuntimeError,
|
|
29
|
-
};
|
|
27
|
+
use wasmer::{AsStoreMut, Engine, Imports, Memory};
|
|
30
28
|
use wasmer_types::StoreId;
|
|
31
|
-
use wasmer_wasix::bin_factory::
|
|
29
|
+
use wasmer_wasix::bin_factory::{run_exec, spawn_load_module};
|
|
30
|
+
use wasmer_wasix::os::task::TaskJoinHandle;
|
|
32
31
|
use wasmer_wasix::runtime::module_cache::{self, HashedModuleData, ModuleCache};
|
|
32
|
+
use wasmer_wasix::runtime::task_manager::TaskWasm;
|
|
33
33
|
use wasmer_wasix::{
|
|
34
34
|
PluggableRuntime, Runtime, WasiEnv, WasiFunctionEnv, WasiRuntimeError, WasiVersion,
|
|
35
35
|
generate_import_object_from_env,
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
-
#[derive(Debug)]
|
|
39
|
-
struct GasBudget {
|
|
40
|
-
initial: u64,
|
|
41
|
-
state: Mutex<GasState>,
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
#[derive(Debug)]
|
|
45
|
-
struct GasState {
|
|
46
|
-
remaining: u64,
|
|
47
|
-
exhausted: bool,
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
impl GasBudget {
|
|
51
|
-
fn new(initial: u64) -> Self {
|
|
52
|
-
Self {
|
|
53
|
-
initial,
|
|
54
|
-
state: Mutex::new(GasState {
|
|
55
|
-
remaining: initial,
|
|
56
|
-
exhausted: false,
|
|
57
|
-
}),
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
fn charge(&self, amount: u64) -> Result<(), RuntimeError> {
|
|
62
|
-
let mut state = self
|
|
63
|
-
.state
|
|
64
|
-
.lock()
|
|
65
|
-
.map_err(|error| RuntimeError::new(error.to_string()))?;
|
|
66
|
-
if amount > state.remaining {
|
|
67
|
-
state.remaining = 0;
|
|
68
|
-
state.exhausted = true;
|
|
69
|
-
return Err(RuntimeError::new("WASM-OJ instruction budget exhausted"));
|
|
70
|
-
}
|
|
71
|
-
state.remaining -= amount;
|
|
72
|
-
Ok(())
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
fn metrics(&self) -> Result<(u64, bool), RunError> {
|
|
76
|
-
let state = self
|
|
77
|
-
.state
|
|
78
|
-
.lock()
|
|
79
|
-
.map_err(|error| RunError::Runtime(error.to_string()))?;
|
|
80
|
-
Ok((
|
|
81
|
-
self.initial.saturating_sub(state.remaining),
|
|
82
|
-
state.exhausted,
|
|
83
|
-
))
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
#[derive(Clone)]
|
|
88
|
-
struct GasEnv(Arc<GasBudget>);
|
|
89
|
-
|
|
90
|
-
fn charge_gas(env: FunctionEnvMut<GasEnv>, amount: i64) -> Result<(), RuntimeError> {
|
|
91
|
-
let amount = u64::try_from(amount)
|
|
92
|
-
.map_err(|_| RuntimeError::new("WASM-OJ received a negative instruction charge"))?;
|
|
93
|
-
env.data().0.charge(amount)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
38
|
#[derive(Debug)]
|
|
97
39
|
struct InteractiveInput {
|
|
98
40
|
pipe: PipeRx,
|
|
@@ -181,6 +123,9 @@ struct InteractiveOutput {
|
|
|
181
123
|
}
|
|
182
124
|
|
|
183
125
|
impl AsyncWrite for InteractiveOutput {
|
|
126
|
+
/// Once the peer has closed its stdin, for example by exiting, the pipe reports a broken
|
|
127
|
+
/// pipe. The bytes are already in the transcript, so the write succeeds and they are dropped,
|
|
128
|
+
/// as when a judge keeps draining a pipe whose reader is gone.
|
|
184
129
|
fn poll_write(
|
|
185
130
|
mut self: Pin<&mut Self>,
|
|
186
131
|
context: &mut Context<'_>,
|
|
@@ -188,7 +133,12 @@ impl AsyncWrite for InteractiveOutput {
|
|
|
188
133
|
) -> Poll<io::Result<usize>> {
|
|
189
134
|
match Pin::new(&mut self.capture).poll_write(context, buffer) {
|
|
190
135
|
Poll::Ready(Ok(written)) => {
|
|
191
|
-
Pin::new(&mut self.pipe).poll_write(context, &buffer[..written])
|
|
136
|
+
match Pin::new(&mut self.pipe).poll_write(context, &buffer[..written]) {
|
|
137
|
+
Poll::Ready(Err(error)) if error.kind() == io::ErrorKind::BrokenPipe => {
|
|
138
|
+
Poll::Ready(Ok(written))
|
|
139
|
+
}
|
|
140
|
+
result => result,
|
|
141
|
+
}
|
|
192
142
|
}
|
|
193
143
|
result => result,
|
|
194
144
|
}
|
|
@@ -263,7 +213,8 @@ impl VirtualFile for InteractiveOutput {
|
|
|
263
213
|
struct PreparedProgram {
|
|
264
214
|
wasm: Vec<u8>,
|
|
265
215
|
operations: BTreeMap<String, u64>,
|
|
266
|
-
|
|
216
|
+
budget: u64,
|
|
217
|
+
meter: Arc<Mutex<Option<MeterState>>>,
|
|
267
218
|
protocol: OutputCapture,
|
|
268
219
|
stderr: OutputCapture,
|
|
269
220
|
filesystem: RuntimeProjectFilesystem,
|
|
@@ -272,16 +223,8 @@ struct PreparedProgram {
|
|
|
272
223
|
|
|
273
224
|
pub async fn interact(request: InteractiveRequest) -> Result<InteractiveResult, RunError> {
|
|
274
225
|
validate_request(&request)?;
|
|
275
|
-
let contestant = prepare_program(
|
|
276
|
-
|
|
277
|
-
CONTESTANT_METERING_MODULE,
|
|
278
|
-
&request.determinism,
|
|
279
|
-
)?;
|
|
280
|
-
let interactor = prepare_program(
|
|
281
|
-
&request.interactor,
|
|
282
|
-
INTERACTOR_METERING_MODULE,
|
|
283
|
-
&request.determinism,
|
|
284
|
-
)?;
|
|
226
|
+
let contestant = prepare_program(&request.contestant, &request.determinism)?;
|
|
227
|
+
let interactor = prepare_program(&request.interactor, &request.determinism)?;
|
|
285
228
|
let maximum_memory = request
|
|
286
229
|
.contestant
|
|
287
230
|
.resources
|
|
@@ -293,7 +236,7 @@ pub async fn interact(request: InteractiveRequest) -> Result<InteractiveResult,
|
|
|
293
236
|
let contestant_runtime = interactive_runtime(
|
|
294
237
|
engine.clone(),
|
|
295
238
|
&request.determinism,
|
|
296
|
-
contestant.
|
|
239
|
+
contestant.meter.clone(),
|
|
297
240
|
contestant.clock.clone(),
|
|
298
241
|
request.contestant.startup_entropy_bytes,
|
|
299
242
|
module_cache.clone(),
|
|
@@ -303,7 +246,7 @@ pub async fn interact(request: InteractiveRequest) -> Result<InteractiveResult,
|
|
|
303
246
|
let interactor_runtime = interactive_runtime(
|
|
304
247
|
engine,
|
|
305
248
|
&request.determinism,
|
|
306
|
-
interactor.
|
|
249
|
+
interactor.meter.clone(),
|
|
307
250
|
interactor.clock.clone(),
|
|
308
251
|
request.interactor.startup_entropy_bytes,
|
|
309
252
|
module_cache,
|
|
@@ -334,36 +277,38 @@ pub async fn interact(request: InteractiveRequest) -> Result<InteractiveResult,
|
|
|
334
277
|
*interactor_wasi
|
|
335
278
|
.lock()
|
|
336
279
|
.map_err(|error| RunError::Runtime(error.to_string()))? = Some(interactor_env.clone());
|
|
337
|
-
let mut contestant_handle =
|
|
338
|
-
HashedModuleData::new(contestant.wasm.clone()),
|
|
280
|
+
let (mut contestant_handle, contestant_points) = spawn_metered(
|
|
339
281
|
"contestant",
|
|
282
|
+
&contestant,
|
|
340
283
|
contestant_env,
|
|
341
284
|
&contestant_runtime,
|
|
342
285
|
)
|
|
343
|
-
.await
|
|
344
|
-
|
|
345
|
-
let mut interactor_handle = spawn_exec_wasm(
|
|
346
|
-
HashedModuleData::new(interactor.wasm.clone()),
|
|
286
|
+
.await?;
|
|
287
|
+
let (mut interactor_handle, interactor_points) = spawn_metered(
|
|
347
288
|
"interactor",
|
|
289
|
+
&interactor,
|
|
348
290
|
interactor_env,
|
|
349
291
|
&interactor_runtime,
|
|
350
292
|
)
|
|
351
|
-
.await
|
|
352
|
-
|
|
353
|
-
let (contestant_status, interactor_status) = futures::join!(
|
|
293
|
+
.await?;
|
|
294
|
+
let (contestant_status, interactor_status, contestant_points, interactor_points) = futures::join!(
|
|
354
295
|
contestant_handle.wait_finished(),
|
|
355
296
|
interactor_handle.wait_finished(),
|
|
297
|
+
contestant_points,
|
|
298
|
+
interactor_points,
|
|
356
299
|
);
|
|
357
300
|
|
|
358
301
|
let contestant_to_interactor = contestant.protocol.bytes();
|
|
359
302
|
let interactor_to_contestant = interactor.protocol.bytes();
|
|
360
303
|
let contestant_result = process_result(
|
|
361
304
|
contestant_status,
|
|
305
|
+
contestant_points,
|
|
362
306
|
contestant,
|
|
363
307
|
contestant_to_interactor.len(),
|
|
364
308
|
)?;
|
|
365
309
|
let interactor_result = process_result(
|
|
366
310
|
interactor_status,
|
|
311
|
+
interactor_points,
|
|
367
312
|
interactor,
|
|
368
313
|
interactor_to_contestant.len(),
|
|
369
314
|
)?;
|
|
@@ -376,15 +321,56 @@ pub async fn interact(request: InteractiveRequest) -> Result<InteractiveResult,
|
|
|
376
321
|
})
|
|
377
322
|
}
|
|
378
323
|
|
|
324
|
+
/// `spawn_exec_wasm` without its CLOEXEC sweep, which a fresh environment does not need, plus a
|
|
325
|
+
/// recycle hook that reads the meter from the store after the process exits. WASIX reports the
|
|
326
|
+
/// exit before it recycles the store, so callers await both.
|
|
327
|
+
async fn spawn_metered(
|
|
328
|
+
name: &str,
|
|
329
|
+
program: &PreparedProgram,
|
|
330
|
+
env: WasiEnv,
|
|
331
|
+
runtime: &Arc<dyn Runtime + Send + Sync>,
|
|
332
|
+
) -> Result<
|
|
333
|
+
(
|
|
334
|
+
TaskJoinHandle,
|
|
335
|
+
oneshot::Receiver<Result<CostPoints, String>>,
|
|
336
|
+
),
|
|
337
|
+
RunError,
|
|
338
|
+
> {
|
|
339
|
+
let module = spawn_load_module(name, HashedModuleData::new(program.wasm.clone()), runtime)
|
|
340
|
+
.await
|
|
341
|
+
.map_err(|error| RunError::Compile(format!("failed to start {name}: {error}")))?;
|
|
342
|
+
let finished = env.thread.join_handle();
|
|
343
|
+
let meter = program.meter.clone();
|
|
344
|
+
let (points, received) = oneshot::channel();
|
|
345
|
+
let task = TaskWasm::new(Box::new(run_exec), env, module, true, true).with_recycle(Box::new(
|
|
346
|
+
move |mut exited| {
|
|
347
|
+
let read = meter
|
|
348
|
+
.lock()
|
|
349
|
+
.map_err(|error| error.to_string())
|
|
350
|
+
.and_then(|meter| {
|
|
351
|
+
meter
|
|
352
|
+
.clone()
|
|
353
|
+
.ok_or_else(|| "meter is unavailable".to_string())
|
|
354
|
+
})
|
|
355
|
+
.and_then(|meter| remaining_points(&mut exited.store, &meter));
|
|
356
|
+
let _ = points.send(read);
|
|
357
|
+
},
|
|
358
|
+
));
|
|
359
|
+
runtime
|
|
360
|
+
.task_manager()
|
|
361
|
+
.task_wasm(task)
|
|
362
|
+
.map_err(|error| RunError::Compile(format!("failed to start {name}: {error}")))?;
|
|
363
|
+
Ok((finished, received))
|
|
364
|
+
}
|
|
365
|
+
|
|
379
366
|
fn prepare_program(
|
|
380
367
|
program: &InteractiveProgram,
|
|
381
|
-
metering_module: &'static str,
|
|
382
368
|
determinism: &crate::DeterminismConfig,
|
|
383
369
|
) -> Result<PreparedProgram, RunError> {
|
|
384
370
|
let limited = enforce_memory_limit(&program.wasm, program.resources.memory_limit_bytes)
|
|
385
371
|
.map_err(RunError::Compile)?;
|
|
386
|
-
let metered =
|
|
387
|
-
|
|
372
|
+
let metered = instrument_wasm(&limited, program.resources.instruction_budget)
|
|
373
|
+
.map_err(RunError::Compile)?;
|
|
388
374
|
let executable = defer_start_section(&metered.wasm).map_err(RunError::Compile)?;
|
|
389
375
|
let wasm =
|
|
390
376
|
rewrite_interactive_deterministic_imports(&executable.wasm).map_err(RunError::Compile)?;
|
|
@@ -397,7 +383,8 @@ fn prepare_program(
|
|
|
397
383
|
Ok(PreparedProgram {
|
|
398
384
|
wasm,
|
|
399
385
|
operations: metered.operations,
|
|
400
|
-
|
|
386
|
+
budget: program.resources.instruction_budget,
|
|
387
|
+
meter: Arc::new(Mutex::new(None)),
|
|
401
388
|
protocol,
|
|
402
389
|
stderr,
|
|
403
390
|
filesystem,
|
|
@@ -443,12 +430,20 @@ fn build_environment(
|
|
|
443
430
|
|
|
444
431
|
fn process_result(
|
|
445
432
|
status: Result<wasmer_wasix::wasmer_wasix_types::wasi::ExitCode, Arc<WasiRuntimeError>>,
|
|
433
|
+
points: Result<Result<CostPoints, String>, oneshot::Canceled>,
|
|
446
434
|
prepared: PreparedProgram,
|
|
447
435
|
protocol_bytes: usize,
|
|
448
436
|
) -> Result<InteractiveProcessResult, RunError> {
|
|
449
437
|
let stderr = prepared.stderr.bytes();
|
|
450
438
|
let output_exceeded = prepared.protocol.exceeded() || prepared.stderr.exceeded();
|
|
451
|
-
let
|
|
439
|
+
let points = points
|
|
440
|
+
.map_err(|_| RunError::Runtime("interactive process exited without its meter".to_string()))?
|
|
441
|
+
.map_err(RunError::Runtime)?;
|
|
442
|
+
let exhausted = points == CostPoints::Exhausted;
|
|
443
|
+
let cost = match points {
|
|
444
|
+
CostPoints::Remaining(points) => prepared.budget.saturating_sub(points),
|
|
445
|
+
CostPoints::Exhausted => prepared.budget,
|
|
446
|
+
};
|
|
452
447
|
let logical_time_exceeded = prepared.clock.limit_exceeded()?;
|
|
453
448
|
let (code, termination) = if prepared.filesystem.quota_exceeded() {
|
|
454
449
|
(137, ExecutionTermination::FilesystemLimit)
|
|
@@ -516,7 +511,7 @@ fn validate_request(request: &InteractiveRequest) -> Result<(), RunError> {
|
|
|
516
511
|
fn interactive_runtime(
|
|
517
512
|
engine: Engine,
|
|
518
513
|
determinism: &crate::DeterminismConfig,
|
|
519
|
-
|
|
514
|
+
meter: Arc<Mutex<Option<MeterState>>>,
|
|
520
515
|
clock: VirtualClock,
|
|
521
516
|
startup_entropy_bytes: u64,
|
|
522
517
|
module_cache: Arc<dyn ModuleCache + Send + Sync>,
|
|
@@ -578,22 +573,7 @@ fn interactive_runtime(
|
|
|
578
573
|
clock.clone(),
|
|
579
574
|
startup_entropy_bytes,
|
|
580
575
|
);
|
|
581
|
-
|
|
582
|
-
.imports()
|
|
583
|
-
.find_map(|import| match import.module() {
|
|
584
|
-
CONTESTANT_METERING_MODULE => Some(CONTESTANT_METERING_MODULE),
|
|
585
|
-
INTERACTOR_METERING_MODULE => Some(INTERACTOR_METERING_MODULE),
|
|
586
|
-
_ => None,
|
|
587
|
-
})
|
|
588
|
-
.ok_or_else(|| {
|
|
589
|
-
io::Error::other("interactive module has no WASM-OJ metering identity")
|
|
590
|
-
})?;
|
|
591
|
-
let gas_env = FunctionEnv::new(&mut *store, GasEnv(gas.clone()));
|
|
592
|
-
imports.define(
|
|
593
|
-
metering_module,
|
|
594
|
-
HOST_GAS_FUNCTION,
|
|
595
|
-
Function::new_typed_with_env(&mut *store, &gas_env, charge_gas),
|
|
596
|
-
);
|
|
576
|
+
attach_safepoint(store, &mut imports);
|
|
597
577
|
attach_capability_denials(store, module, &mut imports).map_err(io::Error::other)?;
|
|
598
578
|
attach_declared_memory_imports(store, module, &mut imports).map_err(io::Error::other)?;
|
|
599
579
|
Ok(imports)
|
|
@@ -616,6 +596,10 @@ fn interactive_runtime(
|
|
|
616
596
|
.memory
|
|
617
597
|
.lock()
|
|
618
598
|
.map_err(|error| io::Error::other(error.to_string()))? = Some(memory);
|
|
599
|
+
*meter
|
|
600
|
+
.lock()
|
|
601
|
+
.map_err(|error| io::Error::other(error.to_string()))? =
|
|
602
|
+
Some(meter_state(instance).map_err(io::Error::other)?);
|
|
619
603
|
pending
|
|
620
604
|
.wasi
|
|
621
605
|
.initialize(&mut *store, instance.clone())
|
|
@@ -673,7 +657,7 @@ mod tests {
|
|
|
673
657
|
use super::interact;
|
|
674
658
|
use crate::{
|
|
675
659
|
DeterminismConfig, ExecutionTermination, InteractiveProgram, InteractiveRequest,
|
|
676
|
-
ResourcePolicy,
|
|
660
|
+
ResourcePolicy, RunRequest,
|
|
677
661
|
};
|
|
678
662
|
use std::collections::BTreeMap;
|
|
679
663
|
|
|
@@ -998,6 +982,277 @@ mod tests {
|
|
|
998
982
|
assert_eq!(result.interactor.metrics.logical_time_ns, 0);
|
|
999
983
|
}
|
|
1000
984
|
|
|
985
|
+
#[test]
|
|
986
|
+
fn interactive_empty_stdin_poll_times_out_on_the_process_clock() {
|
|
987
|
+
let poller = wat::parse_str(
|
|
988
|
+
r#"(module
|
|
989
|
+
(import "wasi_snapshot_preview1" "poll_oneoff"
|
|
990
|
+
(func $poll (param i32 i32 i32 i32) (result i32)))
|
|
991
|
+
(import "wasi_snapshot_preview1" "fd_write"
|
|
992
|
+
(func $fd_write (param i32 i32 i32 i32) (result i32)))
|
|
993
|
+
(memory (export "memory") 1)
|
|
994
|
+
(func (export "_start")
|
|
995
|
+
i32.const 0 i64.const 1 i64.store
|
|
996
|
+
i32.const 8 i32.const 1 i32.store8
|
|
997
|
+
i32.const 16 i32.const 0 i32.store
|
|
998
|
+
i32.const 48 i64.const 2 i64.store
|
|
999
|
+
i32.const 56 i32.const 0 i32.store8
|
|
1000
|
+
i32.const 64 i32.const 1 i32.store
|
|
1001
|
+
i32.const 72 i64.const 5000000000 i64.store
|
|
1002
|
+
i32.const 80 i64.const 1 i64.store
|
|
1003
|
+
i32.const 88 i32.const 0 i32.store16
|
|
1004
|
+
i32.const 0 i32.const 128 i32.const 2 i32.const 240 call $poll drop
|
|
1005
|
+
i32.const 200 i32.const 138 i32.store
|
|
1006
|
+
i32.const 204 i32.const 1 i32.store
|
|
1007
|
+
i32.const 208 i32.const 240 i32.store
|
|
1008
|
+
i32.const 212 i32.const 1 i32.store
|
|
1009
|
+
i32.const 1 i32.const 200 i32.const 2 i32.const 216 call $fd_write drop))"#,
|
|
1010
|
+
)
|
|
1011
|
+
.unwrap();
|
|
1012
|
+
let listener = wat::parse_str(
|
|
1013
|
+
r#"(module
|
|
1014
|
+
(import "wasi_snapshot_preview1" "fd_read"
|
|
1015
|
+
(func $fd_read (param i32 i32 i32 i32) (result i32)))
|
|
1016
|
+
(memory (export "memory") 1)
|
|
1017
|
+
(func (export "_start")
|
|
1018
|
+
(i32.store (i32.const 0) (i32.const 64))
|
|
1019
|
+
(i32.store (i32.const 4) (i32.const 8))
|
|
1020
|
+
(drop (call $fd_read (i32.const 0) (i32.const 0) (i32.const 1) (i32.const 8)))))"#,
|
|
1021
|
+
)
|
|
1022
|
+
.unwrap();
|
|
1023
|
+
let runtime = tokio::runtime::Builder::new_current_thread()
|
|
1024
|
+
.enable_all()
|
|
1025
|
+
.build()
|
|
1026
|
+
.unwrap();
|
|
1027
|
+
let result = runtime
|
|
1028
|
+
.block_on(interact(InteractiveRequest {
|
|
1029
|
+
contestant: program(poller),
|
|
1030
|
+
interactor: program(listener),
|
|
1031
|
+
determinism: DeterminismConfig {
|
|
1032
|
+
random_seed: 7,
|
|
1033
|
+
realtime_epoch_ms: 946_684_800_000,
|
|
1034
|
+
clock_step_ns: 1_000_000,
|
|
1035
|
+
},
|
|
1036
|
+
}))
|
|
1037
|
+
.unwrap();
|
|
1038
|
+
|
|
1039
|
+
assert_eq!(result.contestant_to_interactor, [0, 1]);
|
|
1040
|
+
assert_eq!(result.contestant.termination, ExecutionTermination::Exited);
|
|
1041
|
+
assert_eq!(result.contestant.metrics.logical_time_ns, 5_000_000_000);
|
|
1042
|
+
assert_eq!(result.interactor.termination, ExecutionTermination::Exited);
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
#[test]
|
|
1046
|
+
fn interactive_contestant_is_metered_like_a_standalone_run() {
|
|
1047
|
+
let looping = |ending: &str| {
|
|
1048
|
+
wat::parse_str(format!(
|
|
1049
|
+
r#"(module
|
|
1050
|
+
(import "wasi_snapshot_preview1" "proc_exit" (func $exit (param i32)))
|
|
1051
|
+
(memory (export "memory") 1)
|
|
1052
|
+
(func (export "_start")
|
|
1053
|
+
(local $remaining i32)
|
|
1054
|
+
i32.const 1000 local.set $remaining
|
|
1055
|
+
(loop $again
|
|
1056
|
+
local.get $remaining i32.const 1 i32.sub local.tee $remaining
|
|
1057
|
+
br_if $again)
|
|
1058
|
+
{ending}))"#
|
|
1059
|
+
))
|
|
1060
|
+
.unwrap()
|
|
1061
|
+
};
|
|
1062
|
+
let determinism = DeterminismConfig {
|
|
1063
|
+
random_seed: 7,
|
|
1064
|
+
realtime_epoch_ms: 946_684_800_000,
|
|
1065
|
+
clock_step_ns: 1_000_000,
|
|
1066
|
+
};
|
|
1067
|
+
let execute = |wasm: &[u8], instruction_budget: u64| {
|
|
1068
|
+
let mut contestant = program(wasm.to_vec());
|
|
1069
|
+
contestant.resources.instruction_budget = instruction_budget;
|
|
1070
|
+
let standalone = crate::run(RunRequest {
|
|
1071
|
+
wasm: contestant.wasm.clone(),
|
|
1072
|
+
args: Vec::new(),
|
|
1073
|
+
env: BTreeMap::new(),
|
|
1074
|
+
stdin: Vec::new(),
|
|
1075
|
+
files: BTreeMap::new(),
|
|
1076
|
+
output_paths: Vec::new(),
|
|
1077
|
+
cwd: contestant.cwd.clone(),
|
|
1078
|
+
startup_entropy_bytes: 0,
|
|
1079
|
+
determinism: determinism.clone(),
|
|
1080
|
+
resources: contestant.resources.clone(),
|
|
1081
|
+
})
|
|
1082
|
+
.unwrap();
|
|
1083
|
+
let interactive = tokio::runtime::Builder::new_current_thread()
|
|
1084
|
+
.enable_all()
|
|
1085
|
+
.build()
|
|
1086
|
+
.unwrap()
|
|
1087
|
+
.block_on(interact(InteractiveRequest {
|
|
1088
|
+
interactor: contestant.clone(),
|
|
1089
|
+
contestant,
|
|
1090
|
+
determinism: determinism.clone(),
|
|
1091
|
+
}))
|
|
1092
|
+
.unwrap();
|
|
1093
|
+
assert_eq!(interactive.interactor.code, interactive.contestant.code);
|
|
1094
|
+
assert_eq!(
|
|
1095
|
+
interactive.interactor.termination,
|
|
1096
|
+
interactive.contestant.termination
|
|
1097
|
+
);
|
|
1098
|
+
assert_eq!(
|
|
1099
|
+
interactive.interactor.metrics.cost,
|
|
1100
|
+
interactive.contestant.metrics.cost
|
|
1101
|
+
);
|
|
1102
|
+
(standalone, interactive.contestant)
|
|
1103
|
+
};
|
|
1104
|
+
|
|
1105
|
+
for (ending, code) in [("", 0), ("i32.const 3 call $exit", 3)] {
|
|
1106
|
+
let wasm = looping(ending);
|
|
1107
|
+
let (standalone, interactive) = execute(&wasm, 1_000_000);
|
|
1108
|
+
assert_eq!(standalone.termination, ExecutionTermination::Exited);
|
|
1109
|
+
assert_eq!(standalone.code, code);
|
|
1110
|
+
assert_eq!(interactive.termination, ExecutionTermination::Exited);
|
|
1111
|
+
assert_eq!(interactive.code, code);
|
|
1112
|
+
assert_eq!(interactive.metrics.cost, standalone.metrics.cost);
|
|
1113
|
+
|
|
1114
|
+
let budget = standalone.metrics.cost - 1;
|
|
1115
|
+
let (standalone, interactive) = execute(&wasm, budget);
|
|
1116
|
+
assert_eq!(
|
|
1117
|
+
standalone.termination,
|
|
1118
|
+
ExecutionTermination::InstructionLimit
|
|
1119
|
+
);
|
|
1120
|
+
assert_eq!(
|
|
1121
|
+
interactive.termination,
|
|
1122
|
+
ExecutionTermination::InstructionLimit
|
|
1123
|
+
);
|
|
1124
|
+
assert_eq!(interactive.code, 137);
|
|
1125
|
+
assert_eq!(interactive.metrics.cost, budget);
|
|
1126
|
+
assert_eq!(interactive.metrics.cost, standalone.metrics.cost);
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
let (standalone, interactive) = execute(&looping("unreachable"), 1_000_000);
|
|
1130
|
+
assert_eq!(standalone.termination, ExecutionTermination::Trap);
|
|
1131
|
+
assert_eq!(interactive.metrics.cost, standalone.metrics.cost);
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
const DRAIN_STDIN: &str = r#"
|
|
1135
|
+
(func $drain_stdin (result i32)
|
|
1136
|
+
(local $total i32)
|
|
1137
|
+
(loop $again
|
|
1138
|
+
(i32.store (i32.const 0) (i32.add (i32.const 256) (local.get $total)))
|
|
1139
|
+
(i32.store (i32.const 4) (i32.const 64))
|
|
1140
|
+
(if (call $fd_read (i32.const 0) (i32.const 0) (i32.const 1) (i32.const 8))
|
|
1141
|
+
(then (call $exit (i32.const 3))))
|
|
1142
|
+
(local.set $total (i32.add (local.get $total) (i32.load (i32.const 8))))
|
|
1143
|
+
(br_if $again (i32.load (i32.const 8))))
|
|
1144
|
+
local.get $total)"#;
|
|
1145
|
+
|
|
1146
|
+
fn peer_program(body: &str, data: &str) -> Vec<u8> {
|
|
1147
|
+
wat::parse_str(format!(
|
|
1148
|
+
r#"(module
|
|
1149
|
+
(import "wasi_snapshot_preview1" "fd_read"
|
|
1150
|
+
(func $fd_read (param i32 i32 i32 i32) (result i32)))
|
|
1151
|
+
(import "wasi_snapshot_preview1" "fd_write"
|
|
1152
|
+
(func $fd_write (param i32 i32 i32 i32) (result i32)))
|
|
1153
|
+
(import "wasi_snapshot_preview1" "proc_exit" (func $exit (param i32)))
|
|
1154
|
+
(memory (export "memory") 1)
|
|
1155
|
+
(data (i32.const 128) "{data}")
|
|
1156
|
+
{DRAIN_STDIN}
|
|
1157
|
+
(func $write (param $length i32) (result i32)
|
|
1158
|
+
(i32.store (i32.const 16) (i32.const 128))
|
|
1159
|
+
(i32.store (i32.const 20) (local.get $length))
|
|
1160
|
+
(call $fd_write (i32.const 1) (i32.const 16) (i32.const 1) (i32.const 24)))
|
|
1161
|
+
(func (export "_start") {body}))"#
|
|
1162
|
+
))
|
|
1163
|
+
.unwrap()
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
fn interact_pair(contestant: Vec<u8>, interactor: Vec<u8>) -> crate::InteractiveResult {
|
|
1167
|
+
tokio::runtime::Builder::new_current_thread()
|
|
1168
|
+
.enable_all()
|
|
1169
|
+
.build()
|
|
1170
|
+
.unwrap()
|
|
1171
|
+
.block_on(interact(InteractiveRequest {
|
|
1172
|
+
contestant: program(contestant),
|
|
1173
|
+
interactor: program(interactor),
|
|
1174
|
+
determinism: DeterminismConfig {
|
|
1175
|
+
random_seed: 7,
|
|
1176
|
+
realtime_epoch_ms: 946_684_800_000,
|
|
1177
|
+
clock_step_ns: 1_000_000,
|
|
1178
|
+
},
|
|
1179
|
+
}))
|
|
1180
|
+
.unwrap()
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
#[test]
|
|
1184
|
+
fn interactor_writes_to_an_exited_contestant_without_a_broken_pipe() {
|
|
1185
|
+
let contestant = peer_program("(drop (call $write (i32.const 2)))", "1\\n");
|
|
1186
|
+
let interactor = peer_program(
|
|
1187
|
+
r#"(local $errno i32)
|
|
1188
|
+
(drop (call $drain_stdin))
|
|
1189
|
+
(local.set $errno (call $write (i32.const 8)))
|
|
1190
|
+
(if (local.get $errno) (then (call $exit (local.get $errno))))
|
|
1191
|
+
(if (i32.ne (i32.load (i32.const 24)) (i32.const 8)) (then (call $exit (i32.const 4))))
|
|
1192
|
+
(call $exit (i32.const 42))"#,
|
|
1193
|
+
"correct\\n",
|
|
1194
|
+
);
|
|
1195
|
+
|
|
1196
|
+
let result = interact_pair(contestant, interactor);
|
|
1197
|
+
|
|
1198
|
+
assert_eq!(result.contestant.termination, ExecutionTermination::Exited);
|
|
1199
|
+
assert_eq!(result.contestant.code, 0);
|
|
1200
|
+
assert_eq!(result.interactor.termination, ExecutionTermination::Exited);
|
|
1201
|
+
assert_eq!(result.interactor.code, 42);
|
|
1202
|
+
assert_eq!(result.contestant_to_interactor, b"1\n");
|
|
1203
|
+
assert_eq!(result.interactor_to_contestant, b"correct\n");
|
|
1204
|
+
assert_eq!(result.interactor.metrics.protocol_bytes, 8);
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
#[test]
|
|
1208
|
+
fn contestant_reads_buffered_bytes_then_eof_after_the_interactor_exits() {
|
|
1209
|
+
let contestant = peer_program(
|
|
1210
|
+
r#"(local $total i32)
|
|
1211
|
+
(local.set $total (call $drain_stdin))
|
|
1212
|
+
(call $exit (select (i32.const 0) (i32.const 1)
|
|
1213
|
+
(i32.and
|
|
1214
|
+
(i32.eq (local.get $total) (i32.const 4))
|
|
1215
|
+
(i32.eq (i32.load (i32.const 256)) (i32.const 0x0a657962)))))"#,
|
|
1216
|
+
"",
|
|
1217
|
+
);
|
|
1218
|
+
let interactor = peer_program("(drop (call $write (i32.const 4)))", "bye\\n");
|
|
1219
|
+
|
|
1220
|
+
let result = interact_pair(contestant, interactor);
|
|
1221
|
+
|
|
1222
|
+
assert_eq!(result.interactor.termination, ExecutionTermination::Exited);
|
|
1223
|
+
assert_eq!(result.interactor.code, 0);
|
|
1224
|
+
assert_eq!(result.contestant.termination, ExecutionTermination::Exited);
|
|
1225
|
+
assert_eq!(result.contestant.code, 0);
|
|
1226
|
+
assert_eq!(result.interactor_to_contestant, b"bye\n");
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
#[test]
|
|
1230
|
+
fn contestant_writes_to_an_exited_interactor_without_a_broken_pipe() {
|
|
1231
|
+
let contestant = peer_program(
|
|
1232
|
+
r#"(local $round i32)
|
|
1233
|
+
(local $errno i32)
|
|
1234
|
+
(drop (call $drain_stdin))
|
|
1235
|
+
(loop $again
|
|
1236
|
+
(local.set $errno (call $write (i32.const 2)))
|
|
1237
|
+
(if (local.get $errno) (then (call $exit (local.get $errno))))
|
|
1238
|
+
(if (i32.ne (i32.load (i32.const 24)) (i32.const 2)) (then (call $exit (i32.const 4))))
|
|
1239
|
+
(local.set $round (i32.add (local.get $round) (i32.const 1)))
|
|
1240
|
+
(br_if $again (i32.lt_u (local.get $round) (i32.const 3))))
|
|
1241
|
+
(call $exit (i32.const 42))"#,
|
|
1242
|
+
"x\\n",
|
|
1243
|
+
);
|
|
1244
|
+
let interactor = peer_program("(drop (call $write (i32.const 4)))", "bye\\n");
|
|
1245
|
+
|
|
1246
|
+
let result = interact_pair(contestant, interactor);
|
|
1247
|
+
|
|
1248
|
+
assert_eq!(result.interactor.termination, ExecutionTermination::Exited);
|
|
1249
|
+
assert_eq!(result.interactor.code, 0);
|
|
1250
|
+
assert_eq!(result.contestant.termination, ExecutionTermination::Exited);
|
|
1251
|
+
assert_eq!(result.contestant.code, 42);
|
|
1252
|
+
assert_eq!(result.contestant_to_interactor, b"x\nx\nx\n");
|
|
1253
|
+
assert_eq!(result.interactor_to_contestant, b"bye\n");
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1001
1256
|
fn program(wasm: Vec<u8>) -> InteractiveProgram {
|
|
1002
1257
|
InteractiveProgram {
|
|
1003
1258
|
wasm,
|