summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--source/Api.hs98
-rw-r--r--source/Checking/Declaration.hs142
-rw-r--r--source/Provers.hs939
-rw-r--r--source/Test/Unit/Provers.hs326
4 files changed, 1193 insertions, 312 deletions
diff --git a/source/Api.hs b/source/Api.hs
index 2220434..25359da 100644
--- a/source/Api.hs
+++ b/source/Api.hs
@@ -420,12 +420,18 @@ data VerificationMeasurements = VerificationMeasurements
, verificationPreparedObligationCount :: !Int
, verificationMaximumObligationBatchSize :: !Int
, verificationPreparedRequestBytes :: !Word64
+ , verificationRequestPreparationNanoseconds :: !Word64
, verificationVampireRunCount :: !Int
, verificationModuleRootHitCount :: !Int
, verificationModuleRootMissCount :: !Int
, verificationFirstVampireStartNanoseconds
:: !(Maybe Word64)
+ , verificationFinalVampireSubmissionNanoseconds
+ :: !(Maybe Word64)
+ , verificationFinalVampireCompletionNanoseconds
+ :: !(Maybe Word64)
, verificationVampireExecutionNanoseconds :: !Word64
+ , verificationLongestVampireExecutionNanoseconds :: !Word64
}
deriving (Show, Eq)
@@ -442,11 +448,15 @@ data VerificationObservation = VerificationObservation
, observedPreparedObligationCount :: !Int
, observedMaximumObligationBatchSize :: !Int
, observedPreparedRequestBytes :: !Word64
+ , observedRequestPreparationNanoseconds :: !Word64
, observedVampireRunCount :: !Int
, observedModuleRootHitCount :: !Int
, observedModuleRootMissCount :: !Int
, observedFirstVampireStart :: !(Maybe Word64)
+ , observedFinalVampireSubmission :: !(Maybe Word64)
+ , observedFinalVampireCompletion :: !(Maybe Word64)
, observedVampireExecutionNanoseconds :: !Word64
+ , observedLongestVampireExecutionNanoseconds :: !Word64
}
initialVerificationObservation :: VerificationObservation
@@ -464,11 +474,15 @@ initialVerificationObservation =
, observedPreparedObligationCount = 0
, observedMaximumObligationBatchSize = 0
, observedPreparedRequestBytes = 0
+ , observedRequestPreparationNanoseconds = 0
, observedVampireRunCount = 0
, observedModuleRootHitCount = 0
, observedModuleRootMissCount = 0
, observedFirstVampireStart = Nothing
+ , observedFinalVampireSubmission = Nothing
+ , observedFinalVampireCompletion = Nothing
, observedVampireExecutionNanoseconds = 0
+ , observedLongestVampireExecutionNanoseconds = 0
}
newtype VerificationRequestObserver = VerificationRequestObserver
@@ -855,12 +869,6 @@ verifyMeasuredThrowingWithStore
. VerificationFoundationManifestError)
pure
Foundation.checkedFoundation
- preludeResolver <-
- typedVampireResolver
- runInIO
- executor
- 0
- observationRef
preparationStart <- getMonotonicTimeNSec
prepared <-
prepareDefaultSourceRequest file
@@ -869,7 +877,13 @@ verifyMeasuredThrowingWithStore
pure
let (mounts, request) = prepared
preparationEnd <- getMonotonicTimeNSec
- prelude <-
+ prelude <- withVampireRequestOwner executor \owner -> do
+ preludeResolver <-
+ typedVampireResolver
+ runInIO
+ owner
+ 0
+ observationRef
Typed.acquireFinalPreludeSession
memo store foundation preludeResolver
>>= either
@@ -1150,7 +1164,8 @@ checkTypedWorkspace
sealedByAddress
candidate
- checkModule task sealedByAddress = do
+ checkModule task sealedByAddress =
+ withVampireRequestOwner executor \requestOwner -> do
let parsed = moduleTaskParsed task
source = Felix.parsedModuleResolved parsed
address = Felix.parsedModuleAddress parsed
@@ -1167,7 +1182,7 @@ checkTypedWorkspace
resolver <-
typedVampireResolver
runInIO
- executor
+ requestOwner
(moduleTaskOrdinal task)
observationRef
input <-
@@ -1481,14 +1496,15 @@ typedVampireResolver
:: forall io
. (MonadIO io, MonadLogger io)
=> (forall value. io value -> IO value)
- -> VampireExecutor
+ -> VampireRequestOwner
-> Natural
-> IORef VerificationObservation
-> IO Declaration.VampireResolver
typedVampireResolver
- runInIO executor moduleOrdinal observationRef = do
+ runInIO requestOwner moduleOrdinal observationRef = do
localOrdinalRef <- newIORef 1
- pure (Declaration.vampireBatchResolver \prepared -> do
+ pure (Declaration.vampireBatchResolverWithPreparationObserver
+ (\prepared -> do
let batchSize = NonEmpty.length prepared
ordinalCount = fromIntegral batchSize
firstOrdinal <- atomicModifyIORef' localOrdinalRef
@@ -1531,11 +1547,20 @@ typedVampireResolver
(\(position, task) ->
runInIO
(runPreparedTypedProverWithExecutor
- executor
+ requestOwner
position
task))
(NonEmpty.toList positioned)
pure (NonEmpty.fromList results))
+ (\elapsed ->
+ atomicModifyIORef' observationRef \observation ->
+ ( observation
+ { observedRequestPreparationNanoseconds =
+ observedRequestPreparationNanoseconds observation
+ + elapsed
+ }
+ , ()
+ )))
observeModuleRootAcquisition
:: IORef VerificationObservation
@@ -1610,8 +1635,14 @@ mergeExecutorObservation observationRef executorObserved =
vampireExecutorMaximumLiveCount executorObserved
, observedFirstVampireStart =
vampireExecutorFirstStartNanoseconds executorObserved
+ , observedFinalVampireSubmission =
+ vampireExecutorFinalSubmissionNanoseconds executorObserved
+ , observedFinalVampireCompletion =
+ vampireExecutorFinalCompletionNanoseconds executorObserved
, observedVampireExecutionNanoseconds =
vampireExecutorExecutionNanoseconds executorObserved
+ , observedLongestVampireExecutionNanoseconds =
+ vampireExecutorLongestExecutionNanoseconds executorObserved
}
, ()
)
@@ -1662,6 +1693,8 @@ finalizeVerificationMeasurements
observedMaximumObligationBatchSize observation
, verificationPreparedRequestBytes =
observedPreparedRequestBytes observation
+ , verificationRequestPreparationNanoseconds =
+ observedRequestPreparationNanoseconds observation
, verificationVampireRunCount =
observedVampireRunCount observation
, verificationModuleRootHitCount =
@@ -1672,8 +1705,18 @@ finalizeVerificationMeasurements
fmap
(\started -> started - invocationStart)
(observedFirstVampireStart observation)
+ , verificationFinalVampireSubmissionNanoseconds =
+ fmap
+ (\submitted -> submitted - invocationStart)
+ (observedFinalVampireSubmission observation)
+ , verificationFinalVampireCompletionNanoseconds =
+ fmap
+ (\completed -> completed - invocationStart)
+ (observedFinalVampireCompletion observation)
, verificationVampireExecutionNanoseconds =
observedVampireExecutionNanoseconds observation
+ , verificationLongestVampireExecutionNanoseconds =
+ observedLongestVampireExecutionNanoseconds observation
}
renderVerificationMeasurements
@@ -1764,6 +1807,9 @@ renderVerificationMeasurements measurements =
measurements)
, "prepared_bytes=" <> renderIntegral
(verificationPreparedRequestBytes measurements)
+ , "request_preparation_ms="
+ <> renderNanoseconds
+ (verificationRequestPreparationNanoseconds measurements)
, "vampire_runs=" <> renderIntegral
(verificationVampireRunCount measurements)
, "module_root_hits=" <> renderIntegral
@@ -1780,6 +1826,27 @@ renderVerificationMeasurements measurements =
<> renderNanoseconds
(verificationVampireExecutionNanoseconds
measurements)
+ , "final_submission_ms="
+ <> maybe
+ "none"
+ renderNanoseconds
+ (verificationFinalVampireSubmissionNanoseconds
+ measurements)
+ , "final_completion_ms="
+ <> maybe
+ "none"
+ renderNanoseconds
+ (verificationFinalVampireCompletionNanoseconds
+ measurements)
+ , "vampire_span_ms="
+ <> maybe
+ "none"
+ renderNanoseconds
+ (vampireExecutionSpan measurements)
+ , "longest_vampire_ms="
+ <> renderNanoseconds
+ (verificationLongestVampireExecutionNanoseconds
+ measurements)
, "max_ready_modules=" <> renderIntegral
(verificationMaximumReadyModuleCount
measurements)
@@ -1792,6 +1859,11 @@ renderVerificationMeasurements measurements =
parseMeasurements =
verificationParseMeasurements measurements
+ vampireExecutionSpan measured = do
+ started <- verificationFirstVampireStartNanoseconds measured
+ completed <- verificationFinalVampireCompletionNanoseconds measured
+ pure (completed - started)
+
renderNanoseconds :: Word64 -> Text
renderNanoseconds nanoseconds =
renderIntegral (nanoseconds `div` 1000000)
diff --git a/source/Checking/Declaration.hs b/source/Checking/Declaration.hs
index 15c8282..3919630 100644
--- a/source/Checking/Declaration.hs
+++ b/source/Checking/Declaration.hs
@@ -38,6 +38,7 @@ module Checking.Declaration
, failModuleDriver
, VampireResolver
, vampireBatchResolver
+ , vampireBatchResolverWithPreparationObserver
, vampireResolver
, Declaration
, failDeclaration
@@ -436,7 +437,7 @@ forceCommittedBatch
(declarationValidationRecordCertificates record)
-newtype VampireResolver = VampireResolver
+data VampireResolver = VampireResolver
{ resolveVampireBatch
:: forall local origin.
NonEmpty
@@ -450,6 +451,7 @@ newtype VampireResolver = VampireResolver
(Either
Provers.ProverProcessError
Provers.ProverAnswer))
+ , observeVampirePreparationNanoseconds :: !(Word64 -> IO ())
}
vampireBatchResolver
@@ -466,7 +468,25 @@ vampireBatchResolver
Provers.ProverProcessError
Provers.ProverAnswer)))
-> VampireResolver
-vampireBatchResolver =
+vampireBatchResolver resolve =
+ vampireBatchResolverWithPreparationObserver resolve (const (pure ()))
+
+vampireBatchResolverWithPreparationObserver
+ :: (forall local origin.
+ NonEmpty
+ (Provers.PreparedTypedProverTask
+ SemanticFactOccurrenceFingerprint
+ local
+ origin
+ ObjectId)
+ -> IO
+ (NonEmpty
+ (Either
+ Provers.ProverProcessError
+ Provers.ProverAnswer)))
+ -> (Word64 -> IO ())
+ -> VampireResolver
+vampireBatchResolverWithPreparationObserver =
VampireResolver
vampireResolver
@@ -1985,27 +2005,30 @@ prepareScopedVampireObligationForMode
prepareScopedVampireObligationForMode
taskMode claimSupport claim scopedLocals auxiliaryTags selection =
ModuleDriver do
- DriverState _resolver builder _prefix _validation <- State.get
+ DriverState resolver builder _prefix _validation <- State.get
let closure = logicalBuilderObjectClosure builder
globalType = (`lookupCheckedObjectType` closure)
- pure do
- supportedClaim <-
- first VampireObligationClaimProjectionFailed
- (Backend.projectSupportedProposition
- globalType
- claimSupport
- claim)
- locals <- traverse
- (prepareLocal globalType)
- scopedLocals
- prepareVampireObligationWith
- taskMode
- builder
- closure
- supportedClaim
- locals
- auxiliaryTags
- selection
+ preparation = do
+ supportedClaim <-
+ first VampireObligationClaimProjectionFailed
+ (Backend.projectSupportedProposition
+ globalType
+ claimSupport
+ claim)
+ locals <- traverse
+ (prepareLocal globalType)
+ scopedLocals
+ prepareVampireObligationWith
+ taskMode
+ builder
+ closure
+ supportedClaim
+ locals
+ auxiliaryTags
+ selection
+ State.lift
+ (Except.liftIO
+ (measureVampirePreparation resolver preparation))
where
prepareLocal globalType
(ScopedVampirePremise
@@ -2098,6 +2121,32 @@ prepareVampireObligationWith
problem)
pure (PreparedVampireObligation problem task)
+measureVampirePreparation
+ :: VampireResolver
+ -> Either
+ (VampireObligationPreparationError local)
+ (PreparedVampireObligation local origin)
+ -> IO
+ (Either
+ (VampireObligationPreparationError local)
+ (PreparedVampireObligation local origin))
+measureVampirePreparation resolver preparation = do
+ started <- getMonotonicTimeNSec
+ prepared <- Exception.evaluate (forcePrepared preparation)
+ finished <- getMonotonicTimeNSec
+ observeVampirePreparationNanoseconds resolver (finished - started)
+ pure prepared
+ where
+ forcePrepared result =
+ case result of
+ Left err -> err `seq` result
+ Right prepared@(PreparedVampireObligation _ task) ->
+ let request = Provers.preparedTypedProverRequest task
+ in Provers.preparedVerificationByteCount request `seq`
+ Provers.preparedVerificationRequestId request `seq`
+ prepared `seq`
+ result
+
selectVampireFacts
:: (ObjectId -> Maybe CoreType)
-> LogicalBuilder
@@ -2840,35 +2889,39 @@ prepareCurrentCandidateVampire = CandidateProof do
closed proposition =
embedClosedCore []
(checkedPropositionTerm proposition)
- supportedTarget <-
- State.lift
- (Except.liftEither
- (first
- (CurrentCandidateVampirePreparationFailed
- . VampireObligationClaimProjectionFailed)
+ resolver =
+ declarationVampireResolver
+ (candidateProofDeclaration initial)
+ preparation = do
+ supportedTarget <-
+ first
+ VampireObligationClaimProjectionFailed
(Backend.projectSupportedProposition
globalType
(Vector.empty :: Vector (Void, CoreType))
- (closed target))))
- locals <-
+ (closed target))
+ locals <-
+ traverse
+ (prepareLocal globalType)
+ (zip [0 :: Natural ..] premises)
+ prepareVampireObligationWith
+ Provers.DirectTask
+ builder
+ closure
+ supportedTarget
+ locals
+ []
+ VampireLocalPremises
+ preparedResult <-
State.lift
- (Except.liftEither
- (first CurrentCandidateVampirePreparationFailed
- (traverse
- (prepareLocal globalType)
- (zip [0 :: Natural ..] premises))))
+ (liftIO
+ (measureVampirePreparation resolver preparation))
prepared <-
State.lift
(Except.liftEither
- (first CurrentCandidateVampirePreparationFailed
- (prepareVampireObligationWith
- Provers.DirectTask
- builder
- closure
- supportedTarget
- locals
- []
- VampireLocalPremises)))
+ (first
+ CurrentCandidateVampirePreparationFailed
+ preparedResult))
pure prepared
where
prepareLocal globalType (index, CandidatePremise proposition) = do
@@ -3223,7 +3276,8 @@ validateAcceptedVampireResult prepared result = do
traverse_
(\accepted ->
unless
- (Provers.acceptedVampireRequest accepted == request)
+ (Provers.acceptedVampireRequestId accepted
+ == Provers.preparedVerificationRequestId request)
(Except.throwError VampireRequestMismatch))
(either (const Nothing) Provers.provedVampireRun resolved))
result
diff --git a/source/Provers.hs b/source/Provers.hs
index 941360c..56497f6 100644
--- a/source/Provers.hs
+++ b/source/Provers.hs
@@ -8,6 +8,7 @@ module Provers
, VampireTaskMode(..)
, VampireStatus(..)
, CanonicalAtpOutcome(..)
+ , CanonicalAtpRejection(..)
, VampireProtocolError(..)
, classifyVampireProtocol
, vampireStatusParser
@@ -35,10 +36,9 @@ module Provers
, PreparedTypedProverTask
, prepareTypedProverTask
, preparedTypedProverLogicalProblem
- , preparedTypedProverTptpProblem
, preparedTypedProverRequest
, AcceptedVampireRun
- , acceptedVampireRequest
+ , acceptedVampireRequestId
, provedVampireRun
, runPreparedTypedProver
, runPreparedTypedProverWithObserver
@@ -52,8 +52,18 @@ module Provers
, workPositionModuleOrdinal
, workPositionLocalRequestOrdinal
, VampireExecutor
+ , VampireExecutorFault
, VampireExecutorObservation(..)
+ , VampireRequestOwner
+ , VampireHandle
+ , VampireTerminal(..)
+ , renderVampireTerminalDiagnostic
+ , VampireCompletion(..)
, withVampireExecutor
+ , withVampireRequestOwner
+ , submitVampireRequest
+ , awaitVampireRequest
+ , cancelVampireRequest
, runPreparedTypedProverWithExecutor
, vampireExecutorObservation
) where
@@ -64,7 +74,8 @@ import Checking.Backend.Problem
import Checking.Backend.Tptp
import Control.Concurrent.STM
- ( TBQueue
+ ( STM
+ , TBQueue
, TMVar
, TVar
, atomically
@@ -74,11 +85,13 @@ import Control.Concurrent.STM
, newTBQueueIO
, newTVarIO
, orElse
- , putTMVar
+ , readTMVar
, readTBQueue
, readTVar
, readTVarIO
- , takeTMVar
+ , throwSTM
+ , tryPutTMVar
+ , tryReadTMVar
, writeTBQueue
, writeTVar
)
@@ -90,9 +103,10 @@ import Control.Exception
, fromException
)
import Control.Exception qualified as Exception
-import Control.Monad (forever, replicateM, unless)
+import Control.Monad (replicateM, unless, when)
import Control.Monad.Logger
import Data.ByteString qualified as ByteString
+import Data.IntMap.Strict qualified as IntMap
import Data.IORef
( IORef
, atomicModifyIORef'
@@ -103,6 +117,7 @@ import Data.IORef
import Data.Set qualified as Set
import Data.Text qualified as Text
import Data.Text.Encoding qualified as TextEncoding
+import Data.Text.Encoding.Error qualified as TextEncodingError
import Data.Time
import Numeric.Natural (Natural)
import System.Exit (ExitCode(..))
@@ -269,6 +284,15 @@ data CanonicalAtpOutcome
| Indeterminate
deriving (Show, Eq, Ord)
+-- | The closed non-accepting subset of canonical ATP outcomes. Protocol
+-- disagreement is represented separately from a semantic non-proof, while an
+-- accepting result is unrepresentable here.
+data CanonicalAtpRejection
+ = RejectedCounterexample
+ | RejectedContradictoryInput
+ | RejectedIndeterminate
+ deriving (Show, Eq)
+
data VampireProtocolError
= UnsuccessfulVampireExit ExitCode
| UnsupportedVampireStatuses (Set Text)
@@ -305,6 +329,37 @@ instance Show PartialCapturedStreams where
)
)
+data BoundedBytes = BoundedBytes
+ { boundedBytesOriginalCount :: !Int
+ , boundedBytesRetained :: !ByteString.ByteString
+ , boundedBytesTruncated :: !Bool
+ }
+ deriving (Eq)
+
+instance Show BoundedBytes where
+ showsPrec precedence BoundedBytes{..} =
+ showParen (precedence > applicationPrecedence)
+ ( showString "BoundedBytes "
+ . shows
+ ( boundedBytesOriginalCount
+ , ByteString.length boundedBytesRetained
+ , boundedBytesTruncated
+ )
+ )
+
+data BoundedCapturedStreams = BoundedCapturedStreams
+ { boundedStdout :: !BoundedBytes
+ , boundedStderr :: !BoundedBytes
+ }
+ deriving (Show, Eq)
+
+data BoundedDiagnostic = BoundedDiagnostic
+ { boundedDiagnosticSummary :: !Text
+ , boundedDiagnosticStdout :: !BoundedBytes
+ , boundedDiagnosticStderr :: !BoundedBytes
+ }
+ deriving (Show, Eq)
+
-- | Classify parsed Vampire statuses without depending on stream or line order.
classifyVampireProtocol
:: VampireTaskMode
@@ -381,10 +436,21 @@ data PreparedVerificationRequest = PreparedVerificationRequest
{ preparedVerificationDialect :: !VerificationDialect
, preparedVerificationMode :: !VampireTaskMode
, preparedVerificationInput :: !ByteString.ByteString
- , preparedVerificationText :: !Text
+ , preparedVerificationIdentity :: !Authority.PreparedRequestId
}
deriving (Eq)
+-- | Decode the one canonical serialized request only at a textual boundary.
+-- Prepared requests deliberately do not retain an equivalent 'Text' copy.
+preparedVerificationText :: PreparedVerificationRequest -> Text
+preparedVerificationText request =
+ let encoded = preparedVerificationInput request
+ withoutFinalNewline =
+ case ByteString.unsnoc encoded of
+ Just (body, 10) -> body
+ _ -> encoded
+ in TextEncoding.decodeUtf8 withoutFinalNewline
+
preparedVerificationByteCount
:: PreparedVerificationRequest
-> Int
@@ -400,21 +466,14 @@ preparedVerificationBytes =
preparedVerificationRequestId
:: PreparedVerificationRequest
-> Authority.PreparedRequestId
-preparedVerificationRequestId request =
- Authority.preparedRequestId
- (case preparedVerificationDialect request of
- VerificationFof -> Authority.PreparedRequestFof
- VerificationTh0 -> Authority.PreparedRequestTh0)
- (case preparedVerificationMode request of
- DirectTask -> Authority.PreparedRequestDirect
- IndirectTask -> Authority.PreparedRequestIndirect)
- (preparedVerificationBytes request)
+preparedVerificationRequestId =
+ preparedVerificationIdentity
data PreparedTypedProverTask ref local origin global =
PreparedTypedProverTask
!(TypedProblem ref local origin global)
- !(PreparedTypedTptpProblem ref local global)
!PreparedVerificationRequest
+ !Text
prepareTypedProverTask
:: (Ord local, Ord global)
@@ -426,27 +485,34 @@ prepareTypedProverTask
prepareTypedProverTask mode problem = do
prepared <-
prepareTypedTptpProblem problem
- let preparedRequest =
+ let dialect =
+ case preparedTypedTptpRoute prepared of
+ RouteFof -> VerificationFof
+ RouteTh0 -> VerificationTh0
+ bytes =
+ TextEncoding.encodeUtf8
+ (preparedTypedTptpTextNewline prepared)
+ identity =
+ Authority.preparedRequestId
+ (case dialect of
+ VerificationFof -> Authority.PreparedRequestFof
+ VerificationTh0 -> Authority.PreparedRequestTh0)
+ (case mode of
+ DirectTask -> Authority.PreparedRequestDirect
+ IndirectTask -> Authority.PreparedRequestIndirect)
+ bytes
+ preparedRequest =
PreparedVerificationRequest
- { preparedVerificationDialect =
- case preparedTypedTptpRoute prepared of
- RouteFof ->
- VerificationFof
- RouteTh0 ->
- VerificationTh0
+ { preparedVerificationDialect = dialect
, preparedVerificationMode = mode
- , preparedVerificationInput =
- TextEncoding.encodeUtf8
- (preparedTypedTptpTextNewline
- prepared)
- , preparedVerificationText =
- preparedTypedTptpText prepared
+ , preparedVerificationInput = bytes
+ , preparedVerificationIdentity = identity
}
pure
(PreparedTypedProverTask
problem
- prepared
- preparedRequest)
+ preparedRequest
+ (preparedTypedTptpConjectureText prepared))
preparedTypedProverLogicalProblem
:: PreparedTypedProverTask ref local origin global
@@ -454,35 +520,22 @@ preparedTypedProverLogicalProblem
preparedTypedProverLogicalProblem
(PreparedTypedProverTask
problem
- _prepared
- _request) =
+ _request
+ _conjecture) =
problem
-preparedTypedProverTptpProblem
- :: PreparedTypedProverTask ref local origin global
- -> PreparedTypedTptpProblem ref local global
-preparedTypedProverTptpProblem
- (PreparedTypedProverTask
- _problem
- prepared
- _request) =
- prepared
-
preparedTypedProverRequest
:: PreparedTypedProverTask ref local origin global
-> PreparedVerificationRequest
preparedTypedProverRequest
(PreparedTypedProverTask
_problem
- _prepared
- request) =
+ request
+ _conjecture) =
request
data AcceptedVampireRun = AcceptedVampireRun
- { acceptedVampireRequest :: !PreparedVerificationRequest
- , acceptedVampireCommand :: !Vampire
- , acceptedVampireTranscript :: !CompletedTranscript
- }
+ { acceptedVampireRequestId :: !Authority.PreparedRequestId }
deriving (Eq)
data ProverAnswer
@@ -554,15 +607,15 @@ data ProverProcessError
| ProverTimedOut
!FilePath
!TimeLimit
- !PartialCapturedStreams
+ !BoundedCapturedStreams
| ProverOutputLimitExceeded
!FilePath
!ProverOutputStream
- !PartialCapturedStreams
+ !BoundedCapturedStreams
| ProverTerminatedBySignal
!FilePath
!Int
- !PartialCapturedStreams
+ !BoundedCapturedStreams
| ProverLifecycleFailed !FilePath !Text
deriving (Show, Eq)
@@ -613,37 +666,33 @@ runPreparedTypedProverWithObserver
vampireCommand
(PreparedTypedProverTask
_problem
- prepared
- preparedRequest) = do
+ preparedRequest
+ conjecture) = do
startTime <- liftIO getCurrentTime
- transcriptResult <-
- liftIO
- (runPreparedVampireProcessWithObserver
- observer
- vampireCommand
- preparedRequest)
- let answer =
- classifyPreparedVampireAnswer
- "typed obligation"
- vampireCommand
- preparedRequest
- <$> transcriptResult
+ terminal <- liftIO
+ (runPreparedVerificationTerminal
+ observer
+ vampireCommand
+ preparedRequest)
+ answer <- liftIO
+ (completionProverResult
+ preparedRequest
+ (VampireCompletion terminal))
endTime <- liftIO getCurrentTime
let duration =
timeDifferenceToText
startTime
endTime
dialect =
- case preparedTypedTptpRoute prepared of
- RouteFof ->
+ case preparedVerificationDialect preparedRequest of
+ VerificationFof ->
"FOF"
- RouteTh0 ->
+ VerificationTh0 ->
"TH0"
logInfoN
(duration
<> " "
- <> preparedTypedTptpConjectureText
- prepared
+ <> conjecture
<> " [typed "
<> dialect
<> "]")
@@ -654,7 +703,8 @@ runPreparedTypedProverWithObserver
-- checker builders and typed tasks never cross this boundary.
data VampireExecutor = VampireExecutor
{ executorQueue :: !(TBQueue ExecutorJob)
- , executorClosed :: !(TVar Bool)
+ , executorState :: !(TVar VampireExecutorState)
+ , executorNextHandle :: !(TVar Int)
, executorCommand :: !Vampire
, executorRequestObserver
:: !(WorkPosition -> PreparedVerificationRequest -> IO ())
@@ -665,20 +715,75 @@ data ExecutorJob = ExecutorJob
{ executorJobPosition :: !WorkPosition
, executorJobRequest :: !PreparedVerificationRequest
, executorJobCancelled :: !(TVar Bool)
+ , executorJobStarted :: !(TVar Bool)
, executorJobCompletion
- :: !(TMVar
- (Either
- SomeException
- (Either ProverProcessError ProverAnswer)))
+ :: !(TMVar VampireCompletion)
}
+data VampireExecutorState
+ = ExecutorRunning
+ | ExecutorClosing
+ | ExecutorFaulted !VampireExecutorFault
+
+-- | A global executor fault is distinct from one request's declared process
+-- or protocol outcome. Its bounded message carries no process transcript.
+newtype VampireExecutorFault = VampireExecutorFault Text
+ deriving (Show)
+
+instance Exception.Exception VampireExecutorFault
+
+data VampireRequestOwner = VampireRequestOwner
+ { requestOwnerExecutor :: !VampireExecutor
+ , requestOwnerState :: !(TVar VampireRequestOwnerState)
+ }
+
+data VampireRequestOwnerState
+ = RequestOwnerOpen !(IntMap VampireHandle)
+ | RequestOwnerClosed
+
+-- | An opaque, owner-scoped completion handle. It deliberately retains no
+-- prepared request, command, transcript, builder, or parser state.
+data VampireHandle = VampireHandle
+ { vampireHandleOrdinal :: !Int
+ , vampireHandleOwner :: !VampireRequestOwner
+ , vampireHandleCancelled :: !(TVar Bool)
+ , vampireHandleStarted :: !(TVar Bool)
+ , vampireHandleCompletion :: !(TMVar VampireCompletion)
+ }
+
+data VampireTerminal
+ = VampireAccepted !Authority.PreparedRequestId
+ | VampireRejected !CanonicalAtpRejection !BoundedDiagnostic
+ | VampireProtocolFailed !BoundedDiagnostic
+ | VampireProcessFailed !ProverProcessError
+ | VampireCancelled
+ deriving (Show, Eq)
+
+newtype VampireCompletion = VampireCompletion
+ { vampireCompletionTerminal :: VampireTerminal }
+ deriving (Show, Eq)
+
+renderVampireTerminalDiagnostic :: VampireTerminal -> Maybe Text
+renderVampireTerminalDiagnostic = \case
+ VampireAccepted{} -> Nothing
+ VampireRejected _rejection diagnostic ->
+ Just (renderBoundedDiagnostic diagnostic)
+ VampireProtocolFailed diagnostic ->
+ Just (renderBoundedDiagnostic diagnostic)
+ VampireProcessFailed processFailure ->
+ Just (Text.pack (show processFailure))
+ VampireCancelled -> Nothing
+
data VampireExecutorRuntime = VampireExecutorRuntime
{ runtimeSubmittedCount :: !Int
, runtimeRunCount :: !Int
, runtimeLiveCount :: !Int
, runtimeMaximumLiveCount :: !Int
, runtimeFirstStartNanoseconds :: !(Maybe Word64)
+ , runtimeFinalSubmissionNanoseconds :: !(Maybe Word64)
+ , runtimeFinalCompletionNanoseconds :: !(Maybe Word64)
, runtimeExecutionNanoseconds :: !Word64
+ , runtimeLongestExecutionNanoseconds :: !Word64
}
data VampireExecutorObservation = VampireExecutorObservation
@@ -686,7 +791,10 @@ data VampireExecutorObservation = VampireExecutorObservation
, vampireExecutorRunCount :: !Int
, vampireExecutorMaximumLiveCount :: !Int
, vampireExecutorFirstStartNanoseconds :: !(Maybe Word64)
+ , vampireExecutorFinalSubmissionNanoseconds :: !(Maybe Word64)
+ , vampireExecutorFinalCompletionNanoseconds :: !(Maybe Word64)
, vampireExecutorExecutionNanoseconds :: !Word64
+ , vampireExecutorLongestExecutionNanoseconds :: !Word64
}
deriving (Show, Eq)
@@ -695,6 +803,11 @@ data VampireExecutorClosed = VampireExecutorClosed
instance Exception.Exception VampireExecutorClosed
+data VampireRequestOwnerClosed = VampireRequestOwnerClosed
+ deriving (Show)
+
+instance Exception.Exception VampireRequestOwnerClosed
+
data RequestObserverFailure = RequestObserverFailure SomeException
instance Show RequestObserverFailure where
@@ -711,7 +824,10 @@ initialVampireExecutorRuntime =
, runtimeLiveCount = 0
, runtimeMaximumLiveCount = 0
, runtimeFirstStartNanoseconds = Nothing
+ , runtimeFinalSubmissionNanoseconds = Nothing
+ , runtimeFinalCompletionNanoseconds = Nothing
, runtimeExecutionNanoseconds = 0
+ , runtimeLongestExecutionNanoseconds = 0
}
-- | Bracket exactly the selected number of workers. Cancelling the bracket
@@ -731,48 +847,91 @@ withVampireExecutor selected command observer action =
acquire = do
queue <- newTBQueueIO
(fromIntegral workerCount * 2)
- closed <- newTVarIO False
+ state <- newTVarIO ExecutorRunning
+ nextHandle <- newTVarIO 0
runtime <- newTVarIO initialVampireExecutorRuntime
let executor =
VampireExecutor
{ executorQueue = queue
- , executorClosed = closed
+ , executorState = state
+ , executorNextHandle = nextHandle
, executorCommand = command
, executorRequestObserver = observer
, executorRuntime = runtime
}
- workers <- replicateM workerCount (async (executorWorker executor))
+ workers <- replicateM workerCount
+ (async (superviseExecutorWorker executor))
pure (executor, workers)
release (executor, workers) = do
- atomically (writeTVar (executorClosed executor) True)
+ atomically do
+ state <- readTVar (executorState executor)
+ case state of
+ ExecutorRunning ->
+ writeTVar (executorState executor) ExecutorClosing
+ ExecutorClosing ->
+ pure ()
+ ExecutorFaulted{} ->
+ pure ()
traverse_ cancel workers
traverse_ waitCatch workers
+-- | Bracket the complete set of handles submitted by one module pipeline.
+-- Normal early return, failure, and asynchronous cancellation all cancel and
+-- reap the remaining owned work before the scope is left.
+withVampireRequestOwner
+ :: VampireExecutor
+ -> (VampireRequestOwner -> IO value)
+ -> IO value
+withVampireRequestOwner executor =
+ Exception.bracket acquire release
+ where
+ acquire =
+ VampireRequestOwner executor
+ <$> newTVarIO (RequestOwnerOpen IntMap.empty)
+
+ release owner = Exception.mask_ do
+ handles <- atomically do
+ state <- readTVar (requestOwnerState owner)
+ case state of
+ RequestOwnerClosed ->
+ pure []
+ RequestOwnerOpen owned -> do
+ writeTVar (requestOwnerState owner) RequestOwnerClosed
+ pure (IntMap.elems owned)
+ traverse_ signalVampireCancellation handles
+ traverse_ awaitVampireCancellation handles
+ traverse_ deregisterVampireHandle handles
+
runPreparedTypedProverWithExecutor
:: (MonadIO io, MonadLogger io)
- => VampireExecutor
+ => VampireRequestOwner
-> WorkPosition
-> PreparedTypedProverTask ref local origin global
-> io (Either ProverProcessError ProverAnswer)
runPreparedTypedProverWithExecutor
- executor
+ owner
position
(PreparedTypedProverTask
_problem
- prepared
- preparedRequest) = do
+ preparedRequest
+ conjecture) = do
startTime <- liftIO getCurrentTime
- answer <- liftIO (submitVampireRequest executor position preparedRequest)
+ answer <- liftIO do
+ handle <- submitVampireRequest owner position preparedRequest
+ completion <-
+ awaitVampireRequest handle
+ `Exception.onException` cancelVampireRequest handle
+ completionProverResult preparedRequest completion
endTime <- liftIO getCurrentTime
let dialect =
- case preparedTypedTptpRoute prepared of
- RouteFof -> "FOF"
- RouteTh0 -> "TH0"
+ case preparedVerificationDialect preparedRequest of
+ VerificationFof -> "FOF"
+ VerificationTh0 -> "TH0"
logInfoN
(timeDifferenceToText startTime endTime
<> " "
- <> preparedTypedTptpConjectureText prepared
+ <> conjecture
<> " [typed "
<> dialect
<> "]")
@@ -791,96 +950,254 @@ vampireExecutorObservation executor = do
runtimeMaximumLiveCount runtime
, vampireExecutorFirstStartNanoseconds =
runtimeFirstStartNanoseconds runtime
+ , vampireExecutorFinalSubmissionNanoseconds =
+ runtimeFinalSubmissionNanoseconds runtime
+ , vampireExecutorFinalCompletionNanoseconds =
+ runtimeFinalCompletionNanoseconds runtime
, vampireExecutorExecutionNanoseconds =
runtimeExecutionNanoseconds runtime
+ , vampireExecutorLongestExecutionNanoseconds =
+ runtimeLongestExecutionNanoseconds runtime
}
submitVampireRequest
- :: VampireExecutor
+ :: VampireRequestOwner
-> WorkPosition
-> PreparedVerificationRequest
- -> 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))
+ -> IO VampireHandle
+submitVampireRequest owner position request = do
+ -- Force the compact queue payload before masked ownership acquisition.
+ _ <- Exception.evaluate (preparedVerificationByteCount request)
+ _ <- Exception.evaluate (preparedVerificationRequestId request)
+ Exception.mask_ do
cancelled <- newTVarIO False
+ started <- 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
+ let executor = requestOwnerExecutor owner
+ handle <- atomically do
+ executorStatus <- readTVar (executorState executor)
+ case executorStatus of
+ ExecutorClosing -> throwSTM VampireExecutorClosed
+ ExecutorFaulted fault -> throwSTM fault
+ ExecutorRunning -> pure ()
+ ownerStatus <- readTVar (requestOwnerState owner)
+ owned <- case ownerStatus of
+ RequestOwnerClosed -> throwSTM VampireRequestOwnerClosed
+ RequestOwnerOpen current -> pure current
+ ordinal <- readTVar (executorNextHandle executor)
+ writeTVar (executorNextHandle executor) (ordinal + 1)
+ let acquired =
+ VampireHandle
+ { vampireHandleOrdinal = ordinal
+ , vampireHandleOwner = owner
+ , vampireHandleCancelled = cancelled
+ , vampireHandleStarted = started
+ , vampireHandleCompletion = completion
+ }
+ job =
+ ExecutorJob
+ { executorJobPosition = position
+ , executorJobRequest = request
+ , executorJobCancelled = cancelled
+ , executorJobStarted = started
+ , executorJobCompletion = completion
+ }
+ writeTBQueue (executorQueue executor) job
+ writeTVar
+ (requestOwnerState owner)
+ (RequestOwnerOpen (IntMap.insert ordinal acquired owned))
+ modifyTVar'
+ (executorRuntime executor)
+ (\runtime ->
+ runtime
+ { runtimeSubmittedCount =
+ runtimeSubmittedCount runtime + 1
+ })
+ pure acquired
+ submitted <- getMonotonicTimeNSec
+ atomically
+ (modifyTVar'
+ (executorRuntime executor)
+ (\runtime ->
+ runtime
+ { runtimeFinalSubmissionNanoseconds =
+ Just
+ (maybe submitted
+ (max submitted)
+ (runtimeFinalSubmissionNanoseconds
+ runtime))
+ }))
+ pure handle
+
+awaitVampireRequest :: VampireHandle -> IO VampireCompletion
+awaitVampireRequest handle = Exception.mask \restore -> do
+ let executor = requestOwnerExecutor (vampireHandleOwner handle)
+ completion <- restore (atomically do
+ executorStatus <- readTVar (executorState executor)
+ case executorStatus of
+ ExecutorFaulted fault -> throwSTM fault
+ ExecutorRunning -> readTMVar (vampireHandleCompletion handle)
+ ExecutorClosing ->
+ (readTMVar (vampireHandleCompletion handle))
+ `orElse` throwSTM VampireExecutorClosed)
+ deregisterVampireHandle handle
+ pure completion
+
+cancelVampireRequest :: VampireHandle -> IO ()
+cancelVampireRequest handle = Exception.mask_ do
+ signalVampireCancellation handle
+ awaitVampireCancellation handle
+ deregisterVampireHandle handle
+
+signalVampireCancellation :: VampireHandle -> IO ()
+signalVampireCancellation handle = do
+ let executor = requestOwnerExecutor (vampireHandleOwner handle)
+ completion = VampireCompletion VampireCancelled
+ _ <- Exception.evaluate completion
+ published <- getMonotonicTimeNSec
+ atomically do
+ writeTVar (vampireHandleCancelled handle) True
+ started <- readTVar (vampireHandleStarted handle)
+ unless started
+ (void
+ (publishVampireTerminalSTM
+ executor
+ (vampireHandleCompletion handle)
+ published
+ completion))
+
+awaitVampireCancellation :: VampireHandle -> IO ()
+awaitVampireCancellation handle =
+ -- A running worker acknowledges ordinary cancellation only after
+ -- terminating and reaping its owned process group. After a global fault,
+ -- the worker lifecycle flag is the acknowledgement because the terminal
+ -- cell deliberately remains subordinate to that fault.
+ atomically do
+ executorStatus <- readTVar
+ (executorState (requestOwnerExecutor (vampireHandleOwner handle)))
+ case executorStatus of
+ ExecutorFaulted{} -> do
+ stillRunning <- readTVar (vampireHandleStarted handle)
+ check (not stillRunning)
+ _ -> void (readTMVar (vampireHandleCompletion handle))
+
+deregisterVampireHandle :: VampireHandle -> IO ()
+deregisterVampireHandle handle = atomically do
+ let owner = vampireHandleOwner handle
+ state <- readTVar (requestOwnerState owner)
+ case state of
+ RequestOwnerClosed -> pure ()
+ RequestOwnerOpen owned ->
+ writeTVar
+ (requestOwnerState owner)
+ (RequestOwnerOpen
+ (IntMap.delete (vampireHandleOrdinal handle) owned))
+
+superviseExecutorWorker :: VampireExecutor -> IO ()
+superviseExecutorWorker executor = do
+ result <- Exception.try (executorWorker executor)
+ case result of
+ Right () -> pure ()
+ Left workerFailure -> do
+ state <- readTVarIO (executorState executor)
+ case state of
+ ExecutorRunning ->
+ recordExecutorFault executor workerFailure
+ ExecutorClosing ->
+ pure ()
+ ExecutorFaulted{} ->
+ pure ()
+
+recordExecutorFault :: VampireExecutor -> SomeException -> IO ()
+recordExecutorFault executor workerFailure = atomically do
+ state <- readTVar (executorState executor)
+ case state of
+ ExecutorRunning ->
+ writeTVar
+ (executorState executor)
+ (ExecutorFaulted
+ (VampireExecutorFault
+ (boundedText
+ (Text.pack (displayException workerFailure)))))
+ ExecutorClosing -> pure ()
+ ExecutorFaulted{} -> pure ()
executorWorker :: VampireExecutor -> IO ()
-executorWorker executor =
- forever do
- job <- atomically (readTBQueue (executorQueue executor))
- cancelled <- readTVarIO (executorJobCancelled job)
- unless cancelled (executeJob executor job)
+executorWorker executor = do
+ next <- atomically do
+ state <- readTVar (executorState executor)
+ case state of
+ ExecutorRunning -> Just <$> readTBQueue (executorQueue executor)
+ ExecutorClosing -> pure Nothing
+ ExecutorFaulted{} -> pure Nothing
+ case next of
+ Nothing -> pure ()
+ Just job -> do
+ shouldRun <- atomically do
+ completed <- tryReadTMVar (executorJobCompletion job)
+ case completed of
+ Just _ -> pure False
+ Nothing -> do
+ writeTVar (executorJobStarted job) True
+ pure True
+ when shouldRun
+ (executeJob executor job
+ `Exception.finally`
+ atomically (writeTVar (executorJobStarted job) False))
+ executorWorker executor
data JobWait value
= JobFinished !(Either SomeException value)
| JobCancelled
+ | JobExecutorStopped
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))))
+ (timedExecutorRun executor job))
waited <- restore
(atomically
- ( (JobFinished <$> waitCatchSTM running)
- `orElse`
- (do
- cancelled <- readTVar
- (executorJobCancelled job)
- check cancelled
- pure JobCancelled)
- ))
+ (do
+ state <- readTVar (executorState executor)
+ case state of
+ ExecutorRunning ->
+ (JobFinished <$> waitCatchSTM running)
+ `orElse`
+ (do
+ cancelled <- readTVar
+ (executorJobCancelled job)
+ check cancelled
+ pure JobCancelled)
+ ExecutorClosing -> pure JobExecutorStopped
+ ExecutorFaulted{} -> pure JobExecutorStopped))
`Exception.onException`
(cancel running >> void (waitCatch running))
case waited of
- JobFinished result ->
- atomically
- (putTMVar (executorJobCompletion job) result)
+ JobFinished result -> case result of
+ Left workerFailure -> Exception.throwIO workerFailure
+ Right terminal ->
+ publishVampireTerminal
+ executor
+ (executorJobCompletion job)
+ terminal
JobCancelled -> do
cancel running
void (waitCatch running)
+ publishVampireTerminal
+ executor
+ (executorJobCompletion job)
+ VampireCancelled
+ JobExecutorStopped -> do
+ cancel running
+ void (waitCatch running)
-observeExecutorRun :: VampireExecutor -> IO value -> IO value
-observeExecutorRun executor action = do
+timedExecutorRun
+ :: VampireExecutor
+ -> ExecutorJob
+ -> IO VampireTerminal
+timedExecutorRun executor job = Exception.mask \restore -> do
started <- getMonotonicTimeNSec
atomically
(modifyTVar'
@@ -895,33 +1212,147 @@ observeExecutorRun executor action = do
, 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)
- }))
+ terminal <- restore
+ (runPreparedVerificationTerminal
+ (executorRequestObserver executor (executorJobPosition job))
+ (executorCommand executor)
+ (executorJobRequest job))
+ `Exception.onException` finishExecutorRun executor started
+ finishExecutorRun executor started
+ pure terminal
+
+finishExecutorRun :: VampireExecutor -> Word64 -> IO ()
+finishExecutorRun executor started = do
+ finished <- getMonotonicTimeNSec
+ let elapsed = finished - started
+ atomically
+ (modifyTVar'
+ (executorRuntime executor)
+ (\runtime ->
+ runtime
+ { runtimeLiveCount = runtimeLiveCount runtime - 1
+ , runtimeExecutionNanoseconds =
+ runtimeExecutionNanoseconds runtime + elapsed
+ , runtimeLongestExecutionNanoseconds =
+ max elapsed (runtimeLongestExecutionNanoseconds runtime)
+ }))
+
+publishVampireTerminal
+ :: VampireExecutor
+ -> TMVar VampireCompletion
+ -> VampireTerminal
+ -> IO ()
+publishVampireTerminal executor completionCell terminal = do
+ let completion = VampireCompletion terminal
+ _ <- Exception.evaluate completion
+ published <- getMonotonicTimeNSec
+ void
+ (atomically
+ (publishVampireTerminalSTM
+ executor completionCell published completion))
+
+publishVampireTerminalSTM
+ :: VampireExecutor
+ -> TMVar VampireCompletion
+ -> Word64
+ -> VampireCompletion
+ -> STM Bool
+publishVampireTerminalSTM executor completionCell published completion = do
+ inserted <- tryPutTMVar completionCell completion
+ when inserted
+ (modifyTVar'
+ (executorRuntime executor)
+ (\runtime ->
+ runtime
+ { runtimeFinalCompletionNanoseconds =
+ Just
+ (maybe published
+ (max published)
+ (runtimeFinalCompletionNanoseconds runtime))
+ }))
+ pure inserted
-runPreparedVerificationRequest
+runPreparedVerificationTerminal
:: (PreparedVerificationRequest -> IO ())
-> Vampire
-> PreparedVerificationRequest
- -> IO (Either ProverProcessError ProverAnswer)
-runPreparedVerificationRequest observer command request = do
+ -> IO VampireTerminal
+runPreparedVerificationTerminal observer command request = do
transcriptResult <-
runPreparedVampireProcessWithObserver observer command request
- pure
- (classifyPreparedVampireAnswer
- "typed obligation"
- command
- request
- <$> transcriptResult)
+ pure case transcriptResult of
+ Left processFailure ->
+ VampireProcessFailed processFailure
+ Right transcript ->
+ case classifyVampireCompleted
+ (preparedVerificationMode request)
+ transcript of
+ Left protocolFailure ->
+ VampireProtocolFailed
+ (boundedTranscriptDiagnostic
+ (Text.pack (show protocolFailure))
+ transcript)
+ Right Proved ->
+ VampireAccepted (preparedVerificationRequestId request)
+ Right Counterexample ->
+ VampireRejected
+ RejectedCounterexample
+ (boundedTranscriptDiagnostic
+ "Vampire found a countermodel"
+ transcript)
+ Right ContradictoryInput ->
+ VampireRejected
+ RejectedContradictoryInput
+ (boundedTranscriptDiagnostic
+ "Vampire reported contradictory input"
+ transcript)
+ Right Indeterminate ->
+ VampireRejected
+ RejectedIndeterminate
+ (boundedTranscriptDiagnostic
+ "Vampire did not establish the obligation"
+ transcript)
+
+completionProverResult
+ :: PreparedVerificationRequest
+ -> VampireCompletion
+ -> IO (Either ProverProcessError ProverAnswer)
+completionProverResult request completion =
+ case vampireCompletionTerminal completion of
+ VampireAccepted acceptedId
+ | acceptedId == preparedVerificationRequestId request ->
+ pure
+ (Right
+ (ProvedAnswer
+ (AcceptedVampireRun acceptedId)))
+ | otherwise ->
+ Exception.throwIO
+ (VampireExecutorFault
+ "accepted Vampire result has the wrong request id")
+ VampireRejected rejection _diagnostic ->
+ pure
+ (Right
+ (case rejection of
+ RejectedCounterexample ->
+ CounterSatisfiable task
+ RejectedContradictoryInput ->
+ ContradictoryAxioms task
+ RejectedIndeterminate ->
+ Uncertain task))
+ VampireProtocolFailed diagnostic ->
+ pure
+ (Right
+ (Error
+ "typed obligation"
+ (renderBoundedDiagnostic diagnostic)))
+ VampireProcessFailed processFailure ->
+ pure (Left processFailure)
+ VampireCancelled ->
+ Exception.throwIO
+ (VampireExecutorFault
+ "active admission encountered a cancelled Vampire request")
+ where
+ task = preparedVerificationText request
runPreparedVampireProcessWithObserver
:: (PreparedVerificationRequest -> IO ())
@@ -997,11 +1428,13 @@ runPreparedVampireProcessWithObserver
then
ProverLifecycleFailed
executable
- (exceptionText ioFailure)
+ (boundedText
+ (exceptionText ioFailure))
else
ProverLaunchFailed
executable
- (exceptionText ioFailure)))
+ (boundedText
+ (exceptionText ioFailure))))
| otherwise ->
Exception.throwIO err
@@ -1069,7 +1502,7 @@ superviseVampireProcess
(ProverTimedOut
executable
timeLimit
- partial))
+ (boundedCapturedStreams partial)))
Just (Left abort) -> do
terminateOwnedProcess
partial <-
@@ -1096,7 +1529,7 @@ superviseVampireProcess
(ProverTerminatedBySignal
executable
(negate signalCode)
- partial))
+ (boundedCapturedStreams partial)))
_ ->
pure do
stdout <- decodeOutput
@@ -1241,6 +1674,86 @@ capturedBytes capture =
. captureChunksReversed
<$> readIORef capture
+boundedTranscriptBytes :: Int
+boundedTranscriptBytes = 32 * 1024
+
+boundedTranscriptHalf :: Int
+boundedTranscriptHalf = boundedTranscriptBytes `div` 2
+
+boundedBytes :: ByteString.ByteString -> BoundedBytes
+boundedBytes bytes =
+ let original = ByteString.length bytes
+ in if original <= boundedTranscriptBytes
+ then
+ BoundedBytes
+ { boundedBytesOriginalCount = original
+ , boundedBytesRetained = bytes
+ , boundedBytesTruncated = False
+ }
+ else
+ BoundedBytes
+ { boundedBytesOriginalCount = original
+ , boundedBytesRetained =
+ ByteString.take boundedTranscriptHalf bytes
+ <> ByteString.drop
+ (original - boundedTranscriptHalf)
+ bytes
+ , boundedBytesTruncated = True
+ }
+
+boundedText :: Text -> Text
+boundedText =
+ decodeBoundedBytes . boundedBytes . TextEncoding.encodeUtf8
+
+decodeBoundedBytes :: BoundedBytes -> Text
+decodeBoundedBytes =
+ TextEncoding.decodeUtf8With TextEncodingError.lenientDecode
+ . boundedBytesRetained
+
+boundedCapturedStreams
+ :: PartialCapturedStreams
+ -> BoundedCapturedStreams
+boundedCapturedStreams PartialCapturedStreams{..} =
+ BoundedCapturedStreams
+ { boundedStdout = boundedBytes partialStdout
+ , boundedStderr = boundedBytes partialStderr
+ }
+
+boundedTranscriptDiagnostic
+ :: Text
+ -> CompletedTranscript
+ -> BoundedDiagnostic
+boundedTranscriptDiagnostic summary CompletedTranscript{completedStreams = streams} =
+ BoundedDiagnostic
+ { boundedDiagnosticSummary = boundedText summary
+ , boundedDiagnosticStdout =
+ boundedBytes
+ (TextEncoding.encodeUtf8 (completedStdout streams))
+ , boundedDiagnosticStderr =
+ boundedBytes
+ (TextEncoding.encodeUtf8 (completedStderr streams))
+ }
+
+renderBoundedDiagnostic :: BoundedDiagnostic -> Text
+renderBoundedDiagnostic BoundedDiagnostic{..} =
+ Text.unlines
+ [ boundedDiagnosticSummary
+ , renderBoundedStream "stdout" boundedDiagnosticStdout
+ , renderBoundedStream "stderr" boundedDiagnosticStderr
+ ]
+
+renderBoundedStream :: Text -> BoundedBytes -> Text
+renderBoundedStream streamLabel bytes =
+ streamLabel
+ <> (if boundedBytesTruncated bytes
+ then
+ " (retained first and last 16 KiB of "
+ <> Text.pack (show (boundedBytesOriginalCount bytes))
+ <> " bytes)"
+ else "")
+ <> ":\n"
+ <> decodeBoundedBytes bytes
+
supervisorAbortError
:: FilePath
-> PartialCapturedStreams
@@ -1248,11 +1761,14 @@ supervisorAbortError
-> ProverProcessError
supervisorAbortError executable partial = \case
SupervisorCommunicationFailed stream message ->
- ProverCommunicationFailed executable stream message
+ ProverCommunicationFailed executable stream (boundedText message)
SupervisorOutputLimitExceeded stream ->
- ProverOutputLimitExceeded executable stream partial
+ ProverOutputLimitExceeded
+ executable
+ stream
+ (boundedCapturedStreams partial)
SupervisorLifecycleFailed message ->
- ProverLifecycleFailed executable message
+ ProverLifecycleFailed executable (boundedText message)
wallTimeoutMicroseconds :: TimeLimit -> Int
wallTimeoutMicroseconds (Seconds seconds) =
@@ -1296,7 +1812,7 @@ decodeOutput executable stream bytes =
(ProverOutputMalformedUtf8
executable
stream
- (Text.pack (show err)))
+ (boundedText (Text.pack (show err))))
Right output ->
Right output
@@ -1304,31 +1820,6 @@ exceptionText :: IOException -> Text
exceptionText =
Text.pack . displayException
-classifyPreparedVampireAnswer
- :: Text
- -> Vampire
- -> PreparedVerificationRequest
- -> CompletedTranscript
- -> ProverAnswer
-classifyPreparedVampireAnswer
- errorLabel
- vampireCommand
- preparedRequest
- transcript@CompletedTranscript{..} =
- case classifyVampireCompleted
- (preparedVerificationMode preparedRequest)
- transcript of
- Left protocolError ->
- Error
- errorLabel
- (renderProtocolError protocolError completedStreams)
- Right outcome ->
- canonicalOutcomeAnswer
- preparedRequest
- vampireCommand
- transcript
- outcome
-
classifyVampireCompleted
:: VampireTaskMode
-> CompletedTranscript
@@ -1364,46 +1855,6 @@ statusesFromCompleteStreams CompleteCapturedStreams{..}
| otherwise ->
mempty
-canonicalOutcomeAnswer
- :: PreparedVerificationRequest
- -> Vampire
- -> CompletedTranscript
- -> CanonicalAtpOutcome
- -> ProverAnswer
-canonicalOutcomeAnswer
- preparedRequest
- vampireCommand
- transcript = \case
- Proved ->
- ProvedAnswer
- AcceptedVampireRun
- { acceptedVampireRequest = preparedRequest
- , acceptedVampireCommand = vampireCommand
- , acceptedVampireTranscript = transcript
- }
- Counterexample ->
- CounterSatisfiable
- (preparedVerificationText preparedRequest)
- ContradictoryInput ->
- ContradictoryAxioms
- (preparedVerificationText preparedRequest)
- Indeterminate ->
- Uncertain
- (preparedVerificationText preparedRequest)
-
-renderProtocolError
- :: VampireProtocolError
- -> CompleteCapturedStreams
- -> Text
-renderProtocolError protocolError CompleteCapturedStreams{..} =
- Text.unlines
- [ "Vampire protocol error: " <> Text.pack (show protocolError)
- , "stdout:"
- , completedStdout
- , "stderr:"
- , completedStderr
- ]
-
vampireStatusFromText :: Text -> VampireStatus
vampireStatusFromText = \case
"Theorem" -> StatusTheorem
diff --git a/source/Test/Unit/Provers.hs b/source/Test/Unit/Provers.hs
index 22a6b5b..f0f0e8f 100644
--- a/source/Test/Unit/Provers.hs
+++ b/source/Test/Unit/Provers.hs
@@ -7,9 +7,15 @@ import Checking.Backend.Problem
import Checking.Core
import Provers
-import Control.Concurrent (threadDelay)
+import Control.Concurrent
+ ( newEmptyMVar
+ , putMVar
+ , takeMVar
+ , threadDelay
+ )
import Control.Exception (bracket)
import Control.Exception qualified as Exception
+import Control.Monad (when)
import Control.Monad.Logger (runNoLoggingT)
import Data.IORef
( newIORef
@@ -35,6 +41,7 @@ import Test.Tasty.HUnit
import Text.Read (readMaybe)
import Text.Megaparsec (parseMaybe)
import UnliftIO.Async (cancel, mapConcurrently, withAsync)
+import UnliftIO.Async qualified as Async
unitTests :: TestTree
unitTests =
@@ -79,7 +86,38 @@ jobsSelectionTests =
vampireExecutorTests :: TestTree
vampireExecutorTests =
testGroup "bounded Vampire executor"
- [ testCase "bounds live one-core processes" do
+ [ testCase "opaque handles complete out of submission order" do
+ prepared <- preparedTypedTask 0
+ firstStarted <- newEmptyMVar
+ releaseFirst <- newEmptyMVar
+ withFakeVampire
+ [ "cat >/dev/null"
+ , "printf '%s\n' '% SZS status Theorem for fake'"
+ ]
+ \vampireCommand ->
+ withVampireExecutor
+ (positiveJobs 2)
+ vampireCommand
+ (\position _request ->
+ when
+ (workPositionLocalRequestOrdinal position == 1)
+ (putMVar firstStarted () >> takeMVar releaseFirst))
+ \executor -> withVampireRequestOwner executor \owner -> do
+ first <- submitVampireRequest
+ owner
+ (workPosition 1 1)
+ (preparedTypedProverRequest prepared)
+ takeMVar firstStarted
+ second <- submitVampireRequest
+ owner
+ (workPosition 1 2)
+ (preparedTypedProverRequest prepared)
+ secondCompletion <- awaitVampireRequest second
+ assertAcceptedRequest prepared secondCompletion
+ putMVar releaseFirst ()
+ firstCompletion <- awaitVampireRequest first
+ assertAcceptedRequest prepared firstCompletion
+ , testCase "bounds live one-core processes" do
prepared <- preparedTypedTask 0
withFakeVampire
[ "previous=''"
@@ -101,12 +139,12 @@ vampireExecutorTests =
(positiveJobs 2)
vampireCommand
(\_position _request -> pure ())
- \executor -> do
+ \executor -> withVampireRequestOwner executor \owner -> do
answers <- mapConcurrently
(\ordinal ->
runNoLoggingT
(runPreparedTypedProverWithExecutor
- executor
+ owner
(workPosition 1 ordinal)
prepared))
[1..4]
@@ -128,17 +166,17 @@ vampireExecutorTests =
(\_position _request ->
Exception.throwIO
(userError "observer failed"))
- \executor -> do
+ \executor -> withVampireRequestOwner executor \owner -> do
result <- Exception.try
(runNoLoggingT
(runPreparedTypedProverWithExecutor
- executor
+ owner
(workPosition 1 1)
prepared))
case result of
- Left (failure :: Exception.IOException) ->
+ Left (failure :: VampireExecutorFault) ->
assertBool
- "observer exception"
+ "global executor fault"
("observer failed"
`Text.isInfixOf`
Text.pack (show failure))
@@ -156,11 +194,11 @@ vampireExecutorTests =
(positiveJobs 1)
vampireCommand
(\_position _request -> pure ())
- \executor ->
+ \executor -> withVampireRequestOwner executor \owner ->
withAsync
(runNoLoggingT
(runPreparedTypedProverWithExecutor
- executor
+ owner
(workPosition 1 1)
prepared))
\running -> do
@@ -168,7 +206,7 @@ vampireExecutorTests =
withAsync
(runNoLoggingT
(runPreparedTypedProverWithExecutor
- executor
+ owner
(workPosition 2 1)
prepared))
\queued -> do
@@ -182,6 +220,239 @@ vampireExecutorTests =
vampireExecutorRunCount observed
`shouldBe` 1
assertProcessesGone processIds
+ , testCase "explicit cancellation completes queued and running handles" do
+ prepared <- preparedTypedTask 0
+ withProcessGroupFake
+ defaultTimeLimit
+ []
+ \pidFile vampireCommand ->
+ withVampireExecutor
+ (positiveJobs 1)
+ vampireCommand
+ (\_position _request -> pure ())
+ \executor -> withVampireRequestOwner executor \owner -> do
+ running <- submitVampireRequest
+ owner
+ (workPosition 1 1)
+ (preparedTypedProverRequest prepared)
+ processIds <- waitForProcessIds pidFile
+ queued <- submitVampireRequest
+ owner
+ (workPosition 1 2)
+ (preparedTypedProverRequest prepared)
+ cancelVampireRequest queued
+ awaitVampireRequest queued >>= assertCancelled
+ afterQueued <- vampireExecutorObservation executor
+ assertBool
+ "queued terminal updates final completion"
+ (isJust
+ (vampireExecutorFinalCompletionNanoseconds
+ afterQueued))
+ cancelVampireRequest running
+ awaitVampireRequest running >>= assertCancelled
+ assertProcessesGone processIds
+ , testCase "structured shutdown wakes waiter and full-queue submitter" do
+ prepared <- preparedTypedTask 0
+ withProcessGroupFake
+ defaultTimeLimit
+ []
+ \pidFile vampireCommand -> do
+ (waiter, blockedSubmit, processIds) <-
+ withVampireExecutor
+ (positiveJobs 1)
+ vampireCommand
+ (\_position _request -> pure ())
+ \executor ->
+ withVampireRequestOwner executor \owner -> do
+ running <- submitVampireRequest
+ owner
+ (workPosition 1 1)
+ (preparedTypedProverRequest prepared)
+ processIds <- waitForProcessIds pidFile
+ _queuedOne <- submitVampireRequest
+ owner
+ (workPosition 1 2)
+ (preparedTypedProverRequest prepared)
+ _queuedTwo <- submitVampireRequest
+ owner
+ (workPosition 1 3)
+ (preparedTypedProverRequest prepared)
+ waiter <- Async.async
+ (awaitVampireRequest running)
+ submitStarted <- newEmptyMVar
+ blockedSubmit <- Async.async do
+ putMVar submitStarted ()
+ submitVampireRequest
+ owner
+ (workPosition 1 4)
+ (preparedTypedProverRequest prepared)
+ takeMVar submitStarted
+ waitForSubmittedCount executor 3
+ pure (waiter, blockedSubmit, processIds)
+ Async.waitCatch waiter >>= \case
+ Right completion -> assertCancelled completion
+ Left failure ->
+ assertFailure
+ ("shutdown waiter failed: " <> show failure)
+ Async.waitCatch blockedSubmit >>= \case
+ Left failure ->
+ assertBool
+ "backpressured submit observes owner shutdown"
+ ("VampireRequestOwnerClosed"
+ `Text.isInfixOf`
+ Text.pack (show failure))
+ Right _handle ->
+ assertFailure
+ "backpressured submit survived structured shutdown"
+ assertProcessesGone processIds
+ , testCase "worker fault wakes a waiter and a full-queue submitter" do
+ prepared <- preparedTypedTask 0
+ observerEntered <- newEmptyMVar
+ failObserver <- newEmptyMVar
+ withFakeVampire
+ [ "cat >/dev/null"
+ , "printf '%s\n' '% SZS status Theorem for fake'"
+ ]
+ \vampireCommand ->
+ withVampireExecutor
+ (positiveJobs 1)
+ vampireCommand
+ (\position _request ->
+ when
+ (workPositionLocalRequestOrdinal position == 1)
+ (putMVar observerEntered ()
+ >> takeMVar failObserver
+ >> Exception.throwIO
+ (userError "fatal observer fault")))
+ \executor -> withVampireRequestOwner executor \owner -> do
+ first <- submitVampireRequest
+ owner
+ (workPosition 1 1)
+ (preparedTypedProverRequest prepared)
+ takeMVar observerEntered
+ _second <- submitVampireRequest
+ owner
+ (workPosition 1 2)
+ (preparedTypedProverRequest prepared)
+ _third <- submitVampireRequest
+ owner
+ (workPosition 1 3)
+ (preparedTypedProverRequest prepared)
+ withAsync
+ (submitVampireRequest
+ owner
+ (workPosition 1 4)
+ (preparedTypedProverRequest prepared))
+ \blockedSubmit -> do
+ waitForSubmittedCount executor 3
+ putMVar failObserver ()
+ awaitFault (awaitVampireRequest first)
+ Async.waitCatch blockedSubmit >>= \case
+ Left failure ->
+ assertExecutorFault failure
+ Right _handle ->
+ assertFailure
+ "full-queue submission survived executor fault"
+ , testCase "declared launch failure remains request-local" do
+ prepared <- preparedTypedTask 0
+ let missing = vampire
+ "/definitely/missing/felix-vampire"
+ defaultTimeLimit
+ defaultMemoryLimit
+ withVampireExecutor
+ (positiveJobs 1)
+ missing
+ (\_position _request -> pure ())
+ \executor -> withVampireRequestOwner executor \owner -> do
+ handle <- submitVampireRequest
+ owner
+ (workPosition 1 1)
+ (preparedTypedProverRequest prepared)
+ completion <- awaitVampireRequest handle
+ case vampireCompletionTerminal completion of
+ VampireProcessFailed ProverLaunchFailed{} -> pure ()
+ terminal ->
+ assertFailure
+ ("expected a local launch failure, got "
+ <> show terminal)
+ , testCase "protocol failure is distinct from ATP rejection" do
+ prepared <- preparedTypedTask 0
+ withFakeVampire
+ [ "cat >/dev/null"
+ , "printf '%s\n' 'completed without an SZS status'"
+ ]
+ \vampireCommand ->
+ withVampireExecutor
+ (positiveJobs 1)
+ vampireCommand
+ (\_position _request -> pure ())
+ \executor -> withVampireRequestOwner executor \owner -> do
+ handle <- submitVampireRequest
+ owner
+ (workPosition 1 1)
+ (preparedTypedProverRequest prepared)
+ completion <- awaitVampireRequest handle
+ case vampireCompletionTerminal completion of
+ VampireProtocolFailed{} -> pure ()
+ terminal ->
+ assertFailure
+ ("expected a protocol terminal, got "
+ <> show terminal)
+ , testCase "completed rejection diagnostics are compact" do
+ prepared <- preparedTypedTask 0
+ let headMarker :: Text
+ headMarker = "HEAD-MARKER"
+ tailMarker :: Text
+ tailMarker = "TAIL-MARKER"
+ status :: Text
+ status = "% SZS status CounterSatisfiable for fake"
+ originalByteCount =
+ Text.length headMarker
+ + 1048576
+ + Text.length tailMarker + 1
+ + Text.length status + 1
+ withFakeVampire
+ [ "printf '%s' 'HEAD-MARKER'"
+ , "head -c 1048576 /dev/zero"
+ , "printf '%s\n' 'TAIL-MARKER'"
+ , "printf '%s\n' '% SZS status CounterSatisfiable for fake'"
+ ]
+ \vampireCommand ->
+ withVampireExecutor
+ (positiveJobs 1)
+ vampireCommand
+ (\_position _request -> pure ())
+ \executor -> withVampireRequestOwner executor \owner -> do
+ handle <- submitVampireRequest
+ owner
+ (workPosition 1 1)
+ (preparedTypedProverRequest prepared)
+ completion <- awaitVampireRequest handle
+ case renderVampireTerminalDiagnostic
+ (vampireCompletionTerminal completion) of
+ Just diagnostic -> do
+ assertBool
+ "retained diagnostic is bounded"
+ (Text.length diagnostic < 70000)
+ assertBool
+ "truncation is reported"
+ ("retained first and last 16 KiB"
+ `Text.isInfixOf` diagnostic)
+ assertBool
+ "original byte count is reported"
+ (("of "
+ <> Text.pack
+ (show originalByteCount)
+ <> " bytes)")
+ `Text.isInfixOf` diagnostic)
+ assertBool
+ "diagnostic head is retained"
+ (headMarker `Text.isInfixOf` diagnostic)
+ assertBool
+ "diagnostic tail is retained"
+ (tailMarker `Text.isInfixOf` diagnostic)
+ Nothing ->
+ assertFailure "expected a rejected terminal"
]
positiveJobs :: Int -> EffectiveJobs
@@ -485,6 +756,39 @@ assertProved = \case
answer ->
assertFailure ("expected a proof, got " <> show answer)
+assertAcceptedRequest
+ :: PreparedTypedProverTask ref local origin global
+ -> VampireCompletion
+ -> Assertion
+assertAcceptedRequest prepared completion =
+ case vampireCompletionTerminal completion of
+ VampireAccepted requestId ->
+ requestId
+ `shouldBe`
+ preparedVerificationRequestId
+ (preparedTypedProverRequest prepared)
+ terminal ->
+ assertFailure ("expected an accepted terminal, got " <> show terminal)
+
+assertCancelled :: VampireCompletion -> Assertion
+assertCancelled completion =
+ vampireCompletionTerminal completion `shouldBe` VampireCancelled
+
+awaitFault :: IO value -> Assertion
+awaitFault action = do
+ result <- Exception.try action
+ case result of
+ Left failure -> assertExecutorFault failure
+ Right _value -> assertFailure "expected a global executor fault"
+
+assertExecutorFault :: Exception.SomeException -> Assertion
+assertExecutorFault failure =
+ case Exception.fromException failure :: Maybe VampireExecutorFault of
+ Just _fault -> pure ()
+ Nothing ->
+ assertFailure
+ ("expected VampireExecutorFault, got " <> show failure)
+
assertProtocolError
:: Text
-> Either ProverProcessError ProverAnswer