Skip to content

Commit a0350a8

Browse files
committed
Support wasm runtime
1 parent 800cacc commit a0350a8

16 files changed

Lines changed: 1610 additions & 169 deletions

Cargo.lock

Lines changed: 1202 additions & 23 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "openworkers-runner"
3-
version = "0.8.0"
3+
version = "0.8.1"
44
edition = "2024"
55
license = "MIT"
66
default-run = "openworkers-runner"
@@ -10,6 +10,7 @@ default-run = "openworkers-runner"
1010
[features]
1111
default = ["database"]
1212
v8 = ["dep:openworkers-runtime-v8"]
13+
wasm = ["dep:openworkers-runtime-wasm", "openworkers-core/wasm"]
1314
database = ["dep:postgate"]
1415
# Disabled runtimes (require core 0.5, we're on 0.7) - kept as empty features to avoid cfg warnings
1516
deno = []
@@ -34,11 +35,15 @@ reqwest = { version = "0.13.1", default-features = false, features = ["rustls",
3435

3536
# Core types
3637
# openworkers-core = { path = "../openworkers-core", features = ["hyper"] }
37-
openworkers-core = { git = "https://github.com/openworkers/openworkers-core", tag = "v0.8.0", features = ["hyper"] }
38+
openworkers-core = { git = "https://github.com/openworkers/openworkers-core", tag = "v0.8.1", features = ["hyper"] }
3839

3940
# Runtime backend (v8 only for now, others require core 0.5)
4041
# openworkers-runtime-v8 = { path = "../openworkers-runtime-v8", optional = true }
41-
openworkers-runtime-v8 = { git = "https://github.com/openworkers/openworkers-runtime-v8", tag = "v0.8.0", optional = true }
42+
openworkers-runtime-v8 = { git = "https://github.com/openworkers/openworkers-runtime-v8", tag = "v0.8.1", optional = true }
43+
44+
# WASM runtime (optional)
45+
# openworkers-runtime-wasm = { path = "../openworkers-runtime-wasm", optional = true }
46+
openworkers-runtime-wasm = { git = "https://github.com/openworkers/openworkers-runtime-wasm", tag = "v0.1.0", optional = true }
4247

4348
# Database bindings (optional)
4449
# postgate = { path = "../postgate", default-features = false, optional = true }
@@ -87,6 +92,7 @@ lto = true # Enable link-time optimization.
8792

8893
[dev-dependencies]
8994
criterion = { version = "0.8.1", features = ["async_tokio"] }
95+
wat = "1.0"
9096

9197
[[bin]]
9298
name = "openworkers-runner"

bin/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use std::time::Duration;
1010
use tokio::net::TcpSocket;
1111
use tokio::sync::oneshot::channel;
1212

13-
use openworkers_core::{HttpRequest, HyperBody};
13+
use openworkers_core::{HttpRequest, HttpResponse, HyperBody};
1414
use openworkers_runner::store::WorkerIdentifier;
1515

1616
use sqlx::postgres::PgPoolOptions;
@@ -181,7 +181,7 @@ async fn handle_request(
181181
}
182182
};
183183

184-
let (res_tx, res_rx) = channel::<openworkers_runner::runtime::HttpResponse>();
184+
let (res_tx, res_rx) = channel::<HttpResponse>();
185185
let (termination_tx, termination_rx) =
186186
channel::<Result<(), openworkers_core::TerminationReason>>();
187187

src/event_fetch.rs

Lines changed: 28 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ use std::time::Duration;
33
use tokio::sync::OwnedSemaphorePermit;
44

55
use crate::ops::{DbPool, RunnerOperations};
6-
use crate::runtime::Worker;
7-
use crate::store::{WorkerWithBindings, bindings_to_infos};
6+
use crate::store::WorkerWithBindings;
7+
use crate::worker::{create_worker, prepare_script};
88
use crate::worker_pool::WORKER_POOL;
99

1010
use openworkers_core::{
11-
FetchInit, HttpRequest, HttpResponse, ResponseBody, ResponseSender, RuntimeLimits, Script,
12-
Task, TerminationReason, WorkerCode,
11+
FetchInit, HttpRequest, HttpResponse, ResponseBody, ResponseSender, RuntimeLimits, Task,
12+
TerminationReason,
1313
};
1414

1515
type TerminationTx = tokio::sync::oneshot::Sender<Result<(), TerminationReason>>;
@@ -18,53 +18,36 @@ type TerminationTx = tokio::sync::oneshot::Sender<Result<(), TerminationReason>>
1818
const FETCH_TIMEOUT_MS: u64 = 64_000; // 64 seconds
1919

2020
pub fn run_fetch(
21-
worker: WorkerWithBindings,
21+
worker_data: WorkerWithBindings,
2222
req: HttpRequest,
2323
res_tx: ResponseSender,
2424
termination_tx: TerminationTx,
2525
global_log_tx: std::sync::mpsc::Sender<crate::log::LogMessage>,
2626
permit: OwnedSemaphorePermit,
2727
db_pool: DbPool,
2828
) {
29-
let worker_id = worker.id.clone();
30-
let (log_tx, log_handler) = crate::log::create_log_handler(worker_id.clone(), global_log_tx);
31-
32-
let code = match crate::transform::parse_worker_code_str(&worker.script, &worker.language) {
33-
Ok(code) => WorkerCode::js(code),
34-
Err(e) => {
35-
log::error!("Failed to parse worker code: {}", e);
29+
let worker_id = worker_data.id.clone();
30+
let bindings = worker_data.bindings.clone();
31+
let code_type = worker_data.code_type.clone();
32+
33+
// Parse script before spawning (fail fast)
34+
let script = match prepare_script(&worker_data) {
35+
Ok(s) => s,
36+
Err(err) => {
37+
log::error!("Failed to prepare script: {err:?}");
3638
res_tx
3739
.send(HttpResponse {
3840
status: 500,
3941
headers: vec![],
40-
body: ResponseBody::Bytes(format!("Failed to parse worker code: {}", e).into()),
42+
body: ResponseBody::Bytes(format!("Failed to prepare script: {err:?}").into()),
4143
})
4244
.ok();
43-
termination_tx
44-
.send(Err(TerminationReason::InitializationError(format!(
45-
"Failed to parse worker code: {}",
46-
e
47-
))))
48-
.ok();
45+
termination_tx.send(Err(err)).ok();
4946
return;
5047
}
5148
};
5249

53-
// Convert bindings to BindingInfo for Script (names + types only)
54-
let binding_infos = bindings_to_infos(&worker.bindings);
55-
56-
// Clone bindings for RunnerOperations (full configs)
57-
let bindings_for_ops = worker.bindings.clone();
58-
59-
let script = Script {
60-
code,
61-
env: if worker.env.is_empty() {
62-
None
63-
} else {
64-
Some(worker.env.clone())
65-
},
66-
bindings: binding_infos,
67-
};
50+
let (log_tx, log_handler) = crate::log::create_log_handler(worker_id.clone(), global_log_tx);
6851

6952
// Use the sequential worker pool - ensures ONE V8 isolate per thread at a time
7053
WORKER_POOL.spawn(move || async move {
@@ -85,28 +68,31 @@ pub fn run_fetch(
8568
RunnerOperations::new()
8669
.with_worker_id(worker_id)
8770
.with_log_tx(log_tx)
88-
.with_bindings(bindings_for_ops)
71+
.with_bindings(bindings)
8972
.with_db_pool(db_pool),
9073
);
9174

92-
let mut worker = match Worker::new_with_ops(script, Some(limits), ops).await {
93-
Ok(worker) => worker,
75+
// Create worker
76+
let mut worker = match create_worker(script, limits, ops, &code_type).await {
77+
Ok(w) => w,
9478
Err(err) => {
95-
log::error!("failed to create worker: {err}");
79+
log::error!("failed to create worker: {err:?}");
9680
res_tx
9781
.send(HttpResponse {
9882
status: 500,
9983
headers: vec![],
100-
body: ResponseBody::Bytes(format!("failed to create worker: {err}").into()),
84+
body: ResponseBody::Bytes(
85+
format!("failed to create worker: {err:?}").into(),
86+
),
10187
})
102-
.unwrap();
103-
88+
.ok();
89+
termination_tx.send(Err(err)).ok();
90+
log_handler.flush();
10491
return;
10592
}
10693
};
10794

10895
let task = Task::Fetch(Some(FetchInit::new(req, res_tx)));
109-
11096
log::debug!("exec fetch task with {}ms timeout", FETCH_TIMEOUT_MS);
11197

11298
// Wrap execution with timeout

src/event_scheduled.rs

Lines changed: 41 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
use std::sync::Arc;
22

33
use crate::ops::{DbPool, RunnerOperations};
4-
use crate::runtime::Worker;
5-
use crate::store::{self, Binding, bindings_to_infos};
4+
use crate::store::{self, WorkerWithBindings};
5+
use crate::worker::{create_worker, prepare_script};
66
use crate::worker_pool::WORKER_POOL;
77

8-
use openworkers_core::{RuntimeLimits, ScheduledInit, Script, Task, WorkerCode};
8+
use openworkers_core::{RuntimeLimits, ScheduledInit, Task, TerminationReason};
99

1010
use serde::Deserialize;
1111
use serde::Serialize;
@@ -21,11 +21,19 @@ pub struct ScheduledData {
2121

2222
fn run_scheduled(
2323
data: ScheduledData,
24-
script: Script,
25-
bindings: Vec<Binding>,
24+
worker_data: WorkerWithBindings,
2625
db_pool: DbPool,
2726
global_log_tx: std::sync::mpsc::Sender<crate::log::LogMessage>,
2827
) {
28+
// Parse script before spawning (fail fast)
29+
let script = match prepare_script(&worker_data) {
30+
Ok(s) => s,
31+
Err(err) => {
32+
log::error!("Failed to prepare script for scheduled task: {err:?}");
33+
return;
34+
}
35+
};
36+
2937
// Try to acquire a worker slot
3038
let permit = match crate::worker_pool::WORKER_SEMAPHORE
3139
.clone()
@@ -41,7 +49,9 @@ fn run_scheduled(
4149
}
4250
};
4351

44-
let worker_id = data.worker_id.clone();
52+
let worker_id = worker_data.id.clone();
53+
let bindings = worker_data.bindings.clone();
54+
let code_type = worker_data.code_type.clone();
4555
let (log_tx, log_handler) = crate::log::create_log_handler(worker_id.clone(), global_log_tx);
4656

4757
// Use the sequential worker pool - ensures ONE V8 isolate per thread at a time
@@ -65,10 +75,11 @@ fn run_scheduled(
6575
.with_db_pool(db_pool),
6676
);
6777

68-
let mut worker = match Worker::new_with_ops(script, Some(limits), ops).await {
69-
Ok(worker) => worker,
78+
// Create worker
79+
let mut worker = match create_worker(script, limits, ops, &code_type).await {
80+
Ok(w) => w,
7081
Err(err) => {
71-
log::error!("failed to create scheduled worker: {err}");
82+
log::error!("failed to create scheduled worker: {err:?}");
7283
log_handler.flush();
7384
return;
7485
}
@@ -79,6 +90,7 @@ fn run_scheduled(
7990
let task = Task::Scheduled(Some(ScheduledInit::new(res_tx, data.scheduled_time)));
8091

8192
log::debug!("exec scheduled task");
93+
8294
match worker.exec(task).await {
8395
Ok(()) => {
8496
log::debug!("scheduled task completed successfully");
@@ -88,27 +100,23 @@ fn run_scheduled(
88100
Err(err) => log::error!("scheduled task response error: {err}"),
89101
}
90102
}
91-
Err(reason) => {
92-
use openworkers_core::TerminationReason;
93-
94-
match reason {
95-
TerminationReason::CpuTimeLimit => {
96-
log::warn!("scheduled task terminated: CPU time limit exceeded");
97-
}
98-
TerminationReason::WallClockTimeout => {
99-
log::warn!("scheduled task terminated: wall-clock timeout");
100-
}
101-
TerminationReason::MemoryLimit => {
102-
log::warn!("scheduled task terminated: memory limit exceeded");
103-
}
104-
TerminationReason::Exception(msg) => {
105-
log::error!("scheduled task terminated: uncaught exception: {}", msg);
106-
}
107-
_ => {
108-
log::error!("scheduled task terminated: {:?}", reason);
109-
}
103+
Err(reason) => match reason {
104+
TerminationReason::CpuTimeLimit => {
105+
log::warn!("scheduled task terminated: CPU time limit exceeded");
110106
}
111-
}
107+
TerminationReason::WallClockTimeout => {
108+
log::warn!("scheduled task terminated: wall-clock timeout");
109+
}
110+
TerminationReason::MemoryLimit => {
111+
log::warn!("scheduled task terminated: memory limit exceeded");
112+
}
113+
TerminationReason::Exception(msg) => {
114+
log::error!("scheduled task terminated: uncaught exception: {}", msg);
115+
}
116+
_ => {
117+
log::error!("scheduled task terminated: {:?}", reason);
118+
}
119+
},
112120
}
113121

114122
// CRITICAL: Flush logs before worker is dropped to prevent log loss
@@ -165,44 +173,16 @@ pub fn handle_scheduled(
165173
log::debug!("scheduled task parsed: {:?}", data);
166174

167175
let worker_id = store::WorkerIdentifier::Id(data.worker_id.clone());
168-
let worker = match store::get_worker_with_bindings(&mut conn, worker_id).await {
169-
Some(worker) => worker,
176+
let worker_data = match store::get_worker_with_bindings(&mut conn, worker_id).await
177+
{
178+
Some(w) => w,
170179
None => {
171180
log::error!("worker not found: {:?}", data.worker_id);
172181
continue;
173182
}
174183
};
175184

176-
let code =
177-
match crate::transform::parse_worker_code_str(&worker.script, &worker.language)
178-
{
179-
Ok(code) => WorkerCode::js(code),
180-
Err(e) => {
181-
log::error!("Failed to parse worker code for scheduled task: {}", e);
182-
continue;
183-
}
184-
};
185-
186-
// Convert bindings to BindingInfo for Script (names + types only)
187-
let binding_infos = bindings_to_infos(&worker.bindings);
188-
189-
let script = Script {
190-
code,
191-
env: if worker.env.is_empty() {
192-
None
193-
} else {
194-
Some(worker.env.clone())
195-
},
196-
bindings: binding_infos,
197-
};
198-
199-
run_scheduled(
200-
data,
201-
script,
202-
worker.bindings,
203-
db.clone(),
204-
global_log_tx.clone(),
205-
);
185+
run_scheduled(data, worker_data, db.clone(), global_log_tx.clone());
206186
}
207187

208188
log::debug!("scheduled task listener stopped");

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ pub mod nats;
66
pub mod ops;
77
pub mod runtime;
88
pub mod store;
9+
#[cfg(feature = "v8")]
910
mod transform;
11+
pub mod worker;
1012
pub mod worker_pool;
1113

1214
// Re-export TerminationReason for use in bin/main.rs

src/runtime.rs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,29 @@ pub use openworkers_runtime_boa::*;
2525
#[cfg(feature = "jsc")]
2626
pub use openworkers_runtime_jsc::*;
2727

28-
// Compile-time check: ensure exactly one runtime is selected
28+
// Snapshot stub for WASM runtime (WASM doesn't need/support snapshots)
29+
#[cfg(feature = "wasm")]
30+
pub mod snapshot {
31+
/// Snapshot output structure (stub for compatibility)
32+
pub struct SnapshotOutput {
33+
pub output: Vec<u8>,
34+
}
35+
36+
/// Create a runtime snapshot (stub - WASM doesn't need/support snapshots)
37+
pub fn create_runtime_snapshot() -> Result<SnapshotOutput, String> {
38+
Err("WASM runtime does not support snapshots".to_string())
39+
}
40+
}
41+
42+
// Compile-time check: ensure at least one runtime is selected
2943
#[cfg(not(any(
3044
feature = "deno",
3145
feature = "quickjs",
3246
feature = "v8",
3347
feature = "boa",
34-
feature = "jsc"
48+
feature = "jsc",
49+
feature = "wasm"
3550
)))]
36-
compile_error!("At least one runtime feature must be enabled: deno, quickjs, v8, boa, or jsc");
51+
compile_error!(
52+
"At least one runtime feature must be enabled: deno, quickjs, v8, boa, jsc, or wasm"
53+
);

0 commit comments

Comments
 (0)