Skip to content

Commit 31fa223

Browse files
Merge pull request #104 from dotindustries/feat/implement-threading-model-declaration-host-function-krt42xr96li73r05ad5ez1t2
feat: implement threading model declaration host function (US-118)
2 parents ed521e9 + d733c73 commit 31fa223

File tree

4 files changed

+248
-6
lines changed

4 files changed

+248
-6
lines changed

crates/warpgrid-host/src/engine.rs

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,22 @@ impl shim::threading::Host for HostState {
141141
&mut self,
142142
model: shim::threading::ThreadingModel,
143143
) -> Result<(), String> {
144-
tracing::info!(?model, "guest declared threading model");
144+
if self.threading_model.is_some() {
145+
return Err("threading model already declared".to_string());
146+
}
147+
148+
match model {
149+
shim::threading::ThreadingModel::ParallelRequired => {
150+
tracing::warn!(
151+
?model,
152+
"parallel threading requested but not supported; execution will use cooperative mode"
153+
);
154+
}
155+
shim::threading::ThreadingModel::Cooperative => {
156+
tracing::info!(?model, "cooperative threading model declared");
157+
}
158+
}
159+
145160
self.threading_model = Some(model);
146161
Ok(())
147162
}
@@ -410,4 +425,58 @@ mod tests {
410425
Some(shim::threading::ThreadingModel::Cooperative)
411426
));
412427
}
428+
429+
#[test]
430+
fn threading_model_parallel_required_succeeds() {
431+
let mut state = HostState {
432+
filesystem: None,
433+
dns: None,
434+
db_proxy: None,
435+
signal_queue: Vec::new(),
436+
threading_model: None,
437+
limiter: None,
438+
};
439+
440+
shim::threading::Host::declare_threading_model(
441+
&mut state,
442+
shim::threading::ThreadingModel::ParallelRequired,
443+
)
444+
.unwrap();
445+
446+
assert!(matches!(
447+
state.threading_model,
448+
Some(shim::threading::ThreadingModel::ParallelRequired)
449+
));
450+
}
451+
452+
#[test]
453+
fn threading_model_double_declaration_errors() {
454+
let mut state = HostState {
455+
filesystem: None,
456+
dns: None,
457+
db_proxy: None,
458+
signal_queue: Vec::new(),
459+
threading_model: None,
460+
limiter: None,
461+
};
462+
463+
shim::threading::Host::declare_threading_model(
464+
&mut state,
465+
shim::threading::ThreadingModel::Cooperative,
466+
)
467+
.unwrap();
468+
469+
let result = shim::threading::Host::declare_threading_model(
470+
&mut state,
471+
shim::threading::ThreadingModel::ParallelRequired,
472+
);
473+
assert!(result.is_err());
474+
assert!(result.unwrap_err().contains("already declared"));
475+
476+
// Original model is preserved
477+
assert!(matches!(
478+
state.threading_model,
479+
Some(shim::threading::ThreadingModel::Cooperative)
480+
));
481+
}
413482
}

crates/warpgrid-host/src/threading.rs

Lines changed: 0 additions & 5 deletions
This file was deleted.
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
//! Threading model declaration host functions.
2+
//!
3+
//! Implements the `warpgrid:shim/threading` [`Host`] trait, storing the
4+
//! guest-declared threading model and enforcing immutability (single
5+
//! declaration only).
6+
//!
7+
//! # Declaration flow
8+
//!
9+
//! ```text
10+
//! Guest calls declare_threading_model(model)
11+
//! → ThreadingHost checks immutability (already declared?)
12+
//! → Already declared → Err("threading model already declared")
13+
//! → Not declared:
14+
//! → ParallelRequired → warn (not supported, cooperative fallback)
15+
//! → Cooperative → info log
16+
//! → Store model, return Ok(())
17+
//! ```
18+
19+
use crate::bindings::warpgrid::shim::threading::{Host, ThreadingModel};
20+
21+
/// Host-side implementation of the `warpgrid:shim/threading` interface.
22+
///
23+
/// Stores the guest-declared threading model and enforces that it can
24+
/// only be declared once per instance (immutability after first call).
25+
///
26+
/// The host can query the declared model via [`threading_model`] to
27+
/// adapt execution strategy.
28+
///
29+
/// [`threading_model`]: ThreadingHost::threading_model
30+
pub struct ThreadingHost {
31+
model: Option<ThreadingModel>,
32+
}
33+
34+
impl ThreadingHost {
35+
/// Create a new `ThreadingHost` with no declared model.
36+
pub fn new() -> Self {
37+
Self { model: None }
38+
}
39+
40+
/// Query the declared threading model.
41+
///
42+
/// Returns `None` if the guest has not yet declared a model.
43+
pub fn threading_model(&self) -> Option<&ThreadingModel> {
44+
self.model.as_ref()
45+
}
46+
}
47+
48+
impl Default for ThreadingHost {
49+
fn default() -> Self {
50+
Self::new()
51+
}
52+
}
53+
54+
impl Host for ThreadingHost {
55+
fn declare_threading_model(&mut self, model: ThreadingModel) -> Result<(), String> {
56+
if self.model.is_some() {
57+
return Err("threading model already declared".to_string());
58+
}
59+
60+
match model {
61+
ThreadingModel::ParallelRequired => {
62+
tracing::warn!(
63+
?model,
64+
"parallel threading requested but not supported; execution will use cooperative mode"
65+
);
66+
}
67+
ThreadingModel::Cooperative => {
68+
tracing::info!(?model, "cooperative threading model declared");
69+
}
70+
}
71+
72+
self.model = Some(model);
73+
Ok(())
74+
}
75+
}
76+
77+
#[cfg(test)]
78+
mod tests {
79+
use super::*;
80+
81+
// ── Construction ────────────────────────────────────────────────
82+
83+
#[test]
84+
fn new_host_has_no_model() {
85+
let host = ThreadingHost::new();
86+
assert!(host.threading_model().is_none());
87+
}
88+
89+
#[test]
90+
fn default_has_no_model() {
91+
let host = ThreadingHost::default();
92+
assert!(host.threading_model().is_none());
93+
}
94+
95+
// ── Cooperative declaration ─────────────────────────────────────
96+
97+
#[test]
98+
fn declare_cooperative_succeeds() {
99+
let mut host = ThreadingHost::new();
100+
let result = host.declare_threading_model(ThreadingModel::Cooperative);
101+
assert!(result.is_ok());
102+
}
103+
104+
#[test]
105+
fn declare_cooperative_is_queryable() {
106+
let mut host = ThreadingHost::new();
107+
host.declare_threading_model(ThreadingModel::Cooperative).unwrap();
108+
assert!(matches!(
109+
host.threading_model(),
110+
Some(&ThreadingModel::Cooperative)
111+
));
112+
}
113+
114+
// ── Parallel-required declaration ───────────────────────────────
115+
116+
#[test]
117+
fn declare_parallel_required_succeeds() {
118+
let mut host = ThreadingHost::new();
119+
let result = host.declare_threading_model(ThreadingModel::ParallelRequired);
120+
assert!(result.is_ok());
121+
}
122+
123+
#[test]
124+
fn declare_parallel_required_is_queryable() {
125+
let mut host = ThreadingHost::new();
126+
host.declare_threading_model(ThreadingModel::ParallelRequired).unwrap();
127+
assert!(matches!(
128+
host.threading_model(),
129+
Some(&ThreadingModel::ParallelRequired)
130+
));
131+
}
132+
133+
// ── Immutability (double declaration) ───────────────────────────
134+
135+
#[test]
136+
fn double_declaration_returns_error() {
137+
let mut host = ThreadingHost::new();
138+
host.declare_threading_model(ThreadingModel::Cooperative).unwrap();
139+
140+
let result = host.declare_threading_model(ThreadingModel::Cooperative);
141+
assert!(result.is_err());
142+
assert!(result.unwrap_err().contains("already declared"));
143+
}
144+
145+
#[test]
146+
fn double_declaration_different_models_returns_error() {
147+
let mut host = ThreadingHost::new();
148+
host.declare_threading_model(ThreadingModel::Cooperative).unwrap();
149+
150+
let result = host.declare_threading_model(ThreadingModel::ParallelRequired);
151+
assert!(result.is_err());
152+
assert!(result.unwrap_err().contains("already declared"));
153+
}
154+
155+
#[test]
156+
fn double_declaration_preserves_original_model() {
157+
let mut host = ThreadingHost::new();
158+
host.declare_threading_model(ThreadingModel::Cooperative).unwrap();
159+
160+
let _ = host.declare_threading_model(ThreadingModel::ParallelRequired);
161+
assert!(matches!(
162+
host.threading_model(),
163+
Some(&ThreadingModel::Cooperative)
164+
));
165+
}
166+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//! Threading model declaration shim.
2+
//!
3+
//! Allows guest modules to declare their threading expectations
4+
//! (cooperative, parallel-required) so the host can warn about
5+
//! incompatibilities.
6+
//!
7+
//! The [`host`] submodule provides the WIT `Host` trait implementation
8+
//! that validates and stores the declared threading model.
9+
10+
pub mod host;
11+
12+
pub use host::ThreadingHost;

0 commit comments

Comments
 (0)