Skip to content

Commit b19074b

Browse files
committed
Create CleanupConfig
1 parent c86522c commit b19074b

File tree

2 files changed

+86
-36
lines changed

2 files changed

+86
-36
lines changed

src/Streamly/Internal/System/Process.hs

Lines changed: 80 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,12 @@ module Streamly.Internal.System.Process
8080
, pipeStdin
8181
, pipeStdout
8282

83+
-- ** CleanupConfig options
84+
, setCleanupCfg
85+
, setTerminateOnException
86+
, setInterruptOnException
87+
, setWaitOnException
88+
8389
-- * Exceptions
8490
, ProcessFailure (..)
8591

@@ -158,6 +164,7 @@ import System.Process
158164
, waitForProcess
159165
, CmdSpec(..)
160166
, terminateProcess
167+
, interruptProcessGroupOf
161168
, withCreateProcess
162169
)
163170
#endif
@@ -230,13 +237,29 @@ inheritStdout (Config _) = Config True
230237

231238
#else
232239

233-
newtype Config = Config CreateProcess
240+
data InterruptOrTerminate =
241+
Interrupt
242+
-- ^Uses "interruptProcessGroupOf" in cleanup on exception
243+
-- sends SIGINT to the child process
244+
| Terminate
245+
-- ^Uses "terminateProcess" in cleanup on exception
246+
-- sends SIGTERM to the child process
247+
248+
data CleanupConfig = CleanupConfig
249+
{ terminateBehaviour :: InterruptOrTerminate
250+
, blockingWait :: Bool
251+
}
252+
253+
defaultCleanupConfig :: CleanupConfig
254+
defaultCleanupConfig = CleanupConfig Terminate False
255+
256+
data Config = Config CleanupConfig CreateProcess
234257

235258
-- | Create a default process configuration from an executable file path and
236259
-- an argument list.
237260
--
238261
mkConfig :: FilePath -> [String] -> Config
239-
mkConfig path args = Config $ CreateProcess
262+
mkConfig path args = Config defaultCleanupConfig $ CreateProcess
240263
{ cmdspec = RawCommand path args
241264
, cwd = Nothing -- inherit
242265
, env = Nothing -- inherit
@@ -274,14 +297,14 @@ mkConfig path args = Config $ CreateProcess
274297
--
275298
-- Default is 'Nothing' - inherited from the parent process.
276299
setCwd :: Maybe FilePath -> Config -> Config
277-
setCwd path (Config cfg) = Config $ cfg { cwd = path }
300+
setCwd path (Config cleanupCfg cfg) = Config cleanupCfg $ cfg { cwd = path }
278301

279302
-- | Set the environment variables for the new process. When 'Nothing', the
280303
-- environment is inherited from the parent process.
281304
--
282305
-- Default is 'Nothing' - inherited from the parent process.
283306
setEnv :: Maybe [(String, String)] -> Config -> Config
284-
setEnv e (Config cfg) = Config $ cfg { env = e }
307+
setEnv e (Config cleanupCfg cfg) = Config cleanupCfg $ cfg { env = e }
285308

286309
{-
287310
-- XXX We should allow setting only those stdio streams which are not used for
@@ -311,15 +334,15 @@ toStdStream x =
311334
312335
-- | What to do with the @stdin@ stream of the process.
313336
setStdin :: Stdio -> Config -> Config
314-
setStdin x (Config cfg) = Config $ cfg { std_in = toStdStream x }
337+
setStdin x (Config cleanupCfg cfg) = Config cleanupCfg $ cfg { std_in = toStdStream x }
315338
316339
-- | What to do with the @stdout@ stream of the process.
317340
setStdout :: Stdio -> Config -> Config
318-
setStdout x (Config cfg) = Config $ cfg { std_out = toStdStream x }
341+
setStdout x (Config cleanupCfg cfg) = Config cleanupCfg $ cfg { std_out = toStdStream x }
319342
320343
-- | What to do with the @stderr@ stream of the process.
321344
setStderr :: Stdio -> Config -> Config
322-
setStderr x (Config cfg) = Config $ cfg { std_err = toStdStream x }
345+
setStderr x (Config cleanupCfg cfg) = Config cleanupCfg $ cfg { std_err = toStdStream x }
323346
-}
324347

325348
-- | Close all open file descriptors inherited from the parent process. Note,
@@ -331,7 +354,7 @@ setStderr x (Config cfg) = Config $ cfg { std_err = toStdStream x }
331354
-- Note: if the number of open descriptors is large, it may take a while
332355
-- closing them.
333356
closeFiles :: Bool -> Config -> Config
334-
closeFiles x (Config cfg) = Config $ cfg { close_fds = x }
357+
closeFiles x (Config cleanupCfg cfg) = Config cleanupCfg $ cfg { close_fds = x }
335358

336359
-- XXX Do these details apply to Windows as well?
337360

@@ -344,7 +367,7 @@ closeFiles x (Config cfg) = Config $ cfg { close_fds = x }
344367
--
345368
-- Default is 'False', the new process belongs to the parent's process group.
346369
newProcessGroup :: Bool -> Config -> Config
347-
newProcessGroup x (Config cfg) = Config $ cfg { create_group = x }
370+
newProcessGroup x (Config cleanupCfg cfg) = Config cleanupCfg $ cfg { create_group = x }
348371

349372
-- | 'InheritSession' makes the new process inherit the terminal session from the
350373
-- parent process. This is the default.
@@ -377,8 +400,8 @@ data Session =
377400
--
378401
-- Default is 'InheritSession'.
379402
setSession :: Session -> Config -> Config
380-
setSession x (Config cfg) =
381-
Config $
403+
setSession x (Config cleanupCfg cfg) =
404+
Config cleanupCfg $
382405
case x of
383406
InheritSession -> cfg
384407
NewSession -> cfg { new_session = True}
@@ -397,11 +420,11 @@ setSession x (Config cfg) =
397420
-- Default is 'Nothing' - inherit from the parent.
398421
setUserId :: Maybe Word32 -> Config -> Config
399422
#if defined(mingw32_HOST_OS)
400-
setUserId _ (Config cfg) =
401-
Config cfg
423+
setUserId _ (Config cleanupCfg cfg) =
424+
Config cleanupCfg cfg
402425
#else
403-
setUserId x (Config cfg) =
404-
Config $ cfg { child_user = CUid <$> x }
426+
setUserId x (Config cleanupCfg cfg) =
427+
Config cleanupCfg $ cfg { child_user = CUid <$> x }
405428
#endif
406429

407430
-- | Use the POSIX
@@ -417,13 +440,25 @@ setUserId x (Config cfg) =
417440
-- Default is 'Nothing' - inherit from the parent.
418441
setGroupId :: Maybe Word32 -> Config -> Config
419442
#if defined(mingw32_HOST_OS)
420-
setGroupId _ (Config cfg) =
421-
Config cfg
443+
setGroupId _ (Config cleanupCfg cfg) =
444+
Config cleanupCfg cfg
422445
#else
423-
setGroupId x (Config cfg) =
424-
Config $ cfg { child_group = CGid <$> x }
446+
setGroupId x (Config cleanupCfg cfg) =
447+
Config cleanupCfg $ cfg { child_group = CGid <$> x }
425448
#endif
426449

450+
setCleanupCfg :: (CleanupConfig -> CleanupConfig) -> Config -> Config
451+
setCleanupCfg modCleanupCfg (Config cleanupCfg cfg) = Config (modCleanupCfg cleanupCfg) cfg
452+
453+
setTerminateOnException :: CleanupConfig -> CleanupConfig
454+
setTerminateOnException cleanupCfg = cleanupCfg { terminateBehaviour = Terminate }
455+
456+
setInterruptOnException :: CleanupConfig -> CleanupConfig
457+
setInterruptOnException cleanupCfg = cleanupCfg { terminateBehaviour = Interrupt }
458+
459+
setWaitOnException :: Bool -> CleanupConfig -> CleanupConfig
460+
setWaitOnException b cleanupCfg = cleanupCfg { blockingWait = b }
461+
427462
-- See https://www.cons.org/cracauer/sigint.html for more details on signal
428463
-- handling by interactive processes.
429464

@@ -441,7 +476,7 @@ setGroupId x (Config cfg) =
441476
--
442477
-- POSIX only. Default is 'False'.
443478
interruptChildOnly :: Bool -> Config -> Config
444-
interruptChildOnly x (Config cfg) = Config $ cfg { delegate_ctlc = x }
479+
interruptChildOnly x (Config cleanupCfg cfg) = Config cleanupCfg $ cfg { delegate_ctlc = x }
445480

446481
{-# DEPRECATED parentIgnoresInterrupt "Use interruptChildOnly instead." #-}
447482
parentIgnoresInterrupt :: Bool -> Config -> Config
@@ -452,14 +487,14 @@ parentIgnoresInterrupt = interruptChildOnly
452487
--
453488
-- Default is 'True'.
454489
waitForDescendants :: Bool -> Config -> Config
455-
waitForDescendants x (Config cfg) = Config $ cfg { use_process_jobs = x }
490+
waitForDescendants x (Config cleanupCfg cfg) = Config cleanupCfg $ cfg { use_process_jobs = x }
456491

457492
{-# DEPRECATED waitForChildTree "Use waitForDescendants instead." #-}
458493
waitForChildTree :: Bool -> Config -> Config
459494
waitForChildTree = waitForDescendants
460495

461496
pipeStdErr :: Config -> Config
462-
pipeStdErr (Config cfg) = Config $ cfg { std_err = CreatePipe }
497+
pipeStdErr (Config cleanupCfg cfg) = Config cleanupCfg $ cfg { std_err = CreatePipe }
463498

464499
pipeStdin :: Config -> Config
465500
pipeStdin (Config cleanupCfg cfg) = Config cleanupCfg $ cfg { std_in = CreatePipe }
@@ -468,10 +503,10 @@ pipeStdout :: Config -> Config
468503
pipeStdout (Config cleanupCfg cfg) = Config cleanupCfg $ cfg { std_out = CreatePipe }
469504

470505
inheritStdin :: Config -> Config
471-
inheritStdin (Config cfg) = Config $ cfg { std_in = Inherit }
506+
inheritStdin (Config cleanupCfg cfg) = Config cleanupCfg $ cfg { std_in = Inherit }
472507

473508
inheritStdout :: Config -> Config
474-
inheritStdout (Config cfg) = Config $ cfg { std_out = Inherit }
509+
inheritStdout (Config cleanupCfg cfg) = Config cleanupCfg $ cfg { std_out = Inherit }
475510
#endif
476511

477512
-------------------------------------------------------------------------------
@@ -531,13 +566,18 @@ cleanupNormal (_, _, _, procHandle) = do
531566
-- possibly use a timer and send a SIGKILL after the timeout if the process is
532567
-- still hanging around.
533568
cleanupException ::
534-
(Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle) -> IO ()
535-
cleanupException (stdinMaybe, stdoutMaybe, stderrMaybe, ph) = do
569+
CleanupConfig
570+
-> (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle) -> IO ()
571+
cleanupException cleanupCfg (stdinMaybe, stdoutMaybe, stderrMaybe, ph) = do
536572
-- Send a SIGTERM to the process
537573
#ifdef USE_NATIVE
538-
terminate ph
574+
case terminateBehaviour cleanupCfg of
575+
Interrupt -> _
576+
Terminate -> terminate ph
539577
#else
540-
terminateProcess ph
578+
case terminateBehaviour cleanupCfg of
579+
Interrupt -> interruptProcessGroupOf ph
580+
Terminate -> terminateProcess ph
541581
#endif
542582

543583
-- Ideally we should be closing the handle without flushing the buffers so
@@ -549,9 +589,13 @@ cleanupException (stdinMaybe, stdoutMaybe, stderrMaybe, ph) = do
549589

550590
-- Non-blocking wait for the process to go away
551591
#ifdef USE_NATIVE
552-
void $ forkIO (void $ wait ph)
592+
if blockingWait cleanupCfg
593+
then void $ wait ph
594+
else void $ forkIO (void $ wait ph)
553595
#else
554-
void $ forkIO (void $ waitForProcess ph)
596+
if blockingWait cleanupCfg
597+
then void $ waitForProcess ph
598+
else void $ forkIO (void $ waitForProcess ph)
555599
#endif
556600

557601
where
@@ -575,7 +619,7 @@ createProc' ::
575619
(Config -> Config) -- ^ Process attribute modifier
576620
-> FilePath -- ^ Executable path
577621
-> [String] -- ^ Arguments
578-
-> IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
622+
-> IO (CleanupConfig, (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle))
579623
-- ^ (Input Handle, Output Handle, Error Handle, Process Handle)
580624
createProc' modCfg path args = do
581625
#ifdef USE_NATIVE
@@ -590,18 +634,18 @@ createProc' modCfg path args = do
590634
hSetBuffering inp NoBuffering
591635
hSetBuffering out NoBuffering
592636
hSetBuffering err NoBuffering
593-
return (Just inp, Just out, err, proc)
637+
return (cleanupCfg, (Just inp, Just out, err, proc))
594638
#else
595639
r@(inp, out, err, _) <- createProcess cfg
596640
mapM_ (`hSetBuffering` NoBuffering) inp
597641
mapM_ (`hSetBuffering` NoBuffering) out
598642
mapM_ (`hSetBuffering` NoBuffering) err
599-
return r
643+
return (cleanupCfg, r)
600644
#endif
601645

602646
where
603647

604-
Config cfg = modCfg $ mkConfig path args
648+
Config cleanupCfg cfg = modCfg $ mkConfig path args
605649

606650
{-# INLINE putChunksClose #-}
607651
putChunksClose :: MonadIO m =>
@@ -625,7 +669,7 @@ pipeChunksWithAction ::
625669
-> Stream m a -- ^ Output stream
626670
pipeChunksWithAction run modCfg path args =
627671
Stream.bracketIO3
628-
alloc cleanupNormal cleanupException cleanupException run
672+
alloc (cleanupNormal . snd) (uncurry cleanupException) (uncurry cleanupException) (run . snd)
629673

630674
where
631675

@@ -1149,7 +1193,7 @@ standalone wait (close_stdin, close_stdout, close_stderr) modCfg path args =
11491193
else return $ Right procHandle
11501194

11511195
cfg =
1152-
let Config c = modCfg $ mkConfig path args
1196+
let Config _ c = modCfg $ mkConfig path args
11531197
s_in = if close_stdin then NoStream else Inherit
11541198
s_out = if close_stdout then NoStream else Inherit
11551199
s_err = if close_stderr then NoStream else Inherit

src/Streamly/System/Process.hs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,12 @@ module Streamly.System.Process
128128
-- | These options have no effect on Posix.
129129
, waitForDescendants
130130

131+
-- ** CleanupConfig options
132+
, setCleanupCfg
133+
, setTerminateOnException
134+
, setInterruptOnException
135+
, setWaitOnException
136+
131137
-- * Generation
132138
, toChunks
133139
, toChunksWith

0 commit comments

Comments
 (0)