rsmt2/
asynch.rs

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