doge_runtime/stdlib/
pack.rs1use std::sync::mpsc::RecvError;
10
11use crate::error::{DogeError, DogeResult};
12use crate::pack::{pack_value, unpack_packed, BowlHandle, PackMode, Packed, PackedError, PupEntry};
13use crate::value::{BowlData, PupState, Value};
14
15const PUP_STACK_SIZE: usize = 256 * 1024 * 1024;
20
21pub fn spawn_pup<F>(job: F) -> DogeResult
27where
28 F: FnOnce() -> Result<Packed, PackedError> + Send + 'static,
29{
30 std::thread::Builder::new()
31 .stack_size(PUP_STACK_SIZE)
32 .spawn(job)
33 .map(Value::pup)
34 .map_err(|err| DogeError::io_error(format!("cannot start a pup: {err}")))
35}
36
37pub fn pack_zoom(entry: PupEntry, globals: Packed, f: &Value, args: &Value) -> DogeResult {
43 if !matches!(
44 f,
45 Value::Function(_) | Value::Class(_) | Value::BoundMethod(_)
46 ) {
47 return Err(DogeError::type_error(format!(
48 "pack.zoom needs something callable to run, got {}",
49 f.describe()
50 )));
51 }
52 let items = match args {
53 Value::List(items) => items.borrow(),
54 _ => {
55 return Err(DogeError::type_error(format!(
56 "pack.zoom needs a List of arguments, got {}",
57 args.describe()
58 )))
59 }
60 };
61 let packed_f = pack_value(f, PackMode::Snapshot)?;
62 let mut packed_args = Vec::with_capacity(items.len());
63 for item in items.iter() {
64 packed_args.push(pack_value(item, PackMode::Transfer)?);
65 }
66 drop(items);
67 spawn_pup(move || entry(globals, packed_f, packed_args))
68}
69
70pub fn pack_fetch(pup: &Value) -> DogeResult {
75 let pup = match pup {
76 Value::Pup(pup) => pup,
77 _ => {
78 return Err(DogeError::type_error(format!(
79 "pack.fetch needs a Pup, got {}",
80 pup.describe()
81 )))
82 }
83 };
84 let handle = match pup.state.replace(PupState::Fetched) {
85 PupState::Running(handle) => handle,
86 PupState::Fetched => {
87 return Err(DogeError::value_error(
88 "this pup was already fetched — a pup's result can only be taken once",
89 ))
90 }
91 };
92 match handle.join() {
93 Ok(Ok(value)) => Ok(unpack_packed(value)),
94 Ok(Err(err)) => Err(err.into_error()),
95 Err(_) => Err(DogeError::io_error("a pup crashed unexpectedly")),
99 }
100}
101
102pub fn pack_bowl() -> DogeResult {
104 Ok(Value::bowl(BowlHandle::new()))
105}
106
107pub fn pack_drop(bowl: &Value, value: &Value) -> DogeResult {
112 let bowl = bowl_arg("drop", bowl)?;
113 let packed = pack_value(value, PackMode::Transfer)?;
114 bowl.handle
115 .sender
116 .send(packed)
117 .map(|()| Value::None)
118 .map_err(|_| DogeError::io_error("this bowl has no readers left"))
119}
120
121pub fn pack_sniff(bowl: &Value) -> DogeResult {
126 let bowl = bowl_arg("sniff", bowl)?;
127 let receiver = bowl
128 .handle
129 .receiver
130 .lock()
131 .unwrap_or_else(|poison| poison.into_inner());
132 match receiver.recv() {
133 Ok(value) => Ok(unpack_packed(value)),
134 Err(RecvError) => Err(DogeError::io_error(
135 "this bowl is empty and every writer is gone — nothing left to sniff",
136 )),
137 }
138}
139
140fn bowl_arg<'a>(fname: &str, value: &'a Value) -> DogeResult<&'a BowlData> {
142 match value {
143 Value::Bowl(bowl) => Ok(bowl),
144 _ => Err(DogeError::type_error(format!(
145 "pack.{fname} needs a Bowl, got {}",
146 value.describe()
147 ))),
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154 use crate::error::ErrorKind;
155 use crate::pack::finish_pup;
156
157 fn stub_entry(_globals: Packed, _f: Packed, _args: Vec<Packed>) -> Result<Packed, PackedError> {
160 finish_pup(Ok(Value::None))
161 }
162
163 #[test]
164 fn a_pup_returns_its_value_through_fetch() {
165 let pup = spawn_pup(|| finish_pup(Ok(Value::Int(49)))).unwrap();
166 assert!(matches!(pack_fetch(&pup).unwrap(), Value::Int(49)));
167 }
168
169 #[test]
170 fn a_pups_error_is_re_raised_by_fetch() {
171 let pup = spawn_pup(|| finish_pup(Err(DogeError::value_error("pup went bad")))).unwrap();
172 let err = pack_fetch(&pup).unwrap_err();
173 assert_eq!(err.kind, ErrorKind::ValueError);
174 assert_eq!(err.message, "pup went bad");
175 }
176
177 #[test]
178 fn fetching_twice_is_a_catchable_error() {
179 let pup = spawn_pup(|| finish_pup(Ok(Value::None))).unwrap();
180 pack_fetch(&pup).unwrap();
181 assert_eq!(pack_fetch(&pup).unwrap_err().kind, ErrorKind::ValueError);
182 }
183
184 #[test]
185 fn fetch_and_bowl_ops_reject_wrong_handles() {
186 assert_eq!(
187 pack_fetch(&Value::Int(1)).unwrap_err().kind,
188 ErrorKind::TypeError
189 );
190 assert_eq!(
191 pack_drop(&Value::Int(1), &Value::None).unwrap_err().kind,
192 ErrorKind::TypeError
193 );
194 assert_eq!(
195 pack_sniff(&Value::Int(1)).unwrap_err().kind,
196 ErrorKind::TypeError
197 );
198 }
199
200 #[test]
201 fn a_bowl_carries_a_value_between_threads() {
202 let bowl = pack_bowl().unwrap();
203 let handle = match &bowl {
207 Value::Bowl(bowl) => bowl.handle.clone(),
208 _ => unreachable!(),
209 };
210 let pup = spawn_pup(move || {
211 pack_drop(&Value::bowl(handle), &Value::str("treat")).ok();
212 finish_pup(Ok(Value::None))
213 })
214 .unwrap();
215 assert!(matches!(pack_sniff(&bowl).unwrap(), Value::Str(s) if &*s == "treat"));
216 pack_fetch(&pup).unwrap();
217 }
218
219 #[test]
220 fn zoom_rejects_a_non_callable_and_non_list() {
221 assert_eq!(
222 pack_zoom(
223 stub_entry,
224 Packed::None,
225 &Value::Int(3),
226 &Value::list(vec![])
227 )
228 .unwrap_err()
229 .kind,
230 ErrorKind::TypeError
231 );
232 let f = Value::function(0, "worker", vec![]);
234 assert_eq!(
235 pack_zoom(stub_entry, Packed::None, &f, &Value::Int(3))
236 .unwrap_err()
237 .kind,
238 ErrorKind::TypeError
239 );
240 }
241
242 #[test]
243 fn sending_a_pup_across_a_boundary_is_rejected() {
244 let inner = spawn_pup(|| finish_pup(Ok(Value::None))).unwrap();
245 let bowl = pack_bowl().unwrap();
247 assert_eq!(
248 pack_drop(&bowl, &inner).unwrap_err().kind,
249 ErrorKind::TypeError
250 );
251 pack_fetch(&inner).unwrap();
252 }
253}