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}