Skip to content

Commit 4d72927

Browse files
authored
Merge pull request #38 from tweag/qa/exit_command
`Process` backend: add a function for sending `(exit)`
2 parents 5179a35 + 1193534 commit 4d72927

File tree

4 files changed

+67
-29
lines changed

4 files changed

+67
-29
lines changed

smtlib-backends-process/CHANGELOG.md

+10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# v0.3-alpha
22
- make test-suite compatible with `smtlib-backends-0.3`
3+
- rename `Process.close` to `Process.kill`
4+
- rename `Process.wait` to `Process.close` and improve it
5+
- ensure the process gets killed even if an error is caught
6+
- send an `(exit)` command before waiting for the process to exit
7+
- this means `Process.with` now closes the process with this new version of
8+
`Process.close`, hence gracefully
9+
- add a `Process.write` function for writing commands without reading the
10+
solver's response
11+
- add a test checking that we can pile up procedures for exiting a process
12+
safely
313

414
# v0.2
515
split `smtlib-backends`'s `Process` module into its own library

smtlib-backends-process/src/SMTLIB/Backends/Process.hs

+32-17
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
{-# LANGUAGE DisambiguateRecordFields #-}
22
{-# LANGUAGE OverloadedStrings #-}
33
{-# LANGUAGE PatternSynonyms #-}
4+
{-# LANGUAGE ScopedTypeVariables #-}
45
{-# LANGUAGE ViewPatterns #-}
56

67
-- | A module providing a backend that launches solvers as external processes.
78
module SMTLIB.Backends.Process
89
( Config (..),
910
Handle (..),
1011
new,
11-
wait,
12+
write,
1213
close,
14+
kill,
1315
with,
1416
toBackend,
1517
)
@@ -28,7 +30,7 @@ import qualified Data.ByteString.Char8 as BS
2830
import qualified Data.ByteString.Lazy.Char8 as LBS
2931
import Data.Default (Default, def)
3032
import SMTLIB.Backends (Backend (..))
31-
import System.Exit (ExitCode)
33+
import System.Exit (ExitCode (ExitFailure))
3234
import qualified System.IO as IO
3335
import System.Process.Typed
3436
( Process,
@@ -106,23 +108,37 @@ new config = do
106108
)
107109
reportError' = reportError config . LBS.fromStrict
108110

109-
-- | Wait for the process to exit and cleanup its resources.
110-
wait :: Handle -> IO ExitCode
111-
wait handle = do
112-
cancel $ errorReader handle
113-
waitExitCode $ process handle
111+
-- | Send a command to the process without reading its response.
112+
write :: Handle -> Builder -> IO ()
113+
write handle cmd = do
114+
hPutBuilder (getStdin $ process handle) $ cmd <> "\n"
115+
IO.hFlush $ getStdin $ process handle
114116

115-
-- | Terminate the process, wait for it to actually exit and cleanup its resources.
116-
-- Don't use this if you're manually stopping the solver process by sending an
117-
-- @(exit)@ command. Use `wait` instead.
118-
close :: Handle -> IO ()
117+
-- | Cleanup the process' resources.
118+
cleanup :: Handle -> IO ()
119+
cleanup = cancel . errorReader
120+
121+
-- | Cleanup the process' resources, send it an @(exit)@ command and wait for it
122+
-- to exit.
123+
close :: Handle -> IO ExitCode
119124
close handle = do
120-
cancel $ errorReader handle
125+
cleanup handle
126+
let p = process handle
127+
( do
128+
write handle "(exit)"
129+
waitExitCode p
130+
)
131+
`X.catch` \(_ :: X.IOException) -> do
132+
stopProcess p
133+
return $ ExitFailure 1
134+
135+
-- | Cleanup the process' resources and kill it immediately.
136+
kill :: Handle -> IO ()
137+
kill handle = do
138+
cleanup handle
121139
stopProcess $ process handle
122140

123-
-- | Create a solver process, use it to make a computation and stop it.
124-
-- Don't use this if you're manually stopping the solver process by sending an
125-
-- @(exit)@ command. Use @\\config -> `System.IO.bracket` (`new` config) `wait`@ instead.
141+
-- | Create a solver process, use it to make a computation and close it.
126142
with ::
127143
-- | The solver process' configuration.
128144
Config ->
@@ -140,8 +156,7 @@ pattern c :< rest <- (BS.uncons -> Just (c, rest))
140156
toBackend :: Handle -> Backend
141157
toBackend handle =
142158
Backend $ \cmd -> do
143-
hPutBuilder (getStdin $ process handle) $ cmd <> "\n"
144-
IO.hFlush $ getStdin $ process handle
159+
write handle cmd
145160
toLazyByteString <$> continueNextLine (scanParen 0) mempty
146161
where
147162
-- scanParen read lines from the handle's output channel until it has detected

smtlib-backends-process/tests/Examples.hs

+17-11
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@ module Examples (examples) where
44

55
import qualified Data.ByteString.Lazy.Char8 as LBS
66
import Data.Default (def)
7-
import SMTLIB.Backends (QueuingFlag (..), command, command_, initSolver)
7+
import SMTLIB.Backends (QueuingFlag (..), command, initSolver)
88
import qualified SMTLIB.Backends.Process as Process
9-
import System.Exit (ExitCode (ExitSuccess))
109
import System.IO (BufferMode (LineBuffering), hSetBuffering)
1110
import System.Process.Typed (getStdin)
1211
import Test.Tasty
@@ -40,6 +39,8 @@ basicUse =
4039
-- we can write the command as a simple string because we have enabled the
4140
-- OverloadedStrings pragma
4241
_ <- command solver "(get-info :name)"
42+
-- note how there is no need to send an @(exit)@ command, this is already
43+
-- handled by the 'Process.with' function
4344
return ()
4445

4546
-- | An example of how to change the default settings of the 'Process' backend.
@@ -74,12 +75,17 @@ manualExit :: IO ()
7475
manualExit = do
7576
-- launch a new process with 'Process.new'
7677
handle <- Process.new def
77-
let backend = Process.toBackend handle
78-
-- here we disable queuing so that we can use 'command_' to ensure the exit
79-
-- command will be received successfully
80-
solver <- initSolver NoQueuing backend
81-
command_ solver "(exit)"
82-
-- 'Process.wait' takes care of cleaning resources and waits for the process to
83-
-- exit
84-
exitCode <- Process.wait handle
85-
assertBool "the solver process didn't exit properly" $ exitCode == ExitSuccess
78+
-- do some stuff
79+
doStuffWithHandle handle
80+
-- kill the process with 'Process.kill'
81+
-- other options include using 'Process.close' to ensure the process exits
82+
-- gracefully
83+
--
84+
-- if this isn't enough for you, it is always possible to send an @(exit)@
85+
-- command using 'Process.write', access the solver process using
86+
-- 'Process.process' and kill it manually
87+
-- if this is what you go with, don't forget to also cancel the
88+
-- 'Process.errorReader' asynchronous process!
89+
Process.kill handle
90+
where
91+
doStuffWithHandle _ = return ()

smtlib-backends-process/tests/Main.hs

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
{-# LANGUAGE OverloadedStrings #-}
2+
13
import Data.Default (def)
24
import Examples (examples)
35
import qualified SMTLIB.Backends.Process as Process
46
import SMTLIB.Backends.Tests (sources, testBackend)
57
import Test.Tasty
8+
import Test.Tasty.HUnit
69

710
main :: IO ()
811
main = do
@@ -11,5 +14,9 @@ main = do
1114
"Tests"
1215
[ testBackend "Basic examples" sources $ \todo ->
1316
Process.with def $ todo . Process.toBackend,
14-
testGroup "API usage examples" examples
17+
testGroup "API usage examples" examples,
18+
testCase "Piling up stopping procedures" $ Process.with def $ \handle -> do
19+
Process.write handle "(exit)"
20+
_ <- Process.close handle
21+
Process.kill handle
1522
]

0 commit comments

Comments
 (0)