cedar_policy_symcc/symcc/
solver_pool.rs1use super::smtlib_script::SmtLibScript;
46use super::solver::{Decision, DecisionWithModel, LocalSolver, Solver, SolverError};
47use miette::Diagnostic;
48use std::io;
49use std::pin::Pin;
50use std::sync::Arc;
51use std::task::{Context, Poll};
52use std::time::Duration;
53use thiserror::Error;
54use tokio::io::AsyncWrite;
55use tokio::sync::{Mutex, OwnedSemaphorePermit, Semaphore};
56
57#[derive(Debug, Diagnostic, Error)]
59pub enum SolverPoolError {
60 #[error("solver pool semaphore closed unexpectedly")]
63 SemaphoreClosed,
64
65 #[error("timeout waiting for solver from pool")]
68 AcquireTimeout,
69
70 #[error(transparent)]
72 #[diagnostic(transparent)]
73 Solver(#[from] SolverError),
74}
75
76#[derive(Debug, Default)]
78struct FailedWriter;
79
80impl FailedWriter {
81 fn error() -> io::Error {
82 io::Error::other(SolverError::SolverMarkedFailed)
83 }
84}
85
86impl AsyncWrite for FailedWriter {
87 fn poll_write(
88 self: Pin<&mut Self>,
89 _cx: &mut Context<'_>,
90 _buf: &[u8],
91 ) -> Poll<io::Result<usize>> {
92 Poll::Ready(Err(Self::error()))
93 }
94
95 fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
96 Poll::Ready(Err(Self::error()))
97 }
98
99 fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
100 Poll::Ready(Err(Self::error()))
101 }
102}
103
104#[derive(Clone, Debug)]
106pub struct SolverPoolConfig {
107 pub min_solvers: usize,
110 pub max_solvers: usize,
113 pub acquire_timeout: Option<Duration>,
117}
118
119impl Default for SolverPoolConfig {
120 fn default() -> Self {
121 Self {
122 min_solvers: 1,
123 max_solvers: 4,
124 acquire_timeout: None,
125 }
126 }
127}
128
129#[derive(Debug)]
141pub struct SolverPool {
142 available: Arc<Mutex<Vec<LocalSolver>>>,
144 semaphore: Arc<Semaphore>,
146 config: SolverPoolConfig,
148}
149
150impl SolverPool {
151 pub async fn new(config: SolverPoolConfig) -> Result<Self, SolverError> {
156 let semaphore = Arc::new(Semaphore::new(config.max_solvers));
157 let mut solvers = Vec::with_capacity(config.max_solvers);
158
159 for _ in 0..config.min_solvers {
161 solvers.push(LocalSolver::cvc5()?);
162 }
163
164 Ok(Self {
165 available: Arc::new(Mutex::new(solvers)),
166 semaphore,
167 config,
168 })
169 }
170
171 pub async fn acquire(&self) -> Result<PooledSolver, SolverPoolError> {
182 let permit = match self.config.acquire_timeout {
184 Some(timeout) => {
185 match tokio::time::timeout(timeout, Arc::clone(&self.semaphore).acquire_owned())
186 .await
187 {
188 Ok(Ok(permit)) => permit,
189 Ok(Err(_)) => return Err(SolverPoolError::SemaphoreClosed),
190 Err(_) => return Err(SolverPoolError::AcquireTimeout),
191 }
192 }
193 None => Arc::clone(&self.semaphore)
194 .acquire_owned()
195 .await
196 .map_err(|_| SolverPoolError::SemaphoreClosed)?,
197 };
198
199 let solver = {
201 let mut available = self.available.lock().await;
202 available.pop()
203 };
204
205 let solver = match solver {
206 Some(s) => s,
207 None => {
208 LocalSolver::cvc5()?
210 }
211 };
212
213 Ok(PooledSolver {
214 solver: Some(solver),
215 pool: Arc::clone(&self.available),
216 permit: Some(permit),
217 failed_writer: FailedWriter,
218 })
219 }
220
221 pub async fn available_count(&self) -> usize {
223 self.available.lock().await.len()
224 }
225
226 pub fn permits_available(&self) -> usize {
229 self.semaphore.available_permits()
230 }
231}
232
233#[derive(Debug)]
239pub struct PooledSolver {
240 solver: Option<LocalSolver>,
242 pool: Arc<Mutex<Vec<LocalSolver>>>,
244 permit: Option<OwnedSemaphorePermit>,
247 failed_writer: FailedWriter,
249}
250
251impl PooledSolver {
252 pub fn mark_failed(&mut self) {
257 self.solver = None;
258 }
259}
260
261impl Solver for PooledSolver {
262 fn smtlib_input(&mut self) -> &mut (dyn tokio::io::AsyncWrite + Unpin + Send) {
263 match &mut self.solver {
264 Some(s) => s.smtlib_input(),
265 None => &mut self.failed_writer,
266 }
267 }
268
269 async fn enable_models(&mut self) -> Result<(), SolverError> {
270 let solver = self
273 .solver
274 .as_mut()
275 .ok_or(SolverError::SolverMarkedFailed)?;
276 solver.enable_models().await
277 }
278
279 async fn check_sat(&mut self) -> Result<Decision, SolverError> {
280 let solver = self
281 .solver
282 .as_mut()
283 .ok_or(SolverError::SolverMarkedFailed)?;
284 solver.check_sat().await
285 }
286
287 async fn check_sat_with_model(&mut self) -> Result<DecisionWithModel, SolverError> {
288 let solver = self
289 .solver
290 .as_mut()
291 .ok_or(SolverError::SolverMarkedFailed)?;
292 solver.check_sat_with_model().await
293 }
294}
295
296impl Drop for PooledSolver {
297 fn drop(&mut self) {
298 let pool = Arc::clone(&self.pool);
299 let solver = self.solver.take();
300 let permit = self.permit.take();
301
302 tokio::spawn(async move {
306 if let Some(mut solver) = solver {
307 if solver.smtlib_input().reset().await.is_ok() {
309 pool.lock().await.push(solver);
311 }
312 }
314 drop(permit);
316 });
317 }
318}
319
320#[cfg(test)]
321mod test {
322 use super::*;
323 use cool_asserts::assert_matches;
324
325 async fn poll_until<F, Fut>(mut condition: F)
328 where
329 F: FnMut() -> Fut,
330 Fut: std::future::Future<Output = bool>,
331 {
332 for _ in 0..10 {
333 if condition().await {
334 return;
335 }
336 tokio::time::sleep(Duration::from_millis(10)).await;
337 }
338 panic!("condition not met after 100ms of polling");
339 }
340
341 #[tokio::test]
342 async fn test_pool_creation() {
343 let pool = SolverPool::new(SolverPoolConfig {
344 min_solvers: 2,
345 max_solvers: 4,
346 acquire_timeout: None,
347 })
348 .await
349 .unwrap();
350
351 assert_eq!(pool.available_count().await, 2);
353 assert_eq!(pool.permits_available(), 4);
354 }
355
356 #[tokio::test]
357 async fn test_acquire_release() {
358 let pool = SolverPool::new(SolverPoolConfig {
359 min_solvers: 1,
360 max_solvers: 4,
361 acquire_timeout: None,
362 })
363 .await
364 .unwrap();
365
366 assert_eq!(pool.available_count().await, 1);
367
368 {
369 let mut solver = pool.acquire().await.unwrap();
370 assert_eq!(pool.available_count().await, 0);
372
373 let decision = solver.check_sat().await.unwrap();
375 assert_eq!(decision, Decision::Sat);
376 }
377
378 poll_until(|| async { pool.available_count().await == 1 }).await;
380 }
381
382 #[tokio::test]
383 async fn test_solver_reuse() {
384 let pool = SolverPool::new(SolverPoolConfig {
385 min_solvers: 1,
386 max_solvers: 1,
387 acquire_timeout: None,
388 })
389 .await
390 .unwrap();
391
392 {
394 let mut solver = pool.acquire().await.unwrap();
395 solver.smtlib_input().assert("false").await.unwrap();
396 let decision = solver.check_sat().await.unwrap();
397 assert_eq!(decision, Decision::Unsat);
398 }
399
400 poll_until(|| async { pool.available_count().await == 1 }).await;
402
403 {
405 let mut solver = pool.acquire().await.unwrap();
406 solver.enable_models().await.unwrap();
408 solver.smtlib_input().assert("true").await.unwrap();
409 let decision = solver.check_sat_with_model().await.unwrap();
410 assert_matches!(decision, DecisionWithModel::Sat { .. });
411 }
412 }
413
414 #[tokio::test]
415 async fn test_concurrent_acquire() {
416 let pool = Arc::new(
417 SolverPool::new(SolverPoolConfig {
418 min_solvers: 2,
419 max_solvers: 4,
420 acquire_timeout: None,
421 })
422 .await
423 .unwrap(),
424 );
425
426 let handles: Vec<_> = (0..4)
427 .map(|_| {
428 let pool = Arc::clone(&pool);
429 tokio::spawn(async move {
430 let mut solver = pool.acquire().await.unwrap();
431 let decision = solver.check_sat().await.unwrap();
432 assert_eq!(decision, Decision::Sat);
433 })
434 })
435 .collect();
436
437 for handle in handles {
438 handle.await.unwrap();
439 }
440 }
441
442 #[tokio::test]
443 async fn test_pool_exhaustion_blocks() {
444 let pool = Arc::new(
445 SolverPool::new(SolverPoolConfig {
446 min_solvers: 1,
447 max_solvers: 1,
448 acquire_timeout: Some(Duration::from_millis(100)),
449 })
450 .await
451 .unwrap(),
452 );
453
454 let _solver = pool.acquire().await.unwrap();
456
457 let result = pool.acquire().await;
459 assert_matches!(result, Err(SolverPoolError::AcquireTimeout));
460 }
461
462 #[tokio::test]
463 async fn test_failed_solver_discarded() {
464 let pool = SolverPool::new(SolverPoolConfig {
465 min_solvers: 1,
466 max_solvers: 1,
467 acquire_timeout: None,
468 })
469 .await
470 .unwrap();
471
472 {
473 let mut solver = pool.acquire().await.unwrap();
474 solver.smtlib_input().assert("tomato").await.unwrap();
476 let result = solver.check_sat().await;
478 assert_matches!(result, Err(SolverError::Solver(_)));
479 solver.mark_failed();
481 }
482
483 poll_until(|| async { pool.permits_available() == 1 }).await;
485
486 assert_eq!(pool.available_count().await, 0);
488
489 let mut solver = pool.acquire().await.unwrap();
491 let decision = solver.check_sat().await.unwrap();
492 assert_eq!(decision, Decision::Sat);
493 }
494
495 #[tokio::test]
496 async fn test_methods_after_mark_failed() {
497 use tokio::io::AsyncWriteExt;
498
499 let pool = SolverPool::new(SolverPoolConfig::default()).await.unwrap();
500 let mut solver = pool.acquire().await.unwrap();
501
502 solver.mark_failed();
504
505 let result = solver.check_sat().await;
507 assert_matches!(result, Err(SolverError::SolverMarkedFailed));
508
509 let result = solver.check_sat_with_model().await;
511 assert_matches!(result, Err(SolverError::SolverMarkedFailed));
512
513 let result = solver.smtlib_input().write_all(b"test").await;
515 assert!(result.is_err());
516
517 let result = solver.smtlib_input().flush().await;
519 assert!(result.is_err());
520
521 let result = solver.smtlib_input().shutdown().await;
523 assert!(result.is_err());
524 }
525
526 #[tokio::test]
527 async fn test_default_config() {
528 let config = SolverPoolConfig::default();
529 assert_eq!(config.min_solvers, 1);
530 assert_eq!(config.max_solvers, 4);
531 assert!(config.acquire_timeout.is_none());
532 }
533
534 #[tokio::test]
535 async fn test_pooled_solver_with_symcompiler() {
536 use crate::symcc::SymCompiler;
537
538 let pool = SolverPool::new(SolverPoolConfig::default()).await.unwrap();
539
540 let solver = pool.acquire().await.unwrap();
542
543 let mut compiler = SymCompiler::new(solver);
545
546 compiler
549 .solver_mut()
550 .smtlib_input()
551 .set_logic("ALL")
552 .await
553 .unwrap();
554 compiler
555 .solver_mut()
556 .smtlib_input()
557 .assert("(= 1 2)")
558 .await
559 .unwrap();
560 let decision = compiler.solver_mut().check_sat().await.unwrap();
561 assert_eq!(decision, Decision::Unsat);
562 }
563}