@@ -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--
238261mkConfig :: 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.
276299setCwd :: 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.
283306setEnv :: 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.
313336setStdin :: 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.
317340setStdout :: 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.
321344setStderr :: 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.
333356closeFiles :: 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.
346369newProcessGroup :: 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'.
379402setSession :: 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.
398421setUserId :: 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.
418441setGroupId :: 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'.
443478interruptChildOnly :: 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." #-}
447482parentIgnoresInterrupt :: Bool -> Config -> Config
@@ -452,14 +487,14 @@ parentIgnoresInterrupt = interruptChildOnly
452487--
453488-- Default is 'True'.
454489waitForDescendants :: 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." #-}
458493waitForChildTree :: Bool -> Config -> Config
459494waitForChildTree = waitForDescendants
460495
461496pipeStdErr :: Config -> Config
462- pipeStdErr (Config cfg) = Config $ cfg { std_err = CreatePipe }
497+ pipeStdErr (Config cleanupCfg cfg) = Config cleanupCfg $ cfg { std_err = CreatePipe }
463498
464499pipeStdin :: Config -> Config
465500pipeStdin (Config cleanupCfg cfg) = Config cleanupCfg $ cfg { std_in = CreatePipe }
@@ -468,10 +503,10 @@ pipeStdout :: Config -> Config
468503pipeStdout (Config cleanupCfg cfg) = Config cleanupCfg $ cfg { std_out = CreatePipe }
469504
470505inheritStdin :: Config -> Config
471- inheritStdin (Config cfg) = Config $ cfg { std_in = Inherit }
506+ inheritStdin (Config cleanupCfg cfg) = Config cleanupCfg $ cfg { std_in = Inherit }
472507
473508inheritStdout :: 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.
533568cleanupException ::
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)
580624createProc' 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 #-}
607651putChunksClose :: MonadIO m =>
@@ -625,7 +669,7 @@ pipeChunksWithAction ::
625669 -> Stream m a -- ^ Output stream
626670pipeChunksWithAction 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
0 commit comments