Skip to content

Commit d4f190e

Browse files
committed
Properly mark everything as (very) unsafe
1 parent 404c001 commit d4f190e

File tree

4 files changed

+73
-55
lines changed

4 files changed

+73
-55
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "fork-map"
3-
version = "0.1.1"
3+
version = "0.1.2"
44
edition = "2021"
55
license = "MIT"
66
description = "A crate for running operations in a child process spawned by `fork()`"
@@ -16,6 +16,6 @@ readme = "README.md"
1616
[dependencies]
1717
anyhow = "1.0"
1818
libc = "0.2"
19-
serde = { version = "1.0", features = ["derive"] }
19+
serde = "1.0"
2020
serde-error = "0.1.2"
2121
serde_json = "1.0"

README.md

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,17 @@ use fork_map::fork_map;
1010

1111
pub fn do_with_fork(value: u64) -> u64 {
1212
// Spawn a child process with a copy-on-write copy of memory
13-
fork_map(|| {
14-
// Do some obnoxious operation with `value`
15-
// * Maybe it leaks memory
16-
// * Maybe it uses static resources unsafely and
17-
// prevents multi-threaded operation
18-
// * Maybe you couldn't figure out how to
19-
// send your data to a thread
20-
Ok(value * 10)
21-
}).unwrap()
13+
unsafe {
14+
fork_map(|| {
15+
// Do some obnoxious operation with `value`
16+
// * Maybe it leaks memory
17+
// * Maybe it uses static resources unsafely and
18+
// prevents multi-threaded operation
19+
// * Maybe you couldn't figure out how to
20+
// send your data to a thread
21+
Ok(value * 10)
22+
}).unwrap()
23+
}
2224
// Execution continues after the child process has exited
2325
}
2426
```
@@ -43,10 +45,12 @@ pub fn main() {
4345
let results = my_big_list.into_par_iter().map(|item| {
4446
// Have each worker spawn a child process for the
4547
// operations we don't want polluting the parent's memory
46-
fork_map(|| {
47-
// Do your ugly operations here
48-
Ok(item * 1234)
49-
}).expect("fork_map succeeded")
48+
unsafe {
49+
fork_map(|| {
50+
// Do your ugly operations here
51+
Ok(item * 1234)
52+
}).expect("fork_map succeeded")
53+
}
5054
}).collect::<Vec<_>>();
5155

5256
// Use results here
@@ -69,17 +73,23 @@ pub fn main() {
6973
.chunks(512)
7074
.map(|items| {
7175
// Now each child process does 512 items at once
72-
fork_map(|| {
73-
let mut results = vec![];
74-
// Maybe this operation is only mildly heinous
75-
// and we can do 512 of them before the child
76-
// process needs to be restarted.
77-
for item in items {
78-
results.push(item * 1234);
79-
}
80-
Ok(results)
81-
}).expect("fork_map succeeded")
76+
unsafe {
77+
fork_map(|| {
78+
let mut results = vec![];
79+
// Maybe this operation is only mildly heinous
80+
// and we can do 512 of them before the child
81+
// process needs to be restarted.
82+
for item in items {
83+
results.push(item * 1234);
84+
}
85+
Ok(results)
86+
}).expect("fork_map succeeded")
87+
}
8288
})
8389
.collect::<Vec<_>>();
8490
}
8591
```
92+
93+
## Safety
94+
95+
Due to the nature of `fork()`, this function is very unsound and likely violates most of Rust's guarantees about lifetimes, considering all of your memory gets duplicated into a second process, even though it calls `exit(0)` after your closure is executed. Any threads other than the one calling `fork_map` will not be present in the new process, so threaded lifetime guarantees are also violated. Don't even think about using async executors with this.

src/lib.rs

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,18 @@ use serde::{Deserialize, Serialize};
1212
/// // Result type needs to implement serde::Serialize and serde::Deserialize
1313
/// pub fn do_with_fork(value: u64) -> u64 {
1414
/// // Spawn a child process with a copy-on-write copy of memory
15-
/// fork_map(|| {
16-
/// // Do some obnoxious operation with `value`
17-
/// // * Maybe it leaks memory
18-
/// // * Maybe it uses static resources unsafely and
19-
/// // prevents multi-threaded operation
20-
/// // * Maybe you couldn't figure out how to
21-
/// // send your data to a thread
22-
/// Ok(value * 10)
23-
/// }).unwrap()
24-
/// // Execution continues after the child process has exited
15+
/// unsafe {
16+
/// fork_map(|| {
17+
/// // Do some obnoxious operation with `value`
18+
/// // * Maybe it leaks memory
19+
/// // * Maybe it uses static resources unsafely and
20+
/// // prevents multi-threaded operation
21+
/// // * Maybe you couldn't figure out how to
22+
/// // send your data to a thread
23+
/// Ok(value * 10)
24+
/// }).unwrap()
25+
/// // Execution continues after the child process has exited
26+
/// }
2527
/// }
2628
/// ```
2729
///
@@ -40,51 +42,57 @@ use serde::{Deserialize, Serialize};
4042
/// let results = my_big_list.into_par_iter().map(|item| {
4143
/// // Have each worker spawn a child process for the
4244
/// // operations we don't want polluting the parent's memory
43-
/// fork_map(|| {
44-
/// // Do your ugly operations here
45-
/// Ok(item * 1234)
46-
/// }).expect("fork_map succeeded")
45+
/// unsafe {
46+
/// fork_map(|| {
47+
/// // Do your ugly operations here
48+
/// Ok(item * 1234)
49+
/// }).expect("fork_map succeeded")
50+
/// }
4751
/// }).collect::<Vec<_>>();
4852
///
4953
/// // Use results here
5054
/// }
5155
/// ```
52-
pub fn fork_map<F, R>(func: F) -> anyhow::Result<R>
56+
///
57+
/// # Safety
58+
///
59+
/// Due to the nature of `fork()`, this function is very unsound and likely violates most of Rust's
60+
/// guarantees about lifetimes, considering all of your memory gets duplicated into a second
61+
/// process, even though it calls `exit(0)` after your closure is executed. Any threads other than
62+
/// the one calling `fork_map` will not be present in the new process, so threaded lifetime
63+
/// guarantees are also violated. Don't even think about using async executors with this.
64+
pub unsafe fn fork_map<F, R>(func: F) -> anyhow::Result<R>
5365
where
5466
F: Fn() -> anyhow::Result<R>,
5567
R: Serialize + for<'a> Deserialize<'a>,
5668
{
57-
// SAFETY: Probably not LOL, didn't crash on my box, use at your own risk, etc.
58-
5969
// Pipe for sending the result from child to parent
6070
let mut pipe: [libc::c_int; 2] = [0; 2];
61-
unsafe {
62-
libc::pipe(pipe.as_mut_ptr());
63-
}
71+
libc::pipe(pipe.as_mut_ptr());
6472

6573
// Here we go
66-
let pid = unsafe { libc::fork() };
74+
let pid = libc::fork();
6775
if pid == 0 {
6876
// Child
69-
unsafe { libc::close(pipe[0]) };
77+
libc::close(pipe[0]);
7078
let result = func().map_err(|e| serde_error::Error::new(&*e));
7179
let ser = serde_json::to_string(&result).unwrap_or("".to_string());
72-
unsafe { libc::write(pipe[1], ser.as_ptr() as *const libc::c_void, ser.len()) };
73-
unsafe { libc::close(pipe[1]) };
74-
unsafe { libc::exit(0) };
80+
libc::write(pipe[1], ser.as_ptr() as *const libc::c_void, ser.len());
81+
libc::close(pipe[1]);
82+
libc::exit(0);
7583
}
7684

7785
// Parent
78-
unsafe { libc::close(pipe[1]) };
86+
libc::close(pipe[1]);
7987

8088
// Read result from pipe
8189
let mut des = vec![];
8290
let des = loop {
8391
const BUF_SIZE: usize = 0x1000;
8492
let mut buf: [u8; BUF_SIZE] = [0; BUF_SIZE];
85-
let count = unsafe { libc::read(pipe[0], buf.as_mut_ptr() as *mut libc::c_void, BUF_SIZE) };
93+
let count = libc::read(pipe[0], buf.as_mut_ptr() as *mut libc::c_void, BUF_SIZE);
8694
if count < 0 {
87-
break Err(anyhow!("io error: {}", unsafe { *libc::__error() }));
95+
break Err(anyhow!("io error: {}", *libc::__error()));
8896
}
8997
des.extend_from_slice(&buf[0..(count as usize)]);
9098
// EOF signalled by less than the max bytes
@@ -94,7 +102,7 @@ pub fn fork_map<F, R>(func: F) -> anyhow::Result<R>
94102
};
95103

96104
let mut status = 0;
97-
unsafe { libc::waitpid(pid, &mut status, 0) };
105+
libc::waitpid(pid, &mut status, 0);
98106

99107
if status != 0 {
100108
return Err(anyhow!("Process returned non-zero status code {}", status));

0 commit comments

Comments
 (0)