@@ -45,17 +45,25 @@ case class proc(command: Shellable*) {
4545   * `call` provides a number of parameters that let you configure how the subprocess 
4646   * is run: 
4747   * 
48-    * @param  cwd              the working directory of the subprocess 
49-    * @param  env              any additional environment variables you wish to set in the subprocess 
50-    * @param  stdin            any data you wish to pass to the subprocess's standard input 
51-    * @param  stdout           How the process's output stream is configured. 
52-    * @param  stderr           How the process's error stream is configured. 
53-    * @param  mergeErrIntoOut  merges the subprocess's stderr stream into it's stdout 
54-    * @param  timeout          how long to wait in milliseconds for the subprocess to complete 
55-    * @param  check            disable this to avoid throwing an exception if the subprocess 
56-    *                        fails with a non-zero exit code 
57-    * @param  propagateEnv     disable this to avoid passing in this parent process's 
58-    *                        environment variables to the subprocess 
48+    * @param  cwd                 the working directory of the subprocess 
49+    * @param  env                 any additional environment variables you wish to set in the subprocess 
50+    * @param  stdin               any data you wish to pass to the subprocess's standard input 
51+    * @param  stdout              How the process's output stream is configured. 
52+    * @param  stderr              How the process's error stream is configured. 
53+    * @param  mergeErrIntoOut     merges the subprocess's stderr stream into it's stdout 
54+    * @param  timeout             how long to wait in milliseconds for the subprocess to complete 
55+    *                           (-1 for no timeout) 
56+    * @param  check               disable this to avoid throwing an exception if the subprocess 
57+    *                           fails with a non-zero exit code 
58+    * @param  propagateEnv        disable this to avoid passing in this parent process's 
59+    *                           environment variables to the subprocess 
60+    * @param  timeoutGracePeriod  if the timeout is enabled, how long in milliseconds for the 
61+    *                           subprocess to gracefully terminate before attempting to 
62+    *                           forcibly kill it 
63+    *                           (-1 for no kill, 0 for always kill immediately) 
64+    * 
65+    * @note  the issuing of `SIGTERM` instead of `SIGKILL` is implementation dependent on your JVM version. Pre-Java 9, no `SIGTERM` may be 
66+    *       issued. Check the documentation for your JDK's `Process.destroy`. 
5967   */  
6068  def  call (
6169      cwd : Path  =  null ,
@@ -66,7 +74,9 @@ case class proc(command: Shellable*) {
6674      mergeErrIntoOut : Boolean  =  false ,
6775      timeout : Long  =  - 1 ,
6876      check : Boolean  =  true ,
69-       propagateEnv : Boolean  =  true 
77+       propagateEnv : Boolean  =  true ,
78+       //  this cannot be next to `timeout` as this will introduce a bin-compat break (default arguments are numbered in the bytecode)
79+       timeoutGracePeriod : Long  =  100 
7080  ):  CommandResult  =  {
7181
7282    val  chunks  =  new  java.util.concurrent.ConcurrentLinkedQueue [Either [geny.Bytes , geny.Bytes ]]
@@ -87,14 +97,38 @@ case class proc(command: Shellable*) {
8797      propagateEnv
8898    )
8999
90-     sub.join(timeout)
100+     sub.join(timeout, timeoutGracePeriod )
91101
92102    val  chunksSeq  =  chunks.iterator.asScala.toIndexedSeq
93103    val  res  =  CommandResult (commandChunks, sub.exitCode(), chunksSeq)
94104    if  (res.exitCode ==  0  ||  ! check) res
95105    else  throw  SubprocessException (res)
96106  }
97107
108+   //  forwarder for the new timeoutGracePeriod flag
109+   private [os] def  call (
110+       cwd : Path ,
111+       env : Map [String , String ],
112+       stdin : ProcessInput ,
113+       stdout : ProcessOutput ,
114+       stderr : ProcessOutput ,
115+       mergeErrIntoOut : Boolean ,
116+       timeout : Long ,
117+       check : Boolean ,
118+       propagateEnv : Boolean 
119+   ):  CommandResult  =  call(
120+     cwd,
121+     env,
122+     stdin,
123+     stdout,
124+     stderr,
125+     mergeErrIntoOut,
126+     timeout,
127+     check,
128+     propagateEnv,
129+     timeoutGracePeriod =  100 
130+   )
131+ 
98132  /**  
99133   * The most flexible of the [[os.proc ]] calls, `os.proc.spawn` simply configures 
100134   * and starts a subprocess, and returns it as a `java.lang.Process` for you to 
@@ -181,24 +215,31 @@ case class ProcGroup private[os] (commands: Seq[proc]) {
181215   * `call` provides a number of parameters that let you configure how the pipeline 
182216   * is run: 
183217   * 
184-    * @param  cwd               the working directory of the pipeline 
185-    * @param  env               any additional environment variables you wish to set in the pipeline 
186-    * @param  stdin             any data you wish to pass to the pipelines's standard input (to the first process) 
187-    * @param  stdout            How the pipelines's output stream is configured (the last process stdout) 
188-    * @param  stderr            How the process's error stream is configured (set for all processes) 
189-    * @param  mergeErrIntoOut   merges the pipeline's stderr stream into it's stdout. Note that then the 
190-    *                         stderr will be forwarded with stdout to subsequent processes in the pipeline. 
191-    * @param  timeout           how long to wait in milliseconds for the pipeline to complete 
192-    * @param  check             disable this to avoid throwing an exception if the pipeline 
193-    *                         fails with a non-zero exit code 
194-    * @param  propagateEnv      disable this to avoid passing in this parent process's 
195-    *                         environment variables to the pipeline 
196-    * @param  pipefail          if true, the pipeline's exitCode will be the exit code of the first 
197-    *                         failing process. If no process fails, the exit code will be 0. 
198-    * @param  handleBrokenPipe  if true, every [[java.io.IOException ]] when redirecting output of a process 
199-    *                         will be caught and handled by killing the writing process. This behaviour 
200-    *                         is consistent with handlers of SIGPIPE signals in most programs 
201-    *                         supporting interruptable piping. Disabled by default on Windows. 
218+    * @param  cwd                 the working directory of the pipeline 
219+    * @param  env                 any additional environment variables you wish to set in the pipeline 
220+    * @param  stdin               any data you wish to pass to the pipelines's standard input (to the first process) 
221+    * @param  stdout              How the pipelines's output stream is configured (the last process stdout) 
222+    * @param  stderr              How the process's error stream is configured (set for all processes) 
223+    * @param  mergeErrIntoOut     merges the pipeline's stderr stream into it's stdout. Note that then the 
224+    *                           stderr will be forwarded with stdout to subsequent processes in the pipeline. 
225+    * @param  timeout             how long to wait in milliseconds for the pipeline to complete 
226+    * @param  check               disable this to avoid throwing an exception if the pipeline 
227+    *                           fails with a non-zero exit code 
228+    * @param  propagateEnv        disable this to avoid passing in this parent process's 
229+    *                           environment variables to the pipeline 
230+    * @param  pipefail            if true, the pipeline's exitCode will be the exit code of the first 
231+    *                           failing process. If no process fails, the exit code will be 0. 
232+    * @param  handleBrokenPipe    if true, every [[java.io.IOException ]] when redirecting output of a process 
233+    *                           will be caught and handled by killing the writing process. This behaviour 
234+    *                           is consistent with handlers of SIGPIPE signals in most programs 
235+    *                           supporting interruptable piping. Disabled by default on Windows. 
236+    * @param  timeoutGracePeriod  if the timeout is enabled, how long in milliseconds for the 
237+    *                           subprocess to gracefully terminate before attempting to 
238+    *                           forcibly kill it 
239+    *                           (-1 for no kill, 0 for always kill immediately) 
240+    * 
241+    * @note  the issuing of `SIGTERM` instead of `SIGKILL` is implementation dependent on your JVM version. Pre-Java 9, no `SIGTERM` may be 
242+    *       issued. Check the documentation for your JDK's `Process.destroy`. 
202243   */  
203244  def  call (
204245      cwd : Path  =  null ,
@@ -211,7 +252,9 @@ case class ProcGroup private[os] (commands: Seq[proc]) {
211252      check : Boolean  =  true ,
212253      propagateEnv : Boolean  =  true ,
213254      pipefail : Boolean  =  true ,
214-       handleBrokenPipe : Boolean  =  ! isWindows
255+       handleBrokenPipe : Boolean  =  ! isWindows,
256+       //  this cannot be next to `timeout` as this will introduce a bin-compat break (default arguments are numbered in the bytecode)
257+       timeoutGracePeriod : Long  =  100 
215258  ):  CommandResult  =  {
216259    val  chunks  =  new  java.util.concurrent.ConcurrentLinkedQueue [Either [geny.Bytes , geny.Bytes ]]
217260
@@ -232,7 +275,7 @@ case class ProcGroup private[os] (commands: Seq[proc]) {
232275      pipefail
233276    )
234277
235-     sub.join(timeout)
278+     sub.join(timeout, timeoutGracePeriod )
236279
237280    val  chunksSeq  =  chunks.iterator.asScala.toIndexedSeq
238281    val  res  = 
@@ -241,6 +284,33 @@ case class ProcGroup private[os] (commands: Seq[proc]) {
241284    else  throw  SubprocessException (res)
242285  }
243286
287+   private [os] def  call (
288+       cwd : Path ,
289+       env : Map [String , String ],
290+       stdin : ProcessInput ,
291+       stdout : ProcessOutput ,
292+       stderr : ProcessOutput ,
293+       mergeErrIntoOut : Boolean ,
294+       timeout : Long ,
295+       check : Boolean ,
296+       propagateEnv : Boolean ,
297+       pipefail : Boolean ,
298+       handleBrokenPipe : Boolean 
299+   ):  CommandResult  =  call(
300+     cwd,
301+     env,
302+     stdin,
303+     stdout,
304+     stderr,
305+     mergeErrIntoOut,
306+     timeout,
307+     check,
308+     propagateEnv,
309+     pipefail,
310+     handleBrokenPipe,
311+     timeoutGracePeriod =  100 
312+   )
313+ 
244314  /**  
245315   * The most flexible of the [[os.ProcGroup ]] calls. It sets-up a pipeline of processes, 
246316   * and returns a [[ProcessPipeline ]] for you to interact with however you like. 
0 commit comments