{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE DoAndIfThenElse #-} {-# LANGUAGE PatternSignatures #-} {-# LANGUAGE StandaloneDeriving #-} {-# language TemplateHaskell #-} -- | call an external solver as separate process, -- communicate via pipes. module Satchmo.SAT.External ( SAT , fresh , emit , solve -- , solve_with_timeout ) where import Satchmo.Data import Satchmo.Boolean hiding ( not ) import Satchmo.Code -- import Satchmo.MonadSAT import Control.Monad.Reader import Control.Monad.State -- import Control.Monad.IO.Class import System.IO import Control.Lens import Control.Applicative import Control.Concurrent import Control.DeepSeq (rnf) import Foreign.C -- import System.Exit (ExitCode(..)) import System.Process -- import System.IO.Error -- import System.Posix.Types import Control.Exception import GHC.IO.Exception ( IOErrorType(..), IOException(..) ) -- import System.Posix.Signals import qualified Control.Exception as C import qualified Data.ByteString.Char8 as BS import qualified Data.Map.Strict as M import Data.List (isPrefixOf) tracing = False report s = when tracing $ hPutStrLn stderr s data S = S { _next_variable :: ! Int , _solver_input :: ! Handle } $(makeLenses ''S) newtype SAT a = SAT (StateT S IO a) deriving (Functor, Applicative, Monad, MonadIO) type Assignment = M.Map Int Bool newtype Dec a = Dec (Reader Assignment a) deriving (Functor, Applicative, Monad) instance MonadSAT SAT where fresh = SAT $ do n <- use next_variable next_variable .= succ n return $ literal True $ fromEnum n emit cl = SAT $ do h <- use solver_input let s = BS.pack $ show cl -- liftIO $ BS.putStrLn s liftIO $ BS.hPutStrLn h s note msg = SAT $ liftIO $ hPutStrLn stderr msg type Decoder SAT = Dec instance Decode Dec Boolean Bool where decode b = case b of Constant c -> return c Boolean l -> do v <- dv $ variable l return $ if positive l then v else not v dv v = Dec $ do assignment <- ask return $ case M.lookup v assignment of Just v -> v Nothing -> error $ unwords [ "unassigned", "variable", show v ] solve :: String -- ^ command, e.g., glucose -> [String] -- ^ options, e.g., -model -> SAT (Dec a) -- ^ action that builds the formula and returns the decoder -> IO (Maybe a) solve command opts (SAT action) = bracket ( do report "Satchmo.SAT.External: creating process" createProcess $ (proc command opts) { std_in = CreatePipe , std_out = CreatePipe , create_group = True } ) ( \ (Just sin, Just sout, _, ph) -> do report "Satchmo.SAT.External: bracket closing" interruptProcessGroupOf ph ) $ \ (Just sin, Just sout, _, ph) -> do dec <- newEmptyMVar -- fork off a thread to start consuming the output output <- hGetContents sout -- lazy IO withForkWait (C.evaluate $ rnf output) $ \ waitOut -> ignoreSigPipe $ do report $ "S.S.External: waiter forked" let s0 = S { _next_variable=1, _solver_input=sin} report $ "S.S.External: writing output" Dec decoder <- evalStateT action s0 putMVar dec decoder hClose sin waitOut hClose sout report $ "S.S.External: waiter done" report "Satchmo.SAT.External: start waiting" waitForProcess ph decoder <- takeMVar dec report "Satchmo.SAT.External: waiting done" let vlines = do line <- lines output guard $ isPrefixOf "v" line return line report $ show vlines let vs = do line <- vlines w <- tail $ words line return (read w :: Int) return $ do guard $ not $ null vlines let m = M.fromList $ do v <- vs ; guard $ v /= 0 ; return (abs v, v>0) return $ runReader decoder m -- * code from System.Process -- http://hackage.haskell.org/package/process-1.2.3.0/docs/src/System-Process.html#readProcess -- but they are not exporting withForkWait, so I have to copy it -- | Fork a thread while doing something else, but kill it if there's an -- exception. -- -- This is important in the cases above because we want to kill the thread -- that is holding the Handle lock, because when we clean up the process we -- try to close that handle, which could otherwise deadlock. -- withForkWait :: IO () -> (IO () -> IO a) -> IO a withForkWait async body = do waitVar <- newEmptyMVar :: IO (MVar (Either SomeException ())) mask $ \restore -> do tid <- forkIO $ try (restore async) >>= putMVar waitVar let wait = takeMVar waitVar >>= either throwIO return restore (body wait) `C.onException` killThread tid ignoreSigPipe :: IO () -> IO () ignoreSigPipe = C.handle $ \e -> case e of IOError { ioe_type = ResourceVanished , ioe_errno = Just ioe } | Errno ioe == ePIPE -> return () _ -> throwIO e