process_fun/lib.rs
1//! # process-fun
2//!
3//! A library for easily running Rust functions in separate processes with minimal boilerplate.
4//!
5//! ## Overview
6//!
7//! This crate provides a simple macro-based approach to execute Rust functions in separate processes.
8//! The `#[process]` attribute macro creates an additional version of your function that runs in a
9//! separate process, while keeping the original function unchanged. This allows you to choose between
10//! in-process and out-of-process execution as needed.
11//!
12//! ## Process Execution Model
13//!
14//! When a function marked with `#[process]` is called through its `_process` variant:
15//!
16//! 1. A new process is forked from the current process
17//! 2. A ProcessWrapper is returned which allows:
18//! - Waiting for completion with optional timeout
19//! - Automatic process cleanup on timeout or drop
20//! - Safe result deserialization
21//!
22//! This execution model ensures complete isolation between the parent and child processes,
23//! making it suitable for running potentially risky or resource-intensive operations.
24//!
25//! ## Usage
26//!
27//! ```rust
28//! use process_fun::process;
29//! use serde::{Serialize, Deserialize};
30//! use std::time::Duration;
31//!
32//! #[derive(Serialize, Deserialize, Debug, Clone)]
33//! struct Point {
34//! x: i32,
35//! y: i32,
36//! }
37//!
38//! #[process]
39//! pub fn add_points(p1: Point, p2: Point) -> Point {
40//! Point {
41//! x: p1.x + p2.x,
42//! y: p1.y + p2.y,
43//! }
44//! }
45//!
46//! fn main() {
47//! let p1 = Point { x: 1, y: 2 };
48//! let p2 = Point { x: 3, y: 4 };
49//!
50//! // Use original function (in-process)
51//! let result1 = add_points(p1.clone(), p2.clone());
52//!
53//! // Use process version with timeout (out-of-process)
54//! let mut process = add_points_process(p1, p2).unwrap();
55//! let result2 = process.timeout(Duration::from_secs(5)).unwrap();
56//!
57//! assert_eq!(result1.x, result2.x);
58//! assert_eq!(result1.y, result2.y);
59//! }
60//! ```
61//!
62//! ## Timeout Example
63//!
64//! ```rust
65//! use process_fun::process;
66//! use std::time::Duration;
67//! use std::thread;
68//!
69//! #[process]
70//! fn long_task() -> i32 {
71//! thread::sleep(Duration::from_secs(10));
72//! 42
73//! }
74//!
75//! fn main() {
76//! let mut process = long_task_process().unwrap();
77//!
78//! // Process will be killed if it exceeds timeout
79//! match process.timeout(Duration::from_secs(1)) {
80//! Ok(result) => println!("Task completed: {}", result),
81//! Err(e) => println!("Task timed out: {}", e)
82//! }
83//! }
84//! ```
85
86#[allow(unused)]
87use serde::{Deserialize, Serialize};
88
89pub use process_fun_core::*;
90pub use process_fun_macro::process;
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95 use std::fs;
96 use std::thread;
97 use std::time::Duration;
98
99 #[derive(Serialize, Deserialize, Debug)]
100 pub struct Point {
101 pub x: i32,
102 pub y: i32,
103 }
104
105 #[derive(Serialize, Deserialize, Debug)]
106 pub struct Counter {
107 value: i32,
108 }
109
110 impl Counter {
111 pub fn new(initial: i32) -> Self {
112 Self { value: initial }
113 }
114
115 #[process]
116 pub fn get_value(&self) -> i32 {
117 self.value
118 }
119 }
120
121 #[test]
122 fn test_self_methods() {
123 let counter = Counter::new(5);
124
125 // Test immutable reference
126 let result = counter.get_value_process().unwrap().wait().unwrap();
127 assert_eq!(result, 5);
128 }
129
130 #[process]
131 pub fn add_points(p1: Point, p2: Point) -> Point {
132 Point {
133 x: p1.x + p2.x,
134 y: p1.y + p2.y,
135 }
136 }
137
138 #[test]
139 fn test_process_function() {
140 let p1 = Point { x: 1, y: 2 };
141 let p2 = Point { x: 3, y: 4 };
142
143 let result = add_points_process(p1, p2).unwrap().wait().unwrap();
144 assert_eq!(result.x, 4);
145 assert_eq!(result.y, 6);
146 }
147
148 #[process]
149 fn panicking_function() -> i32 {
150 panic!("This function panics!");
151 }
152
153 #[test]
154 fn test_process_panic() {
155 let result = panicking_function_process().unwrap().wait();
156 assert!(result.is_err(), "Expected error due to panic");
157 }
158
159 #[process]
160 fn slow_but_within_timeout() -> i32 {
161 thread::sleep(Duration::from_millis(500));
162 42
163 }
164
165 #[test]
166 fn test_timeout_success() {
167 let mut process = slow_but_within_timeout_process().unwrap();
168 let result = process.timeout(Duration::from_secs(1));
169 assert!(result.is_ok(), "{}", result.unwrap_err().to_string());
170 assert_eq!(result.unwrap(), 42);
171 }
172
173 #[process]
174 fn write_file_slow() -> bool {
175 // Try to write to a file after sleeping
176 thread::sleep(Duration::from_secs(5));
177 fs::write("test_timeout.txt", "This should not be written").unwrap();
178 true
179 }
180
181 #[test]
182 fn test_timeout_kill() {
183 // Clean up any existing file
184 let _ = fs::remove_file("test_timeout.txt");
185
186 let mut process = write_file_slow_process().unwrap();
187 let result = process.timeout(Duration::from_millis(500));
188
189 // Should timeout
190 assert!(result.is_err());
191
192 // Give a small grace period for the filesystem
193 thread::sleep(Duration::from_secs(5));
194
195 // File should not exist since process was killed
196 let exists = std::path::Path::new("test_timeout.txt").exists();
197
198 // Clean up
199 let _ = fs::remove_file("test_timeout.txt");
200 assert!(!exists, "Process wasn't killed in time - file was created");
201 }
202
203 #[process]
204 fn long_calculation(iterations: u64) -> u64 {
205 let mut sum: u64 = 0;
206 for i in 0..iterations {
207 sum = sum.wrapping_add(i);
208 if i % 1000 == 0 {
209 // Small sleep to make it actually take some time
210 thread::sleep(Duration::from_micros(1));
211 }
212 }
213 sum
214 }
215
216 #[test]
217 fn test_long_calculation() {
218 let iterations = 1_000_000;
219 let mut process = long_calculation_process(iterations).unwrap();
220 let start_time = std::time::Instant::now();
221 let result = process.timeout(Duration::from_secs(5));
222 let elapsed = start_time.elapsed();
223 assert!(
224 result.is_ok(),
225 "Long calculation should complete within timeout"
226 );
227 assert!(
228 elapsed < Duration::from_secs(3),
229 "Long calculation should complete within timeout and return early"
230 );
231 // Verify the result matches in-process calculation
232 let expected = long_calculation(iterations);
233 assert_eq!(result.unwrap(), expected);
234 }
235}