|
| 1 | +//! This example demonstrates how to block signals in a parent process while |
| 2 | +//! executing code in an isolated process. |
| 3 | +//! |
| 4 | +//! This is useful if: |
| 5 | +//! |
| 6 | +//! 1. You want to simulate the expected behavior of running your code without |
| 7 | +//! `mem_isolate::execute_in_isolated_process()`, which would be guaranteed |
| 8 | +//! to treat signals the same both inside and outside of the `callable()` |
| 9 | +//! function. |
| 10 | +//! 2. You want to prevent either process from being interrupted by signals |
| 11 | +//! while your `callable()` is running. |
| 12 | +//! 3. You want to ensure that the parent process is not killed while the |
| 13 | +//! isolated process is running, leaving an orphaned child process. |
| 14 | +//! |
| 15 | +//! It is important to remember that `mem_isolate::execute_in_isolated_process()` |
| 16 | +//! uses `fork()` under the hood, which creates a child process that will not |
| 17 | +//! receive signals sent to the main process as your `callable()` would otherwise. |
| 18 | +//! |
| 19 | +//! Run this example with `cargo run --example blocking-signals-demonstration` |
| 20 | +//! |
| 21 | +//! This example is great for illustrating how signal blocking works, but if you |
| 22 | +//! want to just copy and paste some code, see `blocking-signals-minimal.rs` |
| 23 | +//! |
| 24 | +//! NOTE: Because both SIGKILL and SIGSTOP are unblockable, nothing can be done |
| 25 | +//! to prevent them from killing the parent process or the child process. |
| 26 | +//! |
| 27 | +//! WARNING: Do not expect signal handling to work as you might think in a |
| 28 | +//! multi-threaded program. It is not recommended to use `mem_isolate` in a |
| 29 | +//! multi-threaded program anyway, so that's generally OK. |
| 30 | +
|
| 31 | +use mem_isolate::{MemIsolateError, execute_in_isolated_process}; |
| 32 | +use nix::errno::Errno; |
| 33 | +use nix::sys::signal::{SigSet, SigmaskHow, sigprocmask}; |
| 34 | +use nix::unistd::Pid; |
| 35 | +use std::process::{Child, Command, Stdio}; |
| 36 | +use std::thread; |
| 37 | +use std::time::Duration; |
| 38 | + |
| 39 | +fn main() -> Result<(), MemIsolateError> { |
| 40 | + let parent_pid = Pid::this(); |
| 41 | + println!("Parent PID: {}", parent_pid); |
| 42 | + let wait_time_for_readability = Duration::from_secs(5); |
| 43 | + |
| 44 | + // Get closures for blocking and restoring signals |
| 45 | + let (block_signals, restore_signals) = get_block_and_restore_signal_closures(); |
| 46 | + |
| 47 | + // Block all signals before calling `mem_isolate::execute_in_isolated_process()` |
| 48 | + // This ensures the main program won't be killed leaving an orphaned child process |
| 49 | + println!("Parent: Blocking all signals"); |
| 50 | + block_signals().expect("Failed to block signals"); |
| 51 | + |
| 52 | + // Kick-off a subprocess that will send a SIGTERM to this process |
| 53 | + let sigterm_sender_proc = send_sigterm_to_parent(Duration::from_secs(1)); |
| 54 | + |
| 55 | + // Run your code in an isolated process. NOTE: The child process created by |
| 56 | + // `fork()` inside `execute_in_isolated_process()` will inherit the signal |
| 57 | + // mask set by main process just above. |
| 58 | + let result = execute_in_isolated_process(move || { |
| 59 | + println!( |
| 60 | + "\nChild: I've started executing a user-defined callable. I'll wait for {} seconds before exiting...", |
| 61 | + wait_time_for_readability.as_secs() |
| 62 | + ); |
| 63 | + thread::sleep(wait_time_for_readability); |
| 64 | + println!("Child: I'm all done now, exiting\n"); |
| 65 | + }); |
| 66 | + |
| 67 | + reap_child_process_so_it_doesnt_become_a_zombie(sigterm_sender_proc); |
| 68 | + |
| 69 | + println!( |
| 70 | + "Parent: Notice that the SIGTERM is pending and didn't interrupt this process or the child process. Unblocking signals in {} seconds...", |
| 71 | + wait_time_for_readability.as_secs() |
| 72 | + ); |
| 73 | + thread::sleep(wait_time_for_readability); |
| 74 | + println!("Parent: Restoring signals, expect the parent to now recieve the SIGTERM"); |
| 75 | + restore_signals().expect("Failed to restore signals"); |
| 76 | + // WARNING: Don't expect code to ever reach this point, because the pending SIGTERM will kill the parent process |
| 77 | + // as soon as we unblock the SIGTERM that has been pending this whole time |
| 78 | + println!("Parent: Notice how I never ran"); |
| 79 | + result |
| 80 | +} |
| 81 | + |
| 82 | +fn get_block_and_restore_signal_closures() -> ( |
| 83 | + impl FnOnce() -> Result<(), Errno>, |
| 84 | + impl FnOnce() -> Result<(), Errno>, |
| 85 | +) { |
| 86 | + let all_signals = SigSet::all(); |
| 87 | + let mut old_signals = SigSet::empty(); |
| 88 | + |
| 89 | + let block_signals = move || { |
| 90 | + sigprocmask( |
| 91 | + SigmaskHow::SIG_SETMASK, |
| 92 | + Some(&all_signals), |
| 93 | + Some(&mut old_signals), |
| 94 | + ) |
| 95 | + }; |
| 96 | + |
| 97 | + let restore_signals = move || sigprocmask(SigmaskHow::SIG_SETMASK, Some(&old_signals), None); |
| 98 | + |
| 99 | + (block_signals, restore_signals) |
| 100 | +} |
| 101 | + |
| 102 | +fn send_sigterm_to_parent(wait_time: Duration) -> Child { |
| 103 | + // We do this via a subprocess instead of a thread because the latter will |
| 104 | + // break the signal mask that we have set. `mem_isolate::execute_in_isolated_process()` |
| 105 | + // also SHOULD NOT be used in a multi-threaded program (see limitations in README) |
| 106 | + println!( |
| 107 | + "Parent: Sending SIGTERM to self in {} seconds. NOTE: This signal will be sent to the parent while the child is executing the user-defined callable", |
| 108 | + wait_time.as_secs() |
| 109 | + ); |
| 110 | + Command::new("sh") |
| 111 | + .arg("-c") |
| 112 | + .arg(format!( |
| 113 | + "sleep {} && kill -s TERM {}", |
| 114 | + wait_time.as_secs(), |
| 115 | + Pid::this() |
| 116 | + )) |
| 117 | + .stdout(Stdio::null()) |
| 118 | + .stderr(Stdio::null()) |
| 119 | + .spawn() |
| 120 | + .expect("Failed to spawn delayed kill command") |
| 121 | +} |
| 122 | + |
| 123 | +fn reap_child_process_so_it_doesnt_become_a_zombie(mut child: Child) { |
| 124 | + let exit_status = child.wait().expect("Failed to wait for child process"); |
| 125 | + if !exit_status.success() { |
| 126 | + panic!("Other process: failed to send SIGTERM to parent process"); |
| 127 | + } |
| 128 | +} |
0 commit comments