1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
//! This modules handles asynchronous interactions with the solver. //! //! The related function is //! [`Solver::async_check_sat_or_unk`](crate::Solver::async_check_sat_or_unk). //! //! Presently, only asynchronous check-sat-s are supported. The idea is to wrap the whole solver in //! a new thread, and yield a future of the check-sat result. The new thread starts parsing the //! output right away, which is going to block it. Once it's done, it sends a message to its parent //! thread with the result. //! //! This module uses unsafe code so that the wrapper retains the `&mut solver` (for lifetimes), but //! the background thread can read the solver's output. If none of your queries on the wrapper //! produce a result, *i.e.* the solver is still trying to answer the check-sat, and the wrapper is //! dropped, the drop action **will wait for the slave thread to produce an answer**. This is //! because otherwise the background thread would keep on listening to `solver`, making using //! `solver` after the wrapper is dropped unsafe. On this topic, do read the following remark. //! //! > **NB:** when the check *runs forever*, the slave process will keep on waiting for the solver //! > to answer. If you want to be able to keep using the solver in this case, you should spawn the //! > solver with the right options to add a timeout. For Z3, `-t:500` for a (soft, per query) //! > timeout, which makes it report `unknown`. This is what the examples below do. //! > //! > This is why [`Solver::async_check_sat_or_unk`](crate::Solver::async_check_sat_or_unk) is //! > marked `unsafe`: if not careful, one can end up with a background process burning 100% CPU. //! //! # Examples //! //! These examples use the second SMT-LIB script from this [stack overflow question] as a script //! that Z3 struggles with. //! //! ```rust //! fn do_smt_stuff() -> rsmt2::SmtRes<()> { //! use std::io::Write; //! let parser = () ; //! use rsmt2::SmtConf ; //! //! let mut conf = SmtConf::default_z3(); //! // Setting a timeout because our query will run "forever" otherwise. //! conf.option("-t:100"); //! //! let mut solver = conf.spawn(parser)? ; //! //! writeln!(solver, r#" //! (declare-fun f (Real) Real) //! (assert (forall ((a Real) (b Real)) ( //! = (+ (f a) (f b)) (f (+ a b)) //! ))) //! (assert (= (f 2) 4)) //! "#)?; //! //! { // The following mut-borrows `solver`, this scope lets us use `solver` again afterwards. //! //! let future = unsafe { solver.async_check_sat_or_unk() }; //! //! // Non-blocking. //! let maybe_result = future.try_get(); //! assert! { maybe_result.is_none() } //! //! // Blocking with timeout. //! let maybe_result = future.timeout_get(std::time::Duration::new(0, 50)); //! assert! { maybe_result.is_none() } //! //! // Blocking. //! let result = future.get(); //! assert! { result.expect("reached timeout").is_none() } //! } //! //! // We can re-use the solver now. //! let result = solver.check_sat_or_unk(); //! assert! { result.expect("reached timeout").is_none() } //! //! Ok(()) //! } //! do_smt_stuff().unwrap() //! ``` //! //! [stack overflow question]: https://stackoverflow.com/questions/43246090 use std::{ sync::mpsc::{channel, Receiver, RecvError, RecvTimeoutError, Sender, TryRecvError}, time::Duration, }; use crate::{errors::*, solver::Solver}; /// Messages sent by the slave thread. type AsyncMsg = SmtRes<Option<bool>>; /// Send channel for the slave thread. type AsyncSend = Sender<AsyncMsg>; /// Receive channel for the master thread. type AsyncRecv = Receiver<AsyncMsg>; /// Solver container for (unsafe) mutable reference sharing. struct Container<Parser> { inner: *mut Solver<Parser>, } unsafe impl<Parser: Send> Send for Container<Parser> {} /// Master thread. #[must_use] pub struct CheckSatFuture<'sref, Parser> { /// Channel for receiving messages from the slave thread. recv: AsyncRecv, /// Reference to the solver. solver: &'sref mut Solver<Parser>, } impl<'sref, Parser> Drop for CheckSatFuture<'sref, Parser> { fn drop(&mut self) { let _ = self.recv.recv(); () } } impl<'sref, Parser: Send + 'static> CheckSatFuture<'sref, Parser> { /// Constructor. pub fn new(solver: &'sref mut Solver<Parser>) -> Self { let (send, recv): (AsyncSend, AsyncRecv) = channel(); let slf = CheckSatFuture { recv, solver }; let solver = Container { inner: slf.solver as *mut Solver<Parser>, }; std::thread::spawn(move || { let solver = unsafe { &mut *solver.inner }; let result = solver.check_sat_or_unk(); send.send(result) }); slf } fn disconnected<T>() -> SmtRes<T> { bail!("error during asynchronous check-sat: disconnected from slave thread") } /// Retrieves the result of the check-sat. Blocking. pub fn get(&self) -> SmtRes<Option<bool>> { match self.recv.recv() { Ok(result) => result, Err(RecvError) => Self::disconnected(), } } /// Tries to retrieve the result of the check-sat. Non-blocking. pub fn try_get(&self) -> Option<SmtRes<Option<bool>>> { match self.recv.try_recv() { Ok(result) => Some(result), Err(TryRecvError::Empty) => None, Err(TryRecvError::Disconnected) => Some(Self::disconnected()), } } /// Tries to retrieve the result of the check-sat with a timeout. pub fn timeout_get(&self, timeout: Duration) -> Option<SmtRes<Option<bool>>> { match self.recv.recv_timeout(timeout) { Ok(result) => Some(result), Err(RecvTimeoutError::Timeout) => None, Err(RecvTimeoutError::Disconnected) => Some(Self::disconnected()), } } }