@wasm-oj/server 0.2.2 → 0.2.4
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 +233 -111
- package/crates/runtime-core/src/meter.rs +8 -55
- package/crates/runtime-core/src/run/mod.rs +2 -0
- package/crates/runtime-core/src/run/native.rs +1 -1
- package/crates/runtime-core/src/run/web.rs +35 -12
- package/crates/runtime-core/src/run/web_interactive.rs +370 -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
|
@@ -111,7 +111,7 @@ fn run_in_runtime(request: RunRequest) -> Result<RunResult, RunError> {
|
|
|
111
111
|
|
|
112
112
|
let instance = Instance::new(&mut store, &module, &imports)
|
|
113
113
|
.map_err(|error| RunError::Compile(format!("failed to instantiate module: {error}")))?;
|
|
114
|
-
let meter = meter_state(&
|
|
114
|
+
let meter = meter_state(&instance).map_err(RunError::Runtime)?;
|
|
115
115
|
let guest_memory = instance
|
|
116
116
|
.exports
|
|
117
117
|
.get_memory("memory")
|
|
@@ -5,10 +5,11 @@ use crate::filesystem::{read_files_bounded, runtime_project_files};
|
|
|
5
5
|
use crate::meter::{CostPoints, METER_MODEL, instrument_wasm, meter_state, remaining_points};
|
|
6
6
|
use crate::module_imports::attach_imported_memory;
|
|
7
7
|
use crate::module_policy::{DEFERRED_START_EXPORT, defer_start_section, enforce_memory_limit};
|
|
8
|
-
use crate::output::{OutputBudget, OutputCapture};
|
|
8
|
+
use crate::output::{CappedOutput, OutputBudget, OutputCapture};
|
|
9
9
|
use crate::{ExecutionMetrics, ExecutionTermination, RunError, RunRequest, RunResult};
|
|
10
10
|
use std::io::Write;
|
|
11
11
|
use std::sync::{Arc, Mutex};
|
|
12
|
+
use virtual_fs::VirtualFile;
|
|
12
13
|
use wasmer::{Instance, Memory, Module, Store};
|
|
13
14
|
use wasmer_wasix::{
|
|
14
15
|
Pipe, WasiEnv, WasiError, WasiModuleInstanceHandles, WasiModuleTreeHandles, wasmer_wasix_types,
|
|
@@ -16,8 +17,29 @@ use wasmer_wasix::{
|
|
|
16
17
|
|
|
17
18
|
pub fn run(
|
|
18
19
|
request: RunRequest,
|
|
19
|
-
|
|
20
|
+
on_execution: impl FnMut(bool) -> Result<(), RunError>,
|
|
20
21
|
) -> Result<RunResult, RunError> {
|
|
22
|
+
let (mut stdin_writer, stdin_reader) = Pipe::channel();
|
|
23
|
+
stdin_writer
|
|
24
|
+
.write_all(&request.stdin)
|
|
25
|
+
.map_err(|error| RunError::Io(error.to_string()))?;
|
|
26
|
+
drop(stdin_writer);
|
|
27
|
+
execute(request, Box::new(stdin_reader), None, on_execution).map(|execution| execution.result)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
pub(super) type StdoutPeer = Box<dyn FnOnce(CappedOutput) -> Box<dyn VirtualFile + Send + Sync>>;
|
|
31
|
+
|
|
32
|
+
pub(super) struct Execution {
|
|
33
|
+
pub result: RunResult,
|
|
34
|
+
pub exit_code: Option<i32>,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
pub(super) fn execute(
|
|
38
|
+
request: RunRequest,
|
|
39
|
+
stdin: Box<dyn VirtualFile + Send + Sync>,
|
|
40
|
+
stdout_peer: Option<StdoutPeer>,
|
|
41
|
+
mut on_execution: impl FnMut(bool) -> Result<(), RunError>,
|
|
42
|
+
) -> Result<Execution, RunError> {
|
|
21
43
|
let limited = enforce_memory_limit(&request.wasm, request.resources.memory_limit_bytes)
|
|
22
44
|
.map_err(RunError::Compile)?;
|
|
23
45
|
let metered = instrument_wasm(&limited, request.resources.instruction_budget)
|
|
@@ -29,11 +51,6 @@ pub fn run(
|
|
|
29
51
|
})?;
|
|
30
52
|
let runtime = runtime_with_engine(store.engine().clone());
|
|
31
53
|
|
|
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
54
|
let output_limit = usize::try_from(request.resources.output_limit_bytes)
|
|
38
55
|
.map_err(|_| RunError::InvalidRequest("output limit exceeds host range".to_string()))?;
|
|
39
56
|
let output_budget = OutputBudget::new(output_limit);
|
|
@@ -51,8 +68,11 @@ pub fn run(
|
|
|
51
68
|
.runtime(runtime)
|
|
52
69
|
.args(request.args.clone())
|
|
53
70
|
.envs(request.env.clone())
|
|
54
|
-
.stdin(
|
|
55
|
-
.stdout(
|
|
71
|
+
.stdin(stdin)
|
|
72
|
+
.stdout(match stdout_peer {
|
|
73
|
+
Some(peer) => peer(stdout_file),
|
|
74
|
+
None => Box::new(stdout_file),
|
|
75
|
+
})
|
|
56
76
|
.stderr(Box::new(stderr_file))
|
|
57
77
|
.fs(filesystem.clone());
|
|
58
78
|
builder
|
|
@@ -87,7 +107,7 @@ pub fn run(
|
|
|
87
107
|
attach_imported_memory(&mut store, &module, &mut imports).map_err(RunError::Compile)?;
|
|
88
108
|
let instance = Instance::new(&mut store, &module, &imports)
|
|
89
109
|
.map_err(|error| RunError::Compile(format!("failed to instantiate module: {error}")))?;
|
|
90
|
-
let meter = meter_state(&
|
|
110
|
+
let meter = meter_state(&instance).map_err(RunError::Runtime)?;
|
|
91
111
|
let guest_memory = instance
|
|
92
112
|
.exports
|
|
93
113
|
.get_memory("memory")
|
|
@@ -138,12 +158,14 @@ pub fn run(
|
|
|
138
158
|
let logical_time_exceeded = clock.limit_exceeded()?;
|
|
139
159
|
|
|
140
160
|
let mut code = 0;
|
|
161
|
+
let mut exit_code = None;
|
|
141
162
|
let mut termination = ExecutionTermination::Exited;
|
|
142
163
|
let mut trap_message = None;
|
|
143
164
|
if let Err(error) = execution {
|
|
144
165
|
if let Some(wasi_error) = crate::wasi_error(&error) {
|
|
145
166
|
match wasi_error {
|
|
146
167
|
WasiError::Exit(exit) => {
|
|
168
|
+
exit_code = Some(exit.raw());
|
|
147
169
|
let errno: wasmer_wasix_types::wasi::Errno = (*exit).into();
|
|
148
170
|
if errno != wasmer_wasix_types::wasi::Errno::Success {
|
|
149
171
|
code = errno as i32;
|
|
@@ -211,7 +233,7 @@ pub fn run(
|
|
|
211
233
|
CostPoints::Exhausted => request.resources.instruction_budget,
|
|
212
234
|
};
|
|
213
235
|
let filesystem_metrics = project_filesystem.metrics();
|
|
214
|
-
|
|
236
|
+
let result = RunResult {
|
|
215
237
|
code,
|
|
216
238
|
metrics: ExecutionMetrics {
|
|
217
239
|
cost,
|
|
@@ -231,5 +253,6 @@ pub fn run(
|
|
|
231
253
|
trap_message,
|
|
232
254
|
determinism: request.determinism,
|
|
233
255
|
resources: request.resources,
|
|
234
|
-
}
|
|
256
|
+
};
|
|
257
|
+
Ok(Execution { result, exit_code })
|
|
235
258
|
}
|
|
@@ -0,0 +1,370 @@
|
|
|
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
|
+
fn poll_write(
|
|
295
|
+
mut self: Pin<&mut Self>,
|
|
296
|
+
context: &mut Context<'_>,
|
|
297
|
+
buffer: &[u8],
|
|
298
|
+
) -> Poll<io::Result<usize>> {
|
|
299
|
+
match Pin::new(&mut self.capture).poll_write(context, buffer) {
|
|
300
|
+
Poll::Ready(Ok(written)) => Poll::Ready(self.streams.write(&buffer[..written])),
|
|
301
|
+
result => result,
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
fn poll_flush(self: Pin<&mut Self>, _context: &mut Context<'_>) -> Poll<io::Result<()>> {
|
|
306
|
+
Poll::Ready(Ok(()))
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
fn poll_shutdown(self: Pin<&mut Self>, _context: &mut Context<'_>) -> Poll<io::Result<()>> {
|
|
310
|
+
Poll::Ready(Ok(()))
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
impl AsyncRead for StreamOutput {
|
|
315
|
+
fn poll_read(
|
|
316
|
+
self: Pin<&mut Self>,
|
|
317
|
+
_context: &mut Context<'_>,
|
|
318
|
+
_buffer: &mut ReadBuf<'_>,
|
|
319
|
+
) -> Poll<io::Result<()>> {
|
|
320
|
+
Poll::Ready(Ok(()))
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
impl AsyncSeek for StreamOutput {
|
|
325
|
+
fn start_seek(self: Pin<&mut Self>, _position: io::SeekFrom) -> io::Result<()> {
|
|
326
|
+
Ok(())
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
fn poll_complete(self: Pin<&mut Self>, _context: &mut Context<'_>) -> Poll<io::Result<u64>> {
|
|
330
|
+
Poll::Ready(Ok(0))
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
impl VirtualFile for StreamOutput {
|
|
335
|
+
fn last_accessed(&self) -> u64 {
|
|
336
|
+
0
|
|
337
|
+
}
|
|
338
|
+
fn last_modified(&self) -> u64 {
|
|
339
|
+
0
|
|
340
|
+
}
|
|
341
|
+
fn created_time(&self) -> u64 {
|
|
342
|
+
0
|
|
343
|
+
}
|
|
344
|
+
fn size(&self) -> u64 {
|
|
345
|
+
0
|
|
346
|
+
}
|
|
347
|
+
fn set_len(&mut self, _new_size: u64) -> Result<(), FsError> {
|
|
348
|
+
Ok(())
|
|
349
|
+
}
|
|
350
|
+
fn unlink(&mut self) -> Result<(), FsError> {
|
|
351
|
+
Ok(())
|
|
352
|
+
}
|
|
353
|
+
fn get_special_fd(&self) -> Option<u32> {
|
|
354
|
+
Some(1)
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
fn poll_read_ready(
|
|
358
|
+
self: Pin<&mut Self>,
|
|
359
|
+
_context: &mut Context<'_>,
|
|
360
|
+
) -> Poll<io::Result<usize>> {
|
|
361
|
+
Poll::Ready(Ok(0))
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
fn poll_write_ready(
|
|
365
|
+
mut self: Pin<&mut Self>,
|
|
366
|
+
context: &mut Context<'_>,
|
|
367
|
+
) -> Poll<io::Result<usize>> {
|
|
368
|
+
Pin::new(&mut self.capture).poll_write_ready(context)
|
|
369
|
+
}
|
|
370
|
+
}
|
|
@@ -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
|
+
}
|