@@ -7,10 +7,12 @@ module System.Process.PID1
7
7
, getRunGroup
8
8
, getRunUser
9
9
, getRunWorkDir
10
+ , getRunSignalImmediateChildOnly
10
11
, run
11
12
, runWithOptions
12
13
, setRunEnv
13
14
, setRunExitTimeoutSec
15
+ , setRunSignalImmediateChildOnly
14
16
, setRunGroup
15
17
, setRunUser
16
18
, setRunWorkDir
@@ -52,6 +54,7 @@ data RunOptions = RunOptions
52
54
-- timeout (in seconds) to wait for all child processes to exit after
53
55
-- receiving SIGTERM or SIGINT signal
54
56
, runExitTimeoutSec :: Int
57
+ , runSignalImmediateChildOnly :: Bool
55
58
} deriving Show
56
59
57
60
-- | return default `RunOptions`
@@ -63,7 +66,8 @@ defaultRunOptions = RunOptions
63
66
, runUser = Nothing
64
67
, runGroup = Nothing
65
68
, runWorkDir = Nothing
66
- , runExitTimeoutSec = 5 }
69
+ , runExitTimeoutSec = 5
70
+ , runSignalImmediateChildOnly = False }
67
71
68
72
-- | Get environment variable overrides for the given `RunOptions`
69
73
--
@@ -127,6 +131,19 @@ getRunExitTimeoutSec = runExitTimeoutSec
127
131
setRunExitTimeoutSec :: Int -> RunOptions -> RunOptions
128
132
setRunExitTimeoutSec sec opts = opts { runExitTimeoutSec = sec }
129
133
134
+ -- | Return boolean flag if we should only send SIGTERM to the immediate child process
135
+ --
136
+ --- @since 0.1.3.0
137
+ getRunSignalImmediateChildOnly :: RunOptions -> Bool
138
+ getRunSignalImmediateChildOnly = runSignalImmediateChildOnly
139
+
140
+ -- | Set boolean flag if we should only send SIGTERM to the immediate child process
141
+ --
142
+ -- @since 0.1.3.0
143
+ setRunSignalImmediateChildOnly :: Bool -> RunOptions -> RunOptions
144
+ setRunSignalImmediateChildOnly x opts = opts { runSignalImmediateChildOnly = x }
145
+
146
+
130
147
-- | Run the given command with specified arguments, with optional environment
131
148
-- variable override (default is to use the current process's environment).
132
149
--
@@ -168,22 +185,20 @@ runWithOptions opts cmd args = do
168
185
for_ (runWorkDir opts) setCurrentDirectory
169
186
let env' = runEnv opts
170
187
timeout = runExitTimeoutSec opts
188
+ single = runSignalImmediateChildOnly opts
171
189
-- check if we should act as pid1 or just exec the process
172
190
myID <- getProcessID
173
191
if myID == 1
174
- then runAsPID1 cmd args env' timeout
192
+ then runAsPID1 single cmd args env' timeout
175
193
else executeFile cmd True args env'
176
194
177
195
-- | Run as a child with signal handling and orphan reaping.
178
- runAsPID1 :: FilePath -> [String ] -> Maybe [(String , String )] -> Int -> IO a
179
- runAsPID1 cmd args env' timeout = do
196
+ runAsPID1 :: Bool -> FilePath -> [String ] -> Maybe [(String , String )] -> Int -> IO a
197
+ runAsPID1 single cmd args env' timeout = do
180
198
-- Set up an MVar to indicate we're ready to start killing all
181
199
-- children processes. Then start a thread waiting for that
182
200
-- variable to be filled and do the actual killing.
183
201
killChildrenVar <- newEmptyMVar
184
- _ <- forkIO $ do
185
- takeMVar killChildrenVar
186
- killAllChildren timeout
187
202
188
203
-- Helper function to start killing, used below
189
204
let startKilling = void $ tryPutMVar killChildrenVar ()
@@ -206,6 +221,13 @@ runAsPID1 cmd args env' timeout = do
206
221
ClosedHandle e -> assert False (exitWith e)
207
222
OpenHandle pid -> return pid
208
223
224
+ _ <- forkIO $ do
225
+ takeMVar killChildrenVar
226
+ if single then
227
+ killImmediateChild child timeout
228
+ else
229
+ killAllChildren timeout
230
+
209
231
-- Loop on reaping child processes
210
232
reap startKilling child
211
233
@@ -267,6 +289,26 @@ killAllChildren timeout = do
267
289
then return ()
268
290
else throwIO e
269
291
292
+ killImmediateChild :: CPid -> Int -> IO ()
293
+ killImmediateChild cid timeout = do
294
+ -- Send immediate child processes the TERM signal
295
+ signalProcess sigTERM cid `catch` \ e ->
296
+ if isDoesNotExistError e
297
+ then return ()
298
+ else throwIO e
299
+
300
+ -- Wait for `timeout` seconds. We don't need to put in any logic about
301
+ -- whether there are still child processes; if all children have
302
+ -- exited, then the reap loop will exit and our process will shut
303
+ -- down.
304
+ threadDelay $ timeout * 1000 * 1000
305
+
306
+ -- OK, some children didn't exit. Now time to get serious!
307
+ signalProcess sigKILL (- 1 ) `catch` \ e ->
308
+ if isDoesNotExistError e
309
+ then return ()
310
+ else throwIO e
311
+
270
312
-- | Convert a ProcessStatus to an ExitCode. In the case of a signal being the
271
313
-- cause of termination, see 'signalToEC'.
272
314
toExitCode :: ProcessStatus -> ExitCode
0 commit comments