@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
|
@@ -15,6 +15,8 @@ mod native;
|
|
|
15
15
|
#[cfg(target_arch = "wasm32")]
|
|
16
16
|
mod web;
|
|
17
17
|
#[cfg(target_arch = "wasm32")]
|
|
18
|
+
pub(crate) mod web_interactive;
|
|
19
|
+
#[cfg(target_arch = "wasm32")]
|
|
18
20
|
pub(crate) mod web_runtime;
|
|
19
21
|
|
|
20
22
|
pub fn run(request: RunRequest) -> Result<RunResult, RunError> {
|
|
@@ -2,7 +2,9 @@ use crate::capabilities::attach_capability_denials;
|
|
|
2
2
|
use crate::deterministic::{VirtualClock, attach_deterministic_imports};
|
|
3
3
|
use crate::filesystem::{read_files_bounded, runtime_project_files};
|
|
4
4
|
use crate::memory::LimitingTunables;
|
|
5
|
-
use crate::meter::{
|
|
5
|
+
use crate::meter::{
|
|
6
|
+
CostPoints, METER_MODEL, attach_safepoint, instrument_wasm, meter_state, remaining_points,
|
|
7
|
+
};
|
|
6
8
|
use crate::module_imports::attach_imported_memory;
|
|
7
9
|
use crate::module_policy::{DEFERRED_START_EXPORT, defer_start_section, enforce_memory_limit};
|
|
8
10
|
use crate::output::{OutputBudget, OutputCapture};
|
|
@@ -105,13 +107,14 @@ fn run_in_runtime(request: RunRequest) -> Result<RunResult, RunError> {
|
|
|
105
107
|
clock.clone(),
|
|
106
108
|
request.startup_entropy_bytes,
|
|
107
109
|
);
|
|
110
|
+
attach_safepoint(&mut store, &mut imports);
|
|
108
111
|
attach_capability_denials(&mut store, &module, &mut imports).map_err(RunError::Compile)?;
|
|
109
112
|
let imported_memory =
|
|
110
113
|
attach_imported_memory(&mut store, &module, &mut imports).map_err(RunError::Compile)?;
|
|
111
114
|
|
|
112
115
|
let instance = Instance::new(&mut store, &module, &imports)
|
|
113
116
|
.map_err(|error| RunError::Compile(format!("failed to instantiate module: {error}")))?;
|
|
114
|
-
let meter = meter_state(&
|
|
117
|
+
let meter = meter_state(&instance).map_err(RunError::Runtime)?;
|
|
115
118
|
let guest_memory = instance
|
|
116
119
|
.exports
|
|
117
120
|
.get_memory("memory")
|
|
@@ -2,13 +2,16 @@ use super::web_runtime::runtime_with_engine;
|
|
|
2
2
|
use crate::capabilities::attach_capability_denials;
|
|
3
3
|
use crate::deterministic::{VirtualClock, attach_deterministic_imports};
|
|
4
4
|
use crate::filesystem::{read_files_bounded, runtime_project_files};
|
|
5
|
-
use crate::meter::{
|
|
5
|
+
use crate::meter::{
|
|
6
|
+
CostPoints, METER_MODEL, attach_safepoint, instrument_wasm, meter_state, remaining_points,
|
|
7
|
+
};
|
|
6
8
|
use crate::module_imports::attach_imported_memory;
|
|
7
9
|
use crate::module_policy::{DEFERRED_START_EXPORT, defer_start_section, enforce_memory_limit};
|
|
8
|
-
use crate::output::{OutputBudget, OutputCapture};
|
|
10
|
+
use crate::output::{CappedOutput, OutputBudget, OutputCapture};
|
|
9
11
|
use crate::{ExecutionMetrics, ExecutionTermination, RunError, RunRequest, RunResult};
|
|
10
12
|
use std::io::Write;
|
|
11
13
|
use std::sync::{Arc, Mutex};
|
|
14
|
+
use virtual_fs::VirtualFile;
|
|
12
15
|
use wasmer::{Instance, Memory, Module, Store};
|
|
13
16
|
use wasmer_wasix::{
|
|
14
17
|
Pipe, WasiEnv, WasiError, WasiModuleInstanceHandles, WasiModuleTreeHandles, wasmer_wasix_types,
|
|
@@ -16,8 +19,29 @@ use wasmer_wasix::{
|
|
|
16
19
|
|
|
17
20
|
pub fn run(
|
|
18
21
|
request: RunRequest,
|
|
19
|
-
|
|
22
|
+
on_execution: impl FnMut(bool) -> Result<(), RunError>,
|
|
20
23
|
) -> Result<RunResult, RunError> {
|
|
24
|
+
let (mut stdin_writer, stdin_reader) = Pipe::channel();
|
|
25
|
+
stdin_writer
|
|
26
|
+
.write_all(&request.stdin)
|
|
27
|
+
.map_err(|error| RunError::Io(error.to_string()))?;
|
|
28
|
+
drop(stdin_writer);
|
|
29
|
+
execute(request, Box::new(stdin_reader), None, on_execution).map(|execution| execution.result)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
pub(super) type StdoutPeer = Box<dyn FnOnce(CappedOutput) -> Box<dyn VirtualFile + Send + Sync>>;
|
|
33
|
+
|
|
34
|
+
pub(super) struct Execution {
|
|
35
|
+
pub result: RunResult,
|
|
36
|
+
pub exit_code: Option<i32>,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
pub(super) fn execute(
|
|
40
|
+
request: RunRequest,
|
|
41
|
+
stdin: Box<dyn VirtualFile + Send + Sync>,
|
|
42
|
+
stdout_peer: Option<StdoutPeer>,
|
|
43
|
+
mut on_execution: impl FnMut(bool) -> Result<(), RunError>,
|
|
44
|
+
) -> Result<Execution, RunError> {
|
|
21
45
|
let limited = enforce_memory_limit(&request.wasm, request.resources.memory_limit_bytes)
|
|
22
46
|
.map_err(RunError::Compile)?;
|
|
23
47
|
let metered = instrument_wasm(&limited, request.resources.instruction_budget)
|
|
@@ -29,11 +53,6 @@ pub fn run(
|
|
|
29
53
|
})?;
|
|
30
54
|
let runtime = runtime_with_engine(store.engine().clone());
|
|
31
55
|
|
|
32
|
-
let (mut stdin_writer, stdin_reader) = Pipe::channel();
|
|
33
|
-
stdin_writer
|
|
34
|
-
.write_all(&request.stdin)
|
|
35
|
-
.map_err(|error| RunError::Io(error.to_string()))?;
|
|
36
|
-
drop(stdin_writer);
|
|
37
56
|
let output_limit = usize::try_from(request.resources.output_limit_bytes)
|
|
38
57
|
.map_err(|_| RunError::InvalidRequest("output limit exceeds host range".to_string()))?;
|
|
39
58
|
let output_budget = OutputBudget::new(output_limit);
|
|
@@ -51,8 +70,11 @@ pub fn run(
|
|
|
51
70
|
.runtime(runtime)
|
|
52
71
|
.args(request.args.clone())
|
|
53
72
|
.envs(request.env.clone())
|
|
54
|
-
.stdin(
|
|
55
|
-
.stdout(
|
|
73
|
+
.stdin(stdin)
|
|
74
|
+
.stdout(match stdout_peer {
|
|
75
|
+
Some(peer) => peer(stdout_file),
|
|
76
|
+
None => Box::new(stdout_file),
|
|
77
|
+
})
|
|
56
78
|
.stderr(Box::new(stderr_file))
|
|
57
79
|
.fs(filesystem.clone());
|
|
58
80
|
builder
|
|
@@ -82,12 +104,13 @@ pub fn run(
|
|
|
82
104
|
clock.clone(),
|
|
83
105
|
request.startup_entropy_bytes,
|
|
84
106
|
);
|
|
107
|
+
attach_safepoint(&mut store, &mut imports);
|
|
85
108
|
attach_capability_denials(&mut store, &module, &mut imports).map_err(RunError::Compile)?;
|
|
86
109
|
let imported_memory =
|
|
87
110
|
attach_imported_memory(&mut store, &module, &mut imports).map_err(RunError::Compile)?;
|
|
88
111
|
let instance = Instance::new(&mut store, &module, &imports)
|
|
89
112
|
.map_err(|error| RunError::Compile(format!("failed to instantiate module: {error}")))?;
|
|
90
|
-
let meter = meter_state(&
|
|
113
|
+
let meter = meter_state(&instance).map_err(RunError::Runtime)?;
|
|
91
114
|
let guest_memory = instance
|
|
92
115
|
.exports
|
|
93
116
|
.get_memory("memory")
|
|
@@ -138,12 +161,14 @@ pub fn run(
|
|
|
138
161
|
let logical_time_exceeded = clock.limit_exceeded()?;
|
|
139
162
|
|
|
140
163
|
let mut code = 0;
|
|
164
|
+
let mut exit_code = None;
|
|
141
165
|
let mut termination = ExecutionTermination::Exited;
|
|
142
166
|
let mut trap_message = None;
|
|
143
167
|
if let Err(error) = execution {
|
|
144
168
|
if let Some(wasi_error) = crate::wasi_error(&error) {
|
|
145
169
|
match wasi_error {
|
|
146
170
|
WasiError::Exit(exit) => {
|
|
171
|
+
exit_code = Some(exit.raw());
|
|
147
172
|
let errno: wasmer_wasix_types::wasi::Errno = (*exit).into();
|
|
148
173
|
if errno != wasmer_wasix_types::wasi::Errno::Success {
|
|
149
174
|
code = errno as i32;
|
|
@@ -211,7 +236,7 @@ pub fn run(
|
|
|
211
236
|
CostPoints::Exhausted => request.resources.instruction_budget,
|
|
212
237
|
};
|
|
213
238
|
let filesystem_metrics = project_filesystem.metrics();
|
|
214
|
-
|
|
239
|
+
let result = RunResult {
|
|
215
240
|
code,
|
|
216
241
|
metrics: ExecutionMetrics {
|
|
217
242
|
cost,
|
|
@@ -231,5 +256,6 @@ pub fn run(
|
|
|
231
256
|
trap_message,
|
|
232
257
|
determinism: request.determinism,
|
|
233
258
|
resources: request.resources,
|
|
234
|
-
}
|
|
259
|
+
};
|
|
260
|
+
Ok(Execution { result, exit_code })
|
|
235
261
|
}
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
use super::web::{Execution, execute};
|
|
2
|
+
use crate::output::CappedOutput;
|
|
3
|
+
use crate::{
|
|
4
|
+
DeterminismConfig, ExecutionTermination, InteractiveMetrics, InteractiveProcessResult,
|
|
5
|
+
InteractiveProgram, RunError, RunRequest,
|
|
6
|
+
};
|
|
7
|
+
use serde::{Deserialize, Serialize};
|
|
8
|
+
use std::io;
|
|
9
|
+
use std::pin::Pin;
|
|
10
|
+
use std::sync::Arc;
|
|
11
|
+
use std::task::{Context, Poll};
|
|
12
|
+
use tokio::io::{AsyncRead, AsyncSeek, AsyncWrite, ReadBuf};
|
|
13
|
+
use virtual_fs::{FsError, VirtualFile};
|
|
14
|
+
use wasm_bindgen::JsValue;
|
|
15
|
+
|
|
16
|
+
#[derive(Clone, Debug, Deserialize, Serialize)]
|
|
17
|
+
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
|
18
|
+
pub struct InteractiveSideRequest {
|
|
19
|
+
pub program: InteractiveProgram,
|
|
20
|
+
pub determinism: DeterminismConfig,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
#[derive(Clone, Debug, Serialize)]
|
|
24
|
+
#[serde(rename_all = "camelCase")]
|
|
25
|
+
pub struct InteractiveSideResult {
|
|
26
|
+
pub process: InteractiveProcessResult,
|
|
27
|
+
#[serde(with = "serde_bytes")]
|
|
28
|
+
pub protocol: Vec<u8>,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/// Stream callbacks supplied by the side Worker. Reads and writes wait with
|
|
32
|
+
/// `Atomics.wait`, so they never return `Pending` to WASIX.
|
|
33
|
+
pub struct HostStreams {
|
|
34
|
+
read: js_sys::Function,
|
|
35
|
+
poll: js_sys::Function,
|
|
36
|
+
wait: js_sys::Function,
|
|
37
|
+
write: js_sys::Function,
|
|
38
|
+
close: js_sys::Function,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// SAFETY: the web build of runtime-core has no threads; these JS handles never
|
|
42
|
+
// leave the Worker thread that created them.
|
|
43
|
+
unsafe impl Send for HostStreams {}
|
|
44
|
+
// SAFETY: see `Send`.
|
|
45
|
+
unsafe impl Sync for HostStreams {}
|
|
46
|
+
|
|
47
|
+
impl HostStreams {
|
|
48
|
+
pub fn new(
|
|
49
|
+
read: js_sys::Function,
|
|
50
|
+
poll: js_sys::Function,
|
|
51
|
+
wait: js_sys::Function,
|
|
52
|
+
write: js_sys::Function,
|
|
53
|
+
close: js_sys::Function,
|
|
54
|
+
) -> Self {
|
|
55
|
+
Self {
|
|
56
|
+
read,
|
|
57
|
+
poll,
|
|
58
|
+
wait,
|
|
59
|
+
write,
|
|
60
|
+
close,
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
fn read(&self, maximum: usize) -> io::Result<Vec<u8>> {
|
|
65
|
+
let chunk = self
|
|
66
|
+
.read
|
|
67
|
+
.call1(&JsValue::UNDEFINED, &JsValue::from(maximum as u32))
|
|
68
|
+
.map_err(host_error)?;
|
|
69
|
+
Ok(js_sys::Uint8Array::new(&chunk).to_vec())
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
fn poll(&self) -> io::Result<Option<usize>> {
|
|
73
|
+
let available = self
|
|
74
|
+
.poll
|
|
75
|
+
.call0(&JsValue::UNDEFINED)
|
|
76
|
+
.map_err(host_error)?
|
|
77
|
+
.as_f64()
|
|
78
|
+
.ok_or_else(|| io::Error::other("interactive input poll returned no size"))?;
|
|
79
|
+
Ok((available >= 0.0).then_some(available as usize))
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
fn wait(&self) -> io::Result<usize> {
|
|
83
|
+
let available = self
|
|
84
|
+
.wait
|
|
85
|
+
.call0(&JsValue::UNDEFINED)
|
|
86
|
+
.map_err(host_error)?
|
|
87
|
+
.as_f64()
|
|
88
|
+
.ok_or_else(|| io::Error::other("interactive input wait returned no size"))?;
|
|
89
|
+
Ok(available as usize)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
fn write(&self, bytes: &[u8]) -> io::Result<usize> {
|
|
93
|
+
let written = self
|
|
94
|
+
.write
|
|
95
|
+
.call1(&JsValue::UNDEFINED, &js_sys::Uint8Array::from(bytes))
|
|
96
|
+
.map_err(host_error)?
|
|
97
|
+
.as_f64()
|
|
98
|
+
.ok_or_else(|| io::Error::other("interactive output write returned no size"))?;
|
|
99
|
+
if written < 0.0 {
|
|
100
|
+
return Err(io::ErrorKind::BrokenPipe.into());
|
|
101
|
+
}
|
|
102
|
+
Ok(written as usize)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/// Closes the input (fd 0) or output (fd 1) pipe end, so the peer sees
|
|
106
|
+
/// EOF or a broken pipe at once, as when native drops its pipe end.
|
|
107
|
+
fn close(&self, fd: u32) {
|
|
108
|
+
let _ = self.close.call1(&JsValue::UNDEFINED, &JsValue::from(fd));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
fn host_error(error: JsValue) -> io::Error {
|
|
113
|
+
io::Error::other(format!("interactive stream failed: {error:?}"))
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
pub fn run_side(
|
|
117
|
+
request: InteractiveSideRequest,
|
|
118
|
+
streams: HostStreams,
|
|
119
|
+
on_execution: impl FnMut(bool) -> Result<(), RunError>,
|
|
120
|
+
) -> Result<InteractiveSideResult, RunError> {
|
|
121
|
+
let program = request.program;
|
|
122
|
+
if program.wasm.is_empty() {
|
|
123
|
+
return Err(RunError::InvalidRequest(
|
|
124
|
+
"interactive Wasm must not be empty".to_string(),
|
|
125
|
+
));
|
|
126
|
+
}
|
|
127
|
+
let run = RunRequest {
|
|
128
|
+
wasm: program.wasm,
|
|
129
|
+
args: program.args,
|
|
130
|
+
env: program.env,
|
|
131
|
+
stdin: Vec::new(),
|
|
132
|
+
files: program.files,
|
|
133
|
+
output_paths: Vec::new(),
|
|
134
|
+
cwd: program.cwd,
|
|
135
|
+
startup_entropy_bytes: program.startup_entropy_bytes,
|
|
136
|
+
determinism: request.determinism,
|
|
137
|
+
resources: program.resources,
|
|
138
|
+
};
|
|
139
|
+
super::validate(&run)?;
|
|
140
|
+
let streams = Arc::new(streams);
|
|
141
|
+
let input = StreamInput {
|
|
142
|
+
streams: streams.clone(),
|
|
143
|
+
};
|
|
144
|
+
let Execution { result, exit_code } = execute(
|
|
145
|
+
run,
|
|
146
|
+
Box::new(input),
|
|
147
|
+
Some(Box::new(move |capture| {
|
|
148
|
+
Box::new(StreamOutput { streams, capture })
|
|
149
|
+
})),
|
|
150
|
+
on_execution,
|
|
151
|
+
)?;
|
|
152
|
+
let code = match exit_code {
|
|
153
|
+
Some(code) if result.termination == ExecutionTermination::Exited => code,
|
|
154
|
+
_ => result.code,
|
|
155
|
+
};
|
|
156
|
+
Ok(InteractiveSideResult {
|
|
157
|
+
process: InteractiveProcessResult {
|
|
158
|
+
code,
|
|
159
|
+
stderr: result.stderr,
|
|
160
|
+
termination: result.termination,
|
|
161
|
+
metrics: InteractiveMetrics {
|
|
162
|
+
cost: result.metrics.cost,
|
|
163
|
+
operations: result.metrics.operations,
|
|
164
|
+
logical_time_ns: result.metrics.logical_time_ns,
|
|
165
|
+
filesystem_bytes: result.metrics.filesystem_bytes,
|
|
166
|
+
filesystem_entries: result.metrics.filesystem_entries,
|
|
167
|
+
protocol_bytes: result.metrics.stdout_bytes,
|
|
168
|
+
stderr_bytes: result.metrics.stderr_bytes,
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
protocol: result.stdout,
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
struct StreamInput {
|
|
176
|
+
streams: Arc<HostStreams>,
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// WASIX drops the stdio handle when the last descriptor that refers to it is
|
|
180
|
+
// closed, which is when native drops its `PipeRx` or `PipeTx`.
|
|
181
|
+
impl Drop for StreamInput {
|
|
182
|
+
fn drop(&mut self) {
|
|
183
|
+
self.streams.close(0);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
impl std::fmt::Debug for StreamInput {
|
|
188
|
+
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
189
|
+
formatter.write_str("StreamInput")
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
impl AsyncRead for StreamInput {
|
|
194
|
+
fn poll_read(
|
|
195
|
+
self: Pin<&mut Self>,
|
|
196
|
+
_context: &mut Context<'_>,
|
|
197
|
+
buffer: &mut ReadBuf<'_>,
|
|
198
|
+
) -> Poll<io::Result<()>> {
|
|
199
|
+
Poll::Ready(self.streams.read(buffer.remaining()).map(|chunk| {
|
|
200
|
+
buffer.put_slice(&chunk);
|
|
201
|
+
}))
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
impl AsyncWrite for StreamInput {
|
|
206
|
+
fn poll_write(
|
|
207
|
+
self: Pin<&mut Self>,
|
|
208
|
+
_context: &mut Context<'_>,
|
|
209
|
+
_buffer: &[u8],
|
|
210
|
+
) -> Poll<io::Result<usize>> {
|
|
211
|
+
Poll::Ready(Err(io::ErrorKind::Unsupported.into()))
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
fn poll_flush(self: Pin<&mut Self>, _context: &mut Context<'_>) -> Poll<io::Result<()>> {
|
|
215
|
+
Poll::Ready(Ok(()))
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
fn poll_shutdown(self: Pin<&mut Self>, _context: &mut Context<'_>) -> Poll<io::Result<()>> {
|
|
219
|
+
Poll::Ready(Ok(()))
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
impl AsyncSeek for StreamInput {
|
|
224
|
+
fn start_seek(self: Pin<&mut Self>, _position: io::SeekFrom) -> io::Result<()> {
|
|
225
|
+
Ok(())
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
fn poll_complete(self: Pin<&mut Self>, _context: &mut Context<'_>) -> Poll<io::Result<u64>> {
|
|
229
|
+
Poll::Ready(Ok(0))
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
impl VirtualFile for StreamInput {
|
|
234
|
+
fn last_accessed(&self) -> u64 {
|
|
235
|
+
0
|
|
236
|
+
}
|
|
237
|
+
fn last_modified(&self) -> u64 {
|
|
238
|
+
0
|
|
239
|
+
}
|
|
240
|
+
fn created_time(&self) -> u64 {
|
|
241
|
+
0
|
|
242
|
+
}
|
|
243
|
+
fn size(&self) -> u64 {
|
|
244
|
+
0
|
|
245
|
+
}
|
|
246
|
+
fn set_len(&mut self, _new_size: u64) -> Result<(), FsError> {
|
|
247
|
+
Ok(())
|
|
248
|
+
}
|
|
249
|
+
fn unlink(&mut self) -> Result<(), FsError> {
|
|
250
|
+
Ok(())
|
|
251
|
+
}
|
|
252
|
+
fn get_special_fd(&self) -> Option<u32> {
|
|
253
|
+
Some(0)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
fn poll_read_ready(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<io::Result<usize>> {
|
|
257
|
+
match self.streams.poll() {
|
|
258
|
+
Ok(Some(available)) => Poll::Ready(Ok(available)),
|
|
259
|
+
Ok(None) if crate::deterministic::defer_stdin_readiness() => {
|
|
260
|
+
context.waker().wake_by_ref();
|
|
261
|
+
Poll::Pending
|
|
262
|
+
}
|
|
263
|
+
Ok(None) => Poll::Ready(self.streams.wait()),
|
|
264
|
+
Err(error) => Poll::Ready(Err(error)),
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
fn poll_write_ready(
|
|
269
|
+
self: Pin<&mut Self>,
|
|
270
|
+
_context: &mut Context<'_>,
|
|
271
|
+
) -> Poll<io::Result<usize>> {
|
|
272
|
+
Poll::Ready(Err(io::ErrorKind::Unsupported.into()))
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
struct StreamOutput {
|
|
277
|
+
streams: Arc<HostStreams>,
|
|
278
|
+
capture: CappedOutput,
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
impl Drop for StreamOutput {
|
|
282
|
+
fn drop(&mut self) {
|
|
283
|
+
self.streams.close(1);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
impl std::fmt::Debug for StreamOutput {
|
|
288
|
+
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
289
|
+
formatter.write_str("StreamOutput")
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
impl AsyncWrite for StreamOutput {
|
|
294
|
+
/// Drops bytes written after the peer closed its stdin, as native does; they are already in
|
|
295
|
+
/// the transcript.
|
|
296
|
+
fn poll_write(
|
|
297
|
+
mut self: Pin<&mut Self>,
|
|
298
|
+
context: &mut Context<'_>,
|
|
299
|
+
buffer: &[u8],
|
|
300
|
+
) -> Poll<io::Result<usize>> {
|
|
301
|
+
match Pin::new(&mut self.capture).poll_write(context, buffer) {
|
|
302
|
+
Poll::Ready(Ok(written)) => Poll::Ready(match self.streams.write(&buffer[..written]) {
|
|
303
|
+
Err(error) if error.kind() == io::ErrorKind::BrokenPipe => Ok(written),
|
|
304
|
+
result => result,
|
|
305
|
+
}),
|
|
306
|
+
result => result,
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
fn poll_flush(self: Pin<&mut Self>, _context: &mut Context<'_>) -> Poll<io::Result<()>> {
|
|
311
|
+
Poll::Ready(Ok(()))
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
fn poll_shutdown(self: Pin<&mut Self>, _context: &mut Context<'_>) -> Poll<io::Result<()>> {
|
|
315
|
+
Poll::Ready(Ok(()))
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
impl AsyncRead for StreamOutput {
|
|
320
|
+
fn poll_read(
|
|
321
|
+
self: Pin<&mut Self>,
|
|
322
|
+
_context: &mut Context<'_>,
|
|
323
|
+
_buffer: &mut ReadBuf<'_>,
|
|
324
|
+
) -> Poll<io::Result<()>> {
|
|
325
|
+
Poll::Ready(Ok(()))
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
impl AsyncSeek for StreamOutput {
|
|
330
|
+
fn start_seek(self: Pin<&mut Self>, _position: io::SeekFrom) -> io::Result<()> {
|
|
331
|
+
Ok(())
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
fn poll_complete(self: Pin<&mut Self>, _context: &mut Context<'_>) -> Poll<io::Result<u64>> {
|
|
335
|
+
Poll::Ready(Ok(0))
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
impl VirtualFile for StreamOutput {
|
|
340
|
+
fn last_accessed(&self) -> u64 {
|
|
341
|
+
0
|
|
342
|
+
}
|
|
343
|
+
fn last_modified(&self) -> u64 {
|
|
344
|
+
0
|
|
345
|
+
}
|
|
346
|
+
fn created_time(&self) -> u64 {
|
|
347
|
+
0
|
|
348
|
+
}
|
|
349
|
+
fn size(&self) -> u64 {
|
|
350
|
+
0
|
|
351
|
+
}
|
|
352
|
+
fn set_len(&mut self, _new_size: u64) -> Result<(), FsError> {
|
|
353
|
+
Ok(())
|
|
354
|
+
}
|
|
355
|
+
fn unlink(&mut self) -> Result<(), FsError> {
|
|
356
|
+
Ok(())
|
|
357
|
+
}
|
|
358
|
+
fn get_special_fd(&self) -> Option<u32> {
|
|
359
|
+
Some(1)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
fn poll_read_ready(
|
|
363
|
+
self: Pin<&mut Self>,
|
|
364
|
+
_context: &mut Context<'_>,
|
|
365
|
+
) -> Poll<io::Result<usize>> {
|
|
366
|
+
Poll::Ready(Ok(0))
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
fn poll_write_ready(
|
|
370
|
+
mut self: Pin<&mut Self>,
|
|
371
|
+
context: &mut Context<'_>,
|
|
372
|
+
) -> Poll<io::Result<usize>> {
|
|
373
|
+
Pin::new(&mut self.capture).poll_write_ready(context)
|
|
374
|
+
}
|
|
375
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
use crate::run::web_interactive::{HostStreams, InteractiveSideRequest, run_side};
|
|
1
2
|
use crate::{
|
|
2
3
|
GoCompilerSession as CoreGoCompilerSession, GoCompilerSessionConfig, GoCompilerSessionRequest,
|
|
3
|
-
|
|
4
|
+
RunError, RunFailure, RunRequest, run_response_from_result,
|
|
4
5
|
};
|
|
5
6
|
use serde::Serialize;
|
|
6
7
|
use wasm_bindgen::prelude::*;
|
|
@@ -72,17 +73,62 @@ impl WebGoCompilerSession {
|
|
|
72
73
|
}
|
|
73
74
|
}
|
|
74
75
|
|
|
76
|
+
/// Runs one side of an interactive session in the calling Worker. `read`,
|
|
77
|
+
/// `wait` and `write` block on the session's shared ring buffers; `poll`
|
|
78
|
+
/// checks the input without blocking; `close(fd)` closes the input (0) or
|
|
79
|
+
/// output (1) end when the guest drops it.
|
|
75
80
|
#[wasm_bindgen]
|
|
76
|
-
pub
|
|
81
|
+
pub fn run_interactive_side(
|
|
82
|
+
request: JsValue,
|
|
83
|
+
read: js_sys::Function,
|
|
84
|
+
poll: js_sys::Function,
|
|
85
|
+
wait: js_sys::Function,
|
|
86
|
+
write: js_sys::Function,
|
|
87
|
+
close: js_sys::Function,
|
|
88
|
+
on_execution: js_sys::Function,
|
|
89
|
+
) -> Result<JsValue, JsValue> {
|
|
77
90
|
console_error_panic_hook::set_once();
|
|
78
|
-
let request:
|
|
79
|
-
.map_err(|error|
|
|
80
|
-
|
|
91
|
+
let request: InteractiveSideRequest =
|
|
92
|
+
serde_wasm_bindgen::from_value(request).map_err(|error| {
|
|
93
|
+
JsValue::from_str(&format!("invalid interactive side request: {error}"))
|
|
94
|
+
})?;
|
|
95
|
+
let response = match run_side(
|
|
96
|
+
request,
|
|
97
|
+
HostStreams::new(read, poll, wait, write, close),
|
|
98
|
+
|running| {
|
|
99
|
+
on_execution
|
|
100
|
+
.call1(&JsValue::UNDEFINED, &JsValue::from_bool(running))
|
|
101
|
+
.map(|_| ())
|
|
102
|
+
.map_err(|error| RunError::Runtime(format!("execution observer failed: {error:?}")))
|
|
103
|
+
},
|
|
104
|
+
) {
|
|
105
|
+
Ok(result) => InteractiveSideResponse {
|
|
106
|
+
ok: true,
|
|
107
|
+
result: Some(result),
|
|
108
|
+
error: None,
|
|
109
|
+
},
|
|
110
|
+
Err(error) => InteractiveSideResponse {
|
|
111
|
+
ok: false,
|
|
112
|
+
result: None,
|
|
113
|
+
error: Some(RunFailure {
|
|
114
|
+
code: error.code(),
|
|
115
|
+
message: error.to_string(),
|
|
116
|
+
}),
|
|
117
|
+
},
|
|
118
|
+
};
|
|
81
119
|
response
|
|
82
120
|
.serialize(&serde_wasm_bindgen::Serializer::new().serialize_maps_as_objects(true))
|
|
83
121
|
.map_err(|error| {
|
|
84
122
|
JsValue::from_str(&format!(
|
|
85
|
-
"failed to serialize interactive response: {error}"
|
|
123
|
+
"failed to serialize interactive side response: {error}"
|
|
86
124
|
))
|
|
87
125
|
})
|
|
88
126
|
}
|
|
127
|
+
|
|
128
|
+
#[derive(Serialize)]
|
|
129
|
+
#[serde(rename_all = "camelCase")]
|
|
130
|
+
struct InteractiveSideResponse {
|
|
131
|
+
ok: bool,
|
|
132
|
+
result: Option<crate::run::web_interactive::InteractiveSideResult>,
|
|
133
|
+
error: Option<RunFailure>,
|
|
134
|
+
}
|