summaryrefslogtreecommitdiff
path: root/source/Provers.hs
diff options
context:
space:
mode:
Diffstat (limited to 'source/Provers.hs')
-rw-r--r--source/Provers.hs642
1 files changed, 444 insertions, 198 deletions
diff --git a/source/Provers.hs b/source/Provers.hs
index 7f85c29..941360c 100644
--- a/source/Provers.hs
+++ b/source/Provers.hs
@@ -32,11 +32,6 @@ module Provers
, preparedVerificationBytes
, preparedVerificationByteCount
, preparedVerificationRequestId
- , PreparedProverTask
- , prepareProverTask
- , preparedProverLogicalTask
- , preparedProverRequest
- , preparedProverTptpTask
, PreparedTypedProverTask
, prepareTypedProverTask
, preparedTypedProverLogicalProblem
@@ -45,24 +40,57 @@ module Provers
, AcceptedVampireRun
, acceptedVampireRequest
, provedVampireRun
- , runProver
- , runPreparedProver
- , runPreparedProverWithObserver
, runPreparedTypedProver
, runPreparedTypedProverWithObserver
- , runVampireProcess
+ , EffectiveJobs
+ , effectiveJobs
+ , effectiveJobsValue
+ , JobsSelection(..)
+ , selectEffectiveJobs
+ , WorkPosition
+ , workPosition
+ , workPositionModuleOrdinal
+ , workPositionLocalRequestOrdinal
+ , VampireExecutor
+ , VampireExecutorObservation(..)
+ , withVampireExecutor
+ , runPreparedTypedProverWithExecutor
+ , vampireExecutorObservation
) where
import Base
import Checking.Authority qualified as Authority
import Checking.Backend.Problem
import Checking.Backend.Tptp
-import Encoding
-import Report.Location
-import Syntax.Internal (Directness(..), Formula, Task(..), isIndirect)
-import Control.Exception (IOException, displayException)
+import Control.Concurrent.STM
+ ( TBQueue
+ , TMVar
+ , TVar
+ , atomically
+ , check
+ , modifyTVar'
+ , newEmptyTMVarIO
+ , newTBQueueIO
+ , newTVarIO
+ , orElse
+ , putTMVar
+ , readTBQueue
+ , readTVar
+ , readTVarIO
+ , takeTMVar
+ , writeTBQueue
+ , writeTVar
+ )
+import Control.Exception
+ ( AsyncException
+ , IOException
+ , SomeException
+ , displayException
+ , fromException
+ )
import Control.Exception qualified as Exception
+import Control.Monad (forever, replicateM, unless)
import Control.Monad.Logger
import Data.ByteString qualified as ByteString
import Data.IORef
@@ -76,6 +104,7 @@ import Data.Set qualified as Set
import Data.Text qualified as Text
import Data.Text.Encoding qualified as TextEncoding
import Data.Time
+import Numeric.Natural (Natural)
import System.Exit (ExitCode(..))
import System.Posix.Signals (sigKILL, signalProcessGroup)
import System.Posix.Types (ProcessGroupID)
@@ -92,7 +121,13 @@ import System.Timeout qualified as Timeout
import Text.Megaparsec
import Text.Megaparsec.Char qualified as Char
import TextBuilder
-import UnliftIO.Async (concurrently)
+import UnliftIO.Async
+ ( async
+ , cancel
+ , concurrently
+ , waitCatch
+ , waitCatchSTM
+ )
data Vampire = Vampire
{ vampireExecutable :: FilePath
@@ -119,9 +154,92 @@ vampireArguments Vampire{..} =
, "--mode", "casc"
, "--time_limit", toSeconds vampireTimeLimit
, "--memory_limit", toMegabytes vampireMemoryLimit
- , "--cores", "2"
+ , "--cores", "1"
]
+-- | A strictly positive invocation-local worker bound.
+newtype EffectiveJobs = EffectiveJobs Int
+ deriving (Show, Eq, Ord)
+
+effectiveJobs :: Int -> Maybe EffectiveJobs
+effectiveJobs amount
+ | amount > 0 = Just (EffectiveJobs amount)
+ | otherwise = Nothing
+
+effectiveJobsValue :: EffectiveJobs -> Int
+effectiveJobsValue (EffectiveJobs amount) = amount
+
+-- | The effective worker policy selected for one invocation. Processor
+-- discovery is operational evidence only and enters no durable identity.
+data JobsSelection = JobsSelection
+ { jobsSelectionDetectedProcessors :: !(Maybe Int)
+ , jobsSelectionEffectiveJobs :: !EffectiveJobs
+ , jobsSelectionWasOverridden :: !Bool
+ }
+ deriving (Show, Eq)
+
+-- | Select the worker bound, with processor discovery injected for focused
+-- testing. Asynchronous cancellation is never mistaken for failed discovery.
+selectEffectiveJobs
+ :: Maybe EffectiveJobs
+ -> IO Int
+ -> IO JobsSelection
+selectEffectiveJobs override detectProcessors =
+ case override of
+ Just selected ->
+ pure
+ JobsSelection
+ { jobsSelectionDetectedProcessors = Nothing
+ , jobsSelectionEffectiveJobs = selected
+ , jobsSelectionWasOverridden = True
+ }
+ Nothing -> do
+ detectedResult <- Exception.try detectProcessors
+ case detectedResult of
+ Left detectionFailure
+ | Just asynchronous <-
+ (fromException detectionFailure
+ :: Maybe AsyncException) ->
+ Exception.throwIO asynchronous
+ | otherwise ->
+ fallback
+ Right detected
+ | detected > 0 ->
+ pure
+ JobsSelection
+ { jobsSelectionDetectedProcessors =
+ Just detected
+ , jobsSelectionEffectiveJobs =
+ EffectiveJobs (max 1 (detected - 1))
+ , jobsSelectionWasOverridden = False
+ }
+ | otherwise ->
+ fallback
+ where
+ fallback =
+ pure
+ JobsSelection
+ { jobsSelectionDetectedProcessors = Nothing
+ , jobsSelectionEffectiveJobs = EffectiveJobs 1
+ , jobsSelectionWasOverridden = False
+ }
+
+-- | Stable runtime diagnostic position. It is deliberately separate from
+-- request, validation, cache, and mathematical identities.
+data WorkPosition = WorkPosition !Natural !Natural
+ deriving (Show, Eq, Ord)
+
+workPosition :: Natural -> Natural -> WorkPosition
+workPosition = WorkPosition
+
+workPositionModuleOrdinal :: WorkPosition -> Natural
+workPositionModuleOrdinal (WorkPosition moduleOrdinal _localOrdinal) =
+ moduleOrdinal
+
+workPositionLocalRequestOrdinal :: WorkPosition -> Natural
+workPositionLocalRequestOrdinal (WorkPosition _moduleOrdinal localOrdinal) =
+ localOrdinal
+
toSeconds :: TimeLimit -> String
toSeconds (Seconds secs) = show secs
@@ -292,39 +410,6 @@ preparedVerificationRequestId request =
IndirectTask -> Authority.PreparedRequestIndirect)
(preparedVerificationBytes request)
-data PreparedProverTask = PreparedProverTask
- !Task
- !PreparedTptpTask
- !PreparedVerificationRequest
-
-prepareProverTask :: Task -> PreparedProverTask
-prepareProverTask task =
- let preparedTptp = prepareTptpTask task
- preparedRequest =
- prepareVerificationRequest task preparedTptp
- in
- PreparedProverTask
- task
- preparedTptp
- preparedRequest
-
-preparedProverLogicalTask :: PreparedProverTask -> Task
-preparedProverLogicalTask
- (PreparedProverTask task _preparedTptp _preparedRequest) =
- task
-
-preparedProverRequest
- :: PreparedProverTask
- -> PreparedVerificationRequest
-preparedProverRequest
- (PreparedProverTask _task _preparedTptp preparedRequest) =
- preparedRequest
-
-preparedProverTptpTask :: PreparedProverTask -> PreparedTptpTask
-preparedProverTptpTask
- (PreparedProverTask _task preparedTptp _preparedRequest) =
- preparedTptp
-
data PreparedTypedProverTask ref local origin global =
PreparedTypedProverTask
!(TypedProblem ref local origin global)
@@ -506,72 +591,6 @@ captureChunkSize :: Int
captureChunkSize =
32 * 1024
-runProver
- :: (MonadIO io, MonadLogger io)
- => Vampire
- -> Task
- -> io
- ( Location
- , Formula
- , Either ProverProcessError ProverAnswer
- )
-runProver vampireCommand task =
- runPreparedProver vampireCommand (prepareProverTask task)
-
-runPreparedProver
- :: (MonadIO io, MonadLogger io)
- => Vampire
- -> PreparedProverTask
- -> io
- ( Location
- , Formula
- , Either ProverProcessError ProverAnswer
- )
-runPreparedProver
- vampireCommand =
- runPreparedProverWithObserver
- (\_request -> pure ())
- vampireCommand
-
-runPreparedProverWithObserver
- :: (MonadIO io, MonadLogger io)
- => (PreparedVerificationRequest -> IO ())
- -> Vampire
- -> PreparedProverTask
- -> io
- ( Location
- , Formula
- , Either ProverProcessError ProverAnswer
- )
-runPreparedProverWithObserver
- observer
- vampireCommand
- (PreparedProverTask task preparedTask preparedRequest) = do
- startTime <- liftIO getCurrentTime
- transcriptResult <-
- liftIO
- (runPreparedVampireProcessWithObserver
- observer
- vampireCommand
- preparedRequest)
- let answer =
- classifyVampireAnswer
- task
- vampireCommand
- preparedRequest
- <$> transcriptResult
- endTime <- liftIO getCurrentTime
- let duration = timeDifferenceToText startTime endTime
-
- logInfoN
- ( duration
- <> " "
- <> preparedTptpConjectureText preparedTask
- <> taskProvenance task
- )
-
- pure (taskLocation task, taskConjecture task, answer)
-
runPreparedTypedProver
:: (MonadIO io, MonadLogger io)
=> Vampire
@@ -630,52 +649,279 @@ runPreparedTypedProverWithObserver
<> "]")
pure answer
-taskProvenance :: Task -> Text
-taskProvenance task =
- " [" <> directness <> " at "
- <> Text.pack (show (locationToText (taskLocation task)))
- <> "]"
- where
- directness = case taskDirectness task of
- Direct -> "direct"
- Indirect _ -> "indirect"
+-- | Invocation-local bounded owner of Vampire subprocesses. The finite queue
+-- carries only immutable prepared bytes plus runtime diagnostic position;
+-- checker builders and typed tasks never cross this boundary.
+data VampireExecutor = VampireExecutor
+ { executorQueue :: !(TBQueue ExecutorJob)
+ , executorClosed :: !(TVar Bool)
+ , executorCommand :: !Vampire
+ , executorRequestObserver
+ :: !(WorkPosition -> PreparedVerificationRequest -> IO ())
+ , executorRuntime :: !(TVar VampireExecutorRuntime)
+ }
-runVampireProcess
- :: Vampire
- -> Task
- -> IO (Either ProverProcessError CompletedTranscript)
-runVampireProcess
- vampireCommand
- task =
- let preparedTask = prepareTptpTask task
- in runPreparedVampireProcess
- vampireCommand
- (prepareVerificationRequest task preparedTask)
-
-prepareVerificationRequest
- :: Task
- -> PreparedTptpTask
- -> PreparedVerificationRequest
-prepareVerificationRequest task preparedTask =
- PreparedVerificationRequest
- { preparedVerificationDialect = VerificationFof
- , preparedVerificationMode = vampireTaskMode task
- , preparedVerificationInput =
- TextEncoding.encodeUtf8
- (preparedTptpTextNewline preparedTask)
- , preparedVerificationText =
- preparedTptpText preparedTask
+data ExecutorJob = ExecutorJob
+ { executorJobPosition :: !WorkPosition
+ , executorJobRequest :: !PreparedVerificationRequest
+ , executorJobCancelled :: !(TVar Bool)
+ , executorJobCompletion
+ :: !(TMVar
+ (Either
+ SomeException
+ (Either ProverProcessError ProverAnswer)))
+ }
+
+data VampireExecutorRuntime = VampireExecutorRuntime
+ { runtimeSubmittedCount :: !Int
+ , runtimeRunCount :: !Int
+ , runtimeLiveCount :: !Int
+ , runtimeMaximumLiveCount :: !Int
+ , runtimeFirstStartNanoseconds :: !(Maybe Word64)
+ , runtimeExecutionNanoseconds :: !Word64
+ }
+
+data VampireExecutorObservation = VampireExecutorObservation
+ { vampireExecutorSubmittedCount :: !Int
+ , vampireExecutorRunCount :: !Int
+ , vampireExecutorMaximumLiveCount :: !Int
+ , vampireExecutorFirstStartNanoseconds :: !(Maybe Word64)
+ , vampireExecutorExecutionNanoseconds :: !Word64
+ }
+ deriving (Show, Eq)
+
+data VampireExecutorClosed = VampireExecutorClosed
+ deriving (Show)
+
+instance Exception.Exception VampireExecutorClosed
+
+data RequestObserverFailure = RequestObserverFailure SomeException
+
+instance Show RequestObserverFailure where
+ show (RequestObserverFailure observerError) =
+ "request observer failed: " <> displayException observerError
+
+instance Exception.Exception RequestObserverFailure
+
+initialVampireExecutorRuntime :: VampireExecutorRuntime
+initialVampireExecutorRuntime =
+ VampireExecutorRuntime
+ { runtimeSubmittedCount = 0
+ , runtimeRunCount = 0
+ , runtimeLiveCount = 0
+ , runtimeMaximumLiveCount = 0
+ , runtimeFirstStartNanoseconds = Nothing
+ , runtimeExecutionNanoseconds = 0
}
-runPreparedVampireProcess
- :: Vampire
+-- | Bracket exactly the selected number of workers. Cancelling the bracket
+-- cancels each worker; a worker owning a subprocess in turn terminates and
+-- reaps that process group through the ordinary supervisor boundary.
+withVampireExecutor
+ :: EffectiveJobs
+ -> Vampire
+ -> (WorkPosition -> PreparedVerificationRequest -> IO ())
+ -> (VampireExecutor -> IO value)
+ -> IO value
+withVampireExecutor selected command observer action =
+ Exception.bracket acquire release (action . fst)
+ where
+ workerCount = effectiveJobsValue selected
+
+ acquire = do
+ queue <- newTBQueueIO
+ (fromIntegral workerCount * 2)
+ closed <- newTVarIO False
+ runtime <- newTVarIO initialVampireExecutorRuntime
+ let executor =
+ VampireExecutor
+ { executorQueue = queue
+ , executorClosed = closed
+ , executorCommand = command
+ , executorRequestObserver = observer
+ , executorRuntime = runtime
+ }
+ workers <- replicateM workerCount (async (executorWorker executor))
+ pure (executor, workers)
+
+ release (executor, workers) = do
+ atomically (writeTVar (executorClosed executor) True)
+ traverse_ cancel workers
+ traverse_ waitCatch workers
+
+runPreparedTypedProverWithExecutor
+ :: (MonadIO io, MonadLogger io)
+ => VampireExecutor
+ -> WorkPosition
+ -> PreparedTypedProverTask ref local origin global
+ -> io (Either ProverProcessError ProverAnswer)
+runPreparedTypedProverWithExecutor
+ executor
+ position
+ (PreparedTypedProverTask
+ _problem
+ prepared
+ preparedRequest) = do
+ startTime <- liftIO getCurrentTime
+ answer <- liftIO (submitVampireRequest executor position preparedRequest)
+ endTime <- liftIO getCurrentTime
+ let dialect =
+ case preparedTypedTptpRoute prepared of
+ RouteFof -> "FOF"
+ RouteTh0 -> "TH0"
+ logInfoN
+ (timeDifferenceToText startTime endTime
+ <> " "
+ <> preparedTypedTptpConjectureText prepared
+ <> " [typed "
+ <> dialect
+ <> "]")
+ pure answer
+
+vampireExecutorObservation
+ :: VampireExecutor
+ -> IO VampireExecutorObservation
+vampireExecutorObservation executor = do
+ runtime <- readTVarIO (executorRuntime executor)
+ pure
+ VampireExecutorObservation
+ { vampireExecutorSubmittedCount = runtimeSubmittedCount runtime
+ , vampireExecutorRunCount = runtimeRunCount runtime
+ , vampireExecutorMaximumLiveCount =
+ runtimeMaximumLiveCount runtime
+ , vampireExecutorFirstStartNanoseconds =
+ runtimeFirstStartNanoseconds runtime
+ , vampireExecutorExecutionNanoseconds =
+ runtimeExecutionNanoseconds runtime
+ }
+
+submitVampireRequest
+ :: VampireExecutor
+ -> WorkPosition
-> PreparedVerificationRequest
- -> IO (Either ProverProcessError CompletedTranscript)
-runPreparedVampireProcess
- vampireCommand =
- runPreparedVampireProcessWithObserver
- (\_request -> pure ())
- vampireCommand
+ -> IO (Either ProverProcessError ProverAnswer)
+submitVampireRequest executor position request =
+ Exception.mask \restore -> do
+ -- Force the compact queue payload before handing it to a worker.
+ _ <- Exception.evaluate (preparedVerificationByteCount request)
+ _ <- Exception.evaluate
+ (Text.length (preparedVerificationText request))
+ cancelled <- newTVarIO False
+ completion <- newEmptyTMVarIO
+ let job =
+ ExecutorJob
+ { executorJobPosition = position
+ , executorJobRequest = request
+ , executorJobCancelled = cancelled
+ , executorJobCompletion = completion
+ }
+ cancelJob = atomically (writeTVar cancelled True)
+ enqueue = atomically do
+ closed <- readTVar (executorClosed executor)
+ if closed
+ then pure False
+ else do
+ writeTBQueue (executorQueue executor) job
+ modifyTVar'
+ (executorRuntime executor)
+ (\runtime ->
+ runtime
+ { runtimeSubmittedCount =
+ runtimeSubmittedCount runtime + 1
+ })
+ pure True
+ accepted <- restore enqueue `Exception.onException` cancelJob
+ unless accepted (Exception.throwIO VampireExecutorClosed)
+ outcome <-
+ restore (atomically (takeTMVar completion))
+ `Exception.onException` cancelJob
+ either Exception.throwIO pure outcome
+
+executorWorker :: VampireExecutor -> IO ()
+executorWorker executor =
+ forever do
+ job <- atomically (readTBQueue (executorQueue executor))
+ cancelled <- readTVarIO (executorJobCancelled job)
+ unless cancelled (executeJob executor job)
+
+data JobWait value
+ = JobFinished !(Either SomeException value)
+ | JobCancelled
+
+executeJob :: VampireExecutor -> ExecutorJob -> IO ()
+executeJob executor job =
+ Exception.mask \restore -> do
+ running <- async
+ (restore
+ (observeExecutorRun executor
+ (runPreparedVerificationRequest
+ (executorRequestObserver executor
+ (executorJobPosition job))
+ (executorCommand executor)
+ (executorJobRequest job))))
+ waited <- restore
+ (atomically
+ ( (JobFinished <$> waitCatchSTM running)
+ `orElse`
+ (do
+ cancelled <- readTVar
+ (executorJobCancelled job)
+ check cancelled
+ pure JobCancelled)
+ ))
+ `Exception.onException`
+ (cancel running >> void (waitCatch running))
+ case waited of
+ JobFinished result ->
+ atomically
+ (putTMVar (executorJobCompletion job) result)
+ JobCancelled -> do
+ cancel running
+ void (waitCatch running)
+
+observeExecutorRun :: VampireExecutor -> IO value -> IO value
+observeExecutorRun executor action = do
+ started <- getMonotonicTimeNSec
+ atomically
+ (modifyTVar'
+ (executorRuntime executor)
+ (\runtime ->
+ let live = runtimeLiveCount runtime + 1
+ in runtime
+ { runtimeRunCount = runtimeRunCount runtime + 1
+ , runtimeLiveCount = live
+ , runtimeMaximumLiveCount =
+ max live (runtimeMaximumLiveCount runtime)
+ , runtimeFirstStartNanoseconds =
+ runtimeFirstStartNanoseconds runtime <|> Just started
+ }))
+ action `Exception.finally` do
+ finished <- getMonotonicTimeNSec
+ atomically
+ (modifyTVar'
+ (executorRuntime executor)
+ (\runtime ->
+ runtime
+ { runtimeLiveCount = runtimeLiveCount runtime - 1
+ , runtimeExecutionNanoseconds =
+ runtimeExecutionNanoseconds runtime
+ + (finished - started)
+ }))
+
+runPreparedVerificationRequest
+ :: (PreparedVerificationRequest -> IO ())
+ -> Vampire
+ -> PreparedVerificationRequest
+ -> IO (Either ProverProcessError ProverAnswer)
+runPreparedVerificationRequest observer command request = do
+ transcriptResult <-
+ runPreparedVampireProcessWithObserver observer command request
+ pure
+ (classifyPreparedVampireAnswer
+ "typed obligation"
+ command
+ request
+ <$> transcriptResult)
runPreparedVampireProcessWithObserver
:: (PreparedVerificationRequest -> IO ())
@@ -706,10 +952,12 @@ runPreparedVampireProcessWithObserver
pure (fromIntegral pid)
Nothing ->
impossible
- "runVampireProcess: missing process-group id"
+ "Vampire supervisor: missing process-group id"
restore
(do
- observer preparedRequest
+ runRequestObserver
+ observer
+ preparedRequest
superviseVampireProcess
executable
(vampireTimeLimit vampireCommand)
@@ -725,27 +973,50 @@ runPreparedVampireProcessWithObserver
processHandle
_ ->
impossible
- "runVampireProcess: expected CreatePipe handles")
+ "Vampire supervisor: expected CreatePipe handles")
:: IO
(Either
- IOException
+ SomeException
(Either ProverProcessError CompletedTranscript))
case processResult of
Right result ->
pure result
- Left err -> do
- started <- readIORef callbackStarted
- pure
- (Left
- (if started
- then
- ProverLifecycleFailed
- executable
- (exceptionText err)
- else
- ProverLaunchFailed
- executable
- (exceptionText err)))
+ Left err
+ | Just (RequestObserverFailure observerFailure) <-
+ fromException err ->
+ Exception.throwIO observerFailure
+ | Just asynchronous <-
+ (fromException err :: Maybe AsyncException) ->
+ Exception.throwIO asynchronous
+ | Just ioFailure <-
+ (fromException err :: Maybe IOException) -> do
+ started <- readIORef callbackStarted
+ pure
+ (Left
+ (if started
+ then
+ ProverLifecycleFailed
+ executable
+ (exceptionText ioFailure)
+ else
+ ProverLaunchFailed
+ executable
+ (exceptionText ioFailure)))
+ | otherwise ->
+ Exception.throwIO err
+
+runRequestObserver
+ :: (PreparedVerificationRequest -> IO ())
+ -> PreparedVerificationRequest
+ -> IO ()
+runRequestObserver observer request =
+ observer request `Exception.catch` \observerError ->
+ case fromException observerError :: Maybe AsyncException of
+ Just asynchronous ->
+ Exception.throwIO asynchronous
+ Nothing ->
+ Exception.throwIO
+ (RequestObserverFailure observerError)
superviseVampireProcess
:: FilePath
@@ -1033,25 +1304,6 @@ exceptionText :: IOException -> Text
exceptionText =
Text.pack . displayException
-classifyVampireAnswer
- :: Task
- -> Vampire
- -> PreparedVerificationRequest
- -> CompletedTranscript
- -> ProverAnswer
-classifyVampireAnswer
- task
- vampireCommand
- preparedRequest
- transcript =
- classifyPreparedVampireAnswer
- (Text.pack
- (show
- (taskConjectureLabel task)))
- vampireCommand
- preparedRequest
- transcript
-
classifyPreparedVampireAnswer
:: Text
-> Vampire
@@ -1152,12 +1404,6 @@ renderProtocolError protocolError CompleteCapturedStreams{..} =
, completedStderr
]
-vampireTaskMode :: Task -> VampireTaskMode
-vampireTaskMode task =
- if isIndirect task
- then IndirectTask
- else DirectTask
-
vampireStatusFromText :: Text -> VampireStatus
vampireStatusFromText = \case
"Theorem" -> StatusTheorem