Skip to content

Commit

Permalink
Expose Test.Sandwich.Util.Process
Browse files Browse the repository at this point in the history
  • Loading branch information
thomasjm committed Jan 7, 2024
1 parent 557515b commit 125cc0f
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import System.FilePath
import System.IO
import System.Process
import Test.Sandwich
import Test.Sandwich.Util.Process
import Test.Sandwich.WebDriver.Internal.Binaries
import Test.Sandwich.WebDriver.Internal.Binaries.DetectChrome (detectChromeVersion)
import Test.Sandwich.WebDriver.Internal.Ports
Expand Down
32 changes: 0 additions & 32 deletions sandwich-webdriver/src/Test/Sandwich/WebDriver/Internal/Util.hs
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,12 @@ import Control.Exception
import qualified Control.Exception.Lifted as E
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Logger
import Control.Monad.Trans.Control (MonadBaseControl)
import Control.Retry
import Data.Maybe
import Data.String.Interpolate
import qualified Data.Text as T
import System.Directory
import System.Process
import qualified System.Random as R
import Test.Sandwich.Logging

#ifdef mingw32_HOST_OS
import System.IO
Expand Down Expand Up @@ -67,31 +63,3 @@ whenRight (Right x) action = action x

makeUUID :: IO T.Text
makeUUID = (T.pack . take 10 . R.randomRs ('a','z')) <$> R.newStdGen

-- * Stopping processes

gracefullyStopProcess :: (MonadIO m, MonadLogger m) => ProcessHandle -> Int -> m ()
gracefullyStopProcess p gracePeriodUs = do
liftIO $ interruptProcessGroupOf p
gracefullyWaitForProcess p gracePeriodUs

gracefullyWaitForProcess :: (MonadIO m, MonadLogger m) => ProcessHandle -> Int -> m ()
gracefullyWaitForProcess p gracePeriodUs = do
let waitForExit = do
let policy = limitRetriesByCumulativeDelay gracePeriodUs $ capDelay 200_000 $ exponentialBackoff 1_000
retrying policy (\_ x -> return $ isNothing x) $ \_ -> do
liftIO $ getProcessExitCode p

waitForExit >>= \case
Just _ -> return ()
Nothing -> do
pid <- liftIO $ getPid p
warn [i|(#{pid}) Process didn't stop after #{gracePeriodUs}us; trying to interrupt|]

liftIO $ interruptProcessGroupOf p
waitForExit >>= \case
Just _ -> return ()
Nothing -> void $ do
warn [i|(#{pid}) Process didn't stop after a further #{gracePeriodUs}us; going to kill|]
liftIO $ terminateProcess p
liftIO $ waitForProcess p
1 change: 1 addition & 0 deletions sandwich/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Unreleased changes

* Add primed versions of createProcessWithLogging etc. with customizable log level
* Add `Test.Sandwich.Util.Process` with `gracefullyStopProcess` and `gracefullyWaitForProcess` (and remove these from an internal `sandwich-webdriver` module).

## 0.2.1.0

Expand Down
1 change: 1 addition & 0 deletions sandwich/package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ library:
- Test.Sandwich.Formatters.TerminalUI
- Test.Sandwich.Internal
- Test.Sandwich.TH
- Test.Sandwich.Util.Process
when:
- condition: "os(windows)"
dependencies:
Expand Down
45 changes: 45 additions & 0 deletions sandwich/src/Test/Sandwich/Util/Process.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@

module Test.Sandwich.Util.Process (
gracefullyStopProcess
, gracefullyWaitForProcess
) where

import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Logger
import Control.Retry
import Data.Maybe
import Data.String.Interpolate
import System.Process
import Test.Sandwich.Logging


-- | Interrupt a process and wait for it to terminate.
gracefullyStopProcess :: (MonadIO m, MonadLogger m) => ProcessHandle -> Int -> m ()
gracefullyStopProcess p gracePeriodUs = do
liftIO $ interruptProcessGroupOf p
gracefullyWaitForProcess p gracePeriodUs

-- | Wait for a process to terminate. If it doesn't terminate within 'gracePeriodUs' microseconds,
-- send it an interrupt signal and wait for another 'gracePeriodUs' microseconds.
-- After this time elapses send a terminate signal and wait for the process to die.
gracefullyWaitForProcess :: (MonadIO m, MonadLogger m) => ProcessHandle -> Int -> m ()
gracefullyWaitForProcess p gracePeriodUs = do
let waitForExit = do
let policy = limitRetriesByCumulativeDelay gracePeriodUs $ capDelay 200_000 $ exponentialBackoff 1_000
retrying policy (\_ x -> return $ isNothing x) $ \_ -> do
liftIO $ getProcessExitCode p

waitForExit >>= \case
Just _ -> return ()
Nothing -> do
pid <- liftIO $ getPid p
warn [i|(#{pid}) Process didn't stop after #{gracePeriodUs}us; trying to interrupt|]

liftIO $ interruptProcessGroupOf p
waitForExit >>= \case
Just _ -> return ()
Nothing -> void $ do
warn [i|(#{pid}) Process didn't stop after a further #{gracePeriodUs}us; going to kill|]
liftIO $ terminateProcess p
liftIO $ waitForProcess p

0 comments on commit 125cc0f

Please sign in to comment.