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!(crate::values_equal(
167 &pack_fetch(&pup).unwrap(),
168 &Value::int(49)
169 ));
170 }
171
172 #[test]
173 fn a_pups_error_is_re_raised_by_fetch() {
174 let pup = spawn_pup(|| finish_pup(Err(DogeError::value_error("pup went bad")))).unwrap();
175 let err = pack_fetch(&pup).unwrap_err();
176 assert_eq!(err.kind, ErrorKind::ValueError);
177 assert_eq!(err.message, "pup went bad");
178 }
179
180 #[test]
181 fn fetching_twice_is_a_catchable_error() {
182 let pup = spawn_pup(|| finish_pup(Ok(Value::None))).unwrap();
183 pack_fetch(&pup).unwrap();
184 assert_eq!(pack_fetch(&pup).unwrap_err().kind, ErrorKind::ValueError);
185 }
186
187 #[test]
188 fn fetch_and_bowl_ops_reject_wrong_handles() {
189 assert_eq!(
190 pack_fetch(&Value::int(1)).unwrap_err().kind,
191 ErrorKind::TypeError
192 );
193 assert_eq!(
194 pack_drop(&Value::int(1), &Value::None).unwrap_err().kind,
195 ErrorKind::TypeError
196 );
197 assert_eq!(
198 pack_sniff(&Value::int(1)).unwrap_err().kind,
199 ErrorKind::TypeError
200 );
201 }
202
203 #[test]
204 fn a_bowl_carries_a_value_between_threads() {
205 let bowl = pack_bowl().unwrap();
206 let handle = match &bowl {
210 Value::Bowl(bowl) => bowl.handle.clone(),
211 _ => unreachable!(),
212 };
213 let pup = spawn_pup(move || {
214 pack_drop(&Value::bowl(handle), &Value::str("treat")).ok();
215 finish_pup(Ok(Value::None))
216 })
217 .unwrap();
218 assert!(matches!(pack_sniff(&bowl).unwrap(), Value::Str(s) if &*s == "treat"));
219 pack_fetch(&pup).unwrap();
220 }
221
222 #[test]
223 fn zoom_rejects_a_non_callable_and_non_list() {
224 assert_eq!(
225 pack_zoom(
226 stub_entry,
227 Packed::None,
228 &Value::int(3),
229 &Value::list(vec![])
230 )
231 .unwrap_err()
232 .kind,
233 ErrorKind::TypeError
234 );
235 let f = Value::function(0, "worker", vec![]);
237 assert_eq!(
238 pack_zoom(stub_entry, Packed::None, &f, &Value::int(3))
239 .unwrap_err()
240 .kind,
241 ErrorKind::TypeError
242 );
243 }
244
245 #[test]
246 fn sending_a_pup_across_a_boundary_is_rejected() {
247 let inner = spawn_pup(|| finish_pup(Ok(Value::None))).unwrap();
248 let bowl = pack_bowl().unwrap();
250 assert_eq!(
251 pack_drop(&bowl, &inner).unwrap_err().kind,
252 ErrorKind::TypeError
253 );
254 pack_fetch(&inner).unwrap();
255 }
256}