source: main/trunk/gli/src/org/greenstone/gatherer/util/Readme_Using_SafeProcess.txt@ 31713

Last change on this file since 31713 was 31713, checked in by ak19, 7 years ago

Further filling in the sections of the Readme doc on SafeProcess

File size: 36.8 KB
Line 
1SAFEPROCESS README
2
3GS Java developers should use SafeProcess.java in place of directly using Java's Process class, unless any issues are discovered with SafeProcess.java hereafter.
4
5
6A tailored version of SafeProcess is found both in GLI and GS3's Java src code:
7- gli/src/org/greenstone/gatherer/util/SafeProcess.java
8- greenstone3/src/java/org/greenstone/util/SafeProcess.java
9
10_______________________________________________________________________________
11WHY WE SHOULD GO THROUGH SAFEPROCESS INSTEAD OF USING JAVA'S PROCESS DIRECTLY
12_______________________________________________________________________________
13
14It's easy to misuse Java's Process class, and this can result in unexpected behaviour and random errors that are hard to track down. Further, the older GLI and GS3 Java src code used to use Java's Process class in different and inconsistent ways, some worse than others. Going through one class, SafeProcess, provides consistency. So if it's buggy, you can fix it in one place.
15
16SafeProcess handles the internal Process' iostreams (input, output and error) properly using separate worker Threads, following http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2.
17
18SafeProcess handles this itself so that the code that uses SafeProcess doesn't need to deal with it, unless customising the processing of the Process' iostreams.
19
20
21
22_____________________________________________________
23MODEL OF SAFEPROCESS THREADS AND INTERRUPT BEHAVIOUR
24_____________________________________________________
25
26This section is especially IMPORTANT TO READ IF you're thinking of customising SafeProcess' handling of the internal Process' iostreams, since each of them are always handled in their own distinct worker threads, and you need to remember to deal with ThreadSafety issues.
27
28The primary process Thread:
29The main process internal to a SafeProcess instance is run in whatever thread the SafeProcess instance's runProcess() (or runBasicProcess()) methods are called.
30The SafeProcess instance internally keeps track of this thread, so that any cancel operation can send the InterruptedException to this primary thread.
31
32SafeProcess does NOT inherit from Thread, it is therefore not a Thread. It just runs its internal process in whatever Thread called SafeProcess's runProcess/runBasicProcess methods.
33
34A SafeProcess thread may further launch 0 or 3 additional worker threads, depending on whether runBasicProcess() or a runProcess() variant was called.
35
36In total, using SafeProcess involves
37- 1 thread (the primary thread) when runBasicProcess() is called
38- or 4 threads (the primary thread + 3 worker threads) when any variant of runBasicProcess() is called. If a variant of runProcess() was called, then 3 additional worker threads are spawned by SafeProcess. One for each iostream of the Process: inputstream, outputstream and errorstream. Note that the SafeProcess class will read from the internal Process' outputStream and ErrorStream, and can send/write a string to the Process' inputstream.
39
40
41 SafeProcess thread
42 |
43 |
44 ________________|_______________________________
45 | | | |
46 | | | |
47 | | | |
48 Worker Thread SafeProcess Worker Thread Worker Thread
49 for Process' Thread for Process' for Process'
50 InputStream (primary) OutputStream ErrorStream
51
52
53__________________________________________________
54USAGE: DEBUGGING
55__________________________________________________
56
57There's a lot of info that a SafeProcess instance will log during its execution and termination phases, including Exceptions but also very basic things, such as the command that the SafeProcess instance is running.
58
59By default, such debugging-related logging is turned off. You can turn it on by adjusting the static SafeProcess.DEBUG = 1 and recompiling.
60
61For GS3, the log output uses log4j.
62For GLI, it goes to System.err at present. But if you want it to go to GLI's DebugStream, uncomment the relevant lines in the 3 variants of the static log() functions in SafeProcess.java. Then recompile.
63
64If you're using SafeProcess yourself and wish to log debug statements that are related to how well you're using SafeProcess, you can call one of the static SafeProcess.log() functions yourself. Remember to set the static SafeProcess.DEBUG = 1.
65
66__________________________________________________
67USAGE: INSTANTIATION & DEFAULT RUNPROCESS VARIANTS
68__________________________________________________
69
70Usage of the SafeProcess class generally follows the following sequence:
71
721. instantiate a SafeProcess object using one of the constructors
73 - public SafeProcess(String[] cmd_args)
74 - public SafeProcess(String cmdStr)
75 - public SafeProcess(String[] cmd_args, String[] envparams, File launchDir)
76 Either or both of envparams and launchdir can be null.
77
78
792. optionally configure your SafeProcess instance.
80Use an appropriate setter method to set some additional fields:
81
82 - public void setInputString(String sendStr)
83 Call this if you wish to write any string to the process' inputstream
84
85 - public void setSplitStdOutputNewLines(boolean split)
86 - public void setSplitStdErrorNewLines(boolean split)
87 Pass in true to either of the above methods if you want to preserve individual lines in the content retrieved from the internal Process' stderr and stdoutput.
88 By default, lines are not split, so you get a String of one single line for the entire output from the Process' stdoutput, and a String of one single line from the Process' stderr output.
89
90
91 - public void setExceptionHandler(SafeProcess.ExceptionHandler exception_handler)
92 Use this to register a SafeProcess ExceptionHandler whose gotException() method will get called for each exception encountered during the *primary* thread's execution: the thread that runs the Process. If you wish to handle exceptions that could happen when reading from the Process' outputStream/errorStream, you will need to do that separately. See the section CUSTOMISING. By default, exceptions when reading from Process' output and errorStreams are logged.
93
94 - public void setMainHandler(SafeProcess.MainProcessHandler handler)
95 To set a handler that will handle the primary (SafeProcess) thread, your handler will need to implement the hooks that will get called during the internal Process' life cycle, such as before and after process.destroy() is called upon a cancel operation.
96
97
983. call one of the following runProcess() variants on your SafeProcess instance:
99
100 a. public int runBasicProcess()
101
102This variant will not handle the Process' iostreams at all, so it won't launch any worker threads. It merely runs the internal process in the thread from which it is called (the primary thread) and waits until it's done.
103Use this variant if you know that the external process you wish SafeProcess to run will NOT be expecting any input nor be producing any output (in stdout or stderror).
104
105NOTE: Do not call runBasicProcess() if you merely wish to ignore the process' iostreams. Because, in Java 6 and earlier, this can block indefinitely if any of the Process' iostreams contain anything or expect anything. If you wish to ignore a process' iostreams, or if you're not sure if they may need handling, call the zero-argument runProcess() variant described below.
106
107 b. public int runProcess()
108
109This zero argument variant will handle all the Process' iostreams in the DEFAULT manner.
110
111- If you wanted to send something to the Process' inputstream, you ought to have configured this by calling setInputString(sendStr) before calling runProcess(). The DEFAULT behaviour is for sendStr to be written to the internal Process' inputstream as soon as the process runs (happens in its own thread).
112
113- once runProcess() finishes, you can inspect what had come out of the Process' output and error streams by calling, see point 4 below.
114The DEFAULT behaviour of SafeProcess' processing of the stdout and stderr streams is for both streams to be read one line at a time until they have nothing left. Note that both streams are always dealt with in their separate worker threads.
115
116Resources are safely allocated at the end of the worker threads dealing with each of the internal Process' iostream, regardless of whether the Process is allowed to terminate naturally or whether SafeProcess is instructed to prematurely terminate it.
117
118 c. public int runProcess(SafeProcess.LineByLineHandler outLineByLineHandler, SafeProcess.LineByLineHandler errLineByLineHandler)
119Use this variant if you want to do anything specific when each line comes in from the internal Process' stderr and stdout streams. Passing in null for either parameter will return to the default behaviour for that stream.
120You'll want to read further details in the section on CUSTOMISING.
121
122 d. public int runProcess(CustomProcessHandler procInHandler, CustomProcessHandler procOutHandler, CustomProcessHandler procErrHandler)
123Use this variant if you want to completely override the way the internal Process' iostreams are handled. Passing in null for any of the parameters will return to the default behaviour for that stream. You'll want to read further details in the section on CUSTOMISING.
124
125
126
127If you want to completely override the default behaviour of any of SafeProcess' iostream related worker threads (such as if you want to read a char at a time from the stderr stream and do something, instead of the default behaviour of reading a line at a time from it), then call this method.
128
129You need to pass in an *instance* of SafeProcess.CustomProcessHandler for *each* of the 3 iostreams, since they're running in separate threads and so can't share the same instance.
130
131You can pass in null for any of these, if you want the default SafeProcess handling to apply for that stream's worker thread. For any iostream's default handling that you want to override, however, you'd extend SafeProcess.CustomProcessHandler.
132
133You would need to take care of ThreadSafety yourself if you're extending a SafeProcess.CustomProcessHandler. You can maintain concurrency by using synchronization, for instance. See the section USEFUL SYNCHRONISATION NOTES.
134
135For EXAMPLES OF USE, see GS3's GS2PerlConstructor.java and GLI's DownloadJob.java.
136
137
138Further NOTES:
139All of the variants of the runProcess methods return the internal Process' exit value *after* the internal Process has completed. The exit value will be -1 if the process was prematurely terminated such as by a cancel operation, which would have interrupted the primary and subsidiary threads.
140
141Running a process blocks the primary thread until complete. So all the runProcess variants above *block*. (Unless and until an external thread calls the cancelRunningProcess() method on a reference to the SafeProcess instance, discussed in the section on CANCELLING).
142
143
1444. if necessary, once the process has *terminated*,
145
146- you can read whatever came out from the Process' stderr or stdout by calling whichever is required of:
147public String getStdOutput()
148public String getStdError()
149
150If you want to do anything special when handling any of the Process' iostreams, see the CUSTOMISING section.
151
152- You can also inspect the exit value of running the process by calling
153public int getExitValue()
154
155The exit value will be -1 if the process was prematurely terminated such as by a cancel operation, which would have interrupted the primary and subsidiary threads.
156The exit value will also be -1 if you call getExitValue() before setting off the SafeProcess via any of the runProcess() variants.
157
158_________________________________________________________________________________________________________________
159INTERNAL IMPLEMENTATION DETAILS - USEFUL IF CUSTOMISING SAFEPROCESS' BEHAVIOUR OR CALLING CANCEL ON SAFEPROCESS
160_________________________________________________________________________________________________________________
161
162This section is for understanding how SafeProcess runs a Java Process internally. These methods are not public and their details are hidden away in the code.
163
164Calling any of the runProcess() variants does the following internally:
165
1661. calls method doRuntimeExec()
167
168 based on how you configured your SafeProcess instance, this method instantiates and returns a Process object by calling Runtime.exec().
169 A possible improvement may be to consider using the ProcessBuilder class for creating a Process object.
170
1712. calls method waitForStreams()
172
173 Called by all but the runBasicProcess() variant, since the runBasicProcess() variant does not do anything with the Process' iostreams and therefore doesn't use worker threads .
174 This method first starts off all the worker threads, to handle each input or output stream of the Process.
175 Then, it calls waitFor() on the Process object. This is the first of 2 calls that block the primary thread (the Thread the Safeprocess runs its Process object in) until finished.
176
177 One of two things happen,
178 - either the internal Process is allowed to terminate naturally, reaching the end.
179 - Or the Process is interrupted by a cancel action, which will result in an InterruptedException. When an InterruptedException is caught, each of the worker threads is interrupted. The worker threads check for the interrupt flag during any loop, so that they may exit their loop early. If an InterruptException happened, the exitValue after the Process' waitFor() call remains at -1 and the SafeProcess instance sets its boolean forciblyTerminateProcess flag to true, to indicate that it may have to call Process.destroy() and ensure any subprocesses are terminated.
180
181 Regardless of whether the Process naturally terminates or is prematurely terminated by an InterruptedException upn a cancel,
182 - all the resources such as streams are closed.
183 - the join() method is called on all worker threads. As per the Java API, callin join() on a Thread "Waits for this thread to die." This means the primary thread now waits for the worker threads to all come to a halt. So the section where join() is called on each worker thread also blocks the primary thread until the joins are complete.
184
185Since both the Process.waitFor() phase and the join() phase block the primary thread until finished, an InterruptedException could theoretically have occurred during either phase. However, we don't want an InterruptedException occurring during the join() phase, as an interrupt at this point will break us out of the join (going into an exception handler for the Interruption, prematurely ending the join()) and thus the worker threads would have been prevented from neatly cleaning up after themselves. To prevent InterruptedExceptions from occurring during any of the join() calls on the worker threads, the join() calls are embedded in a section that is marked as not interruptible. Once the join calls are finished, the code in the waitForStreans() method becomes interruptible again and a notify() is sent to anyone that was waiting for the thread to become interruptible. (Technically, the remainder of the code, the rest of the primary thread, does not respond to InterruptedExceptions hereafter, as there are no more waiting/blocking calls after the join()s are over.)
186
187Marking the join() phase as an uninterruptible section of code helps when the cancelRunProcess() is called on the SafeProcess instance by a separate thread, such as a GUI thread with a cancel button that triggered the call to cancelRunProcess(). The cancelRunProcess() method only interrupts the primary SafeProcess thread if it is in an interruptible section of code. If the primary thread is uninterruptible when cancelRunProcess() was called, then the method either
188- returns immediately if called by a GUI thread (EventDispatchThread),
189- or if cancelRunProcess(boolean forceWaitUntilInterruptible) is called with forceWaitUntilInterruptible, even if the caller is the EventDispatchThread, it will wait until the primary thread is interruptible before it returns from the cancelRunProcess() method. The wait() call blocks until notify()ed that the primary thread has become interruptible again.
190
191The cancelRunningProcess() method, which is called by a Thread external to the primary SafeProcess thread, therefore only causes an InterruptedException on the primary SafeProcess thread. It never sends an interruption during or after the primary thread is in an uninterruptible section, since only the waitFor() phase is ever to be interrupted, as waitFor() is the only phase when the external process is actually running, while the subsequent phases are already in closing down naturally and only need to do cleanup thereafter. If cancelRunningProcess() is called when the primary thread is in the uninterruptible phase after waitFor(), the cancelRunningProcess() method returns straight away or can be told to wait until the uninterupptible phase is done. Either way, it won't cause an interrupt any more, as the process is already naturally closing down.
192
193
1943. If and only if SafeProcess was cancelled, so that its Process is to be prematurely terminated, does SafeProcess call destroyProcess(). (It is not called on natural termination.) destroyProcess() ensures both the external process that is run, denoted by the Process object, and also any subprocesses that are launched by the external process are all terminated.
195
196- On Linux (only tested on Ubuntu), it was found that the simple sequence of events from interrupting the worker threads to the join()s called on them (which closed off al resources used by the worker threads) was sufficient to cleanly end the Process and any subprocesses.
197
198- On Windows, it needs to call Process.destroy() to terminate the Process and further needs to obtain the childids of any subprocesses this launched and terminate them recursively in a Windows-specific manner by calling the Windows specific kill command on each process id.
199The method "public static long getProcessID(Process p)" uses the newly included Java Native Access library to obtain the processID of any Process. Then the childpids of any process denoted by pid are obtained with the command:
200
201 "wmic process where (parentprocessid=<pid>) get processid"
202
203Having the pids of all processes to be terminated, one of "wmic process <pid> delete", "taskkill /f /t /PID <pid>" or "tskill pid" commands is invoked, whichever is available, to recursively and forcibly terminate any process and its subprocesses, by their pid. The taskkill command works like kill -9 (kill -KILL) rather than like kill -TERM.
204
205The wmic prompt-like tool, which has been present since Windows XP, not only recongises the command to delete a process by pid, but it also has commands to get the childids of a process denoted by pid, and vice-versa (to find the parent pid if you know a childpid). Much more is possible with wmic. The tskill command has also been available for a long time, taskkill is newer and may have appeared with Windows Vista or later.
206
207- On Mac, pressing cancel when building a collection in GLI launches the perl processes full-import.pl followed by full-buildcol.pl. The buildcol phase terminates even without a process.destroy() being necessary, which is as on Linux. However, for full-import.pl, process.destroy() firstly is necessary: as always, process.destroy() is able to destroy the Process that was launched (full-import.pl), but not any subprocesses that this launched in turn (such as import.pl). So on Mac, during destroyProcess(), an operating system command is issued to terminate the process and its subprocesses. See the NOTE at the end of the section.
208
209On Linux, SafeProcess() does not issue operating system commands to terminate the internal Process upon cancel, since on Linux both a process and its subprocesses seem to come to an end on their own in such a case. Nevertheless, SafeProcess now also includes code to send an OS level command to terminate a process with its subprocesses on Linux too. The way SafeProcess works on premature termination, with interrupts and closing of resources, doesn't yet require an OS level command. However, there's now a variant of destroyProcess() which takes a boolean parameter which, if false, will force an OS level command to be issued on Linux too if an entire process group (process and subprocesses) is to be destroyed.
210
211On Mac, the command is "pkill -TERM -P <pid>". This is to kill a process and subprocesses. It was tested successfully on Mac Snow Leopard.
212
213However, on Linux, this command only terminates the top level process, while any subprocesses are left running. On Linux, the OS level command to terminate the entire process group is "kill -TERM -<pid>", which has been tested successfully on Ubuntu. Note the hyphen in front of the <pid>, which treats the pid as a process group id, and terminates the entire process tree. (In contrast, the more common "kill -TERM <pid>" only terminates the top level process on Mac and Linux, leaving subprocesses running).
214
215For more information, see https://stackoverflow.com/questions/8533377/why-child-process-still-alive-after-parent-process-was-killed-in-linux
216
217NOTE:
218This note documents weird behaviour on Mac, which has not been explained yet, but which the above operating system commands to terminate the process group successfully taken care of anyway.
219
220When running the full-import.pl and full-buildcol.pl in sequence from the command line on a Mac, the regular "kill -TERM <pid> signal" only terminates the top level process and not subprocesses. To kill the entire process group, the OS specific command for killing a process (mentioned above) must be issued instead.
221When full-import.pl is run from GLI, both process.destroy() and the regular "kill -TERM <pid> signal" are able to successfully terminate the top level process, but not subprocesses. In this, sending the TERM signal results in behaviour similar to the situation with running full-import.pl from the command line.
222
223However, when full-buildcol.pl is run from GLI on a Mac the behaviour is inconsistent with the command line and with running full-import.pl from GLI. When full-buildcol.pl is run from GLI, the Process and subprocesses all terminate tidily on their own when cancel is pressed, not requiring either process.destroy() or the OS command to terminate even the top level process ID, nor requiring the command to terminate the process group either. This behaviour is similar to how both full-import.pl and full-buildcol.pl scripts come to a clean close upon cancel on Linux. In such cases, sending a TERM signal to the process (group) id returns an exitvalue of 1. This exitvalue can mean that the kill signal didn't work, but in these cases, it specifically indicates that the process was already terminated before the TERM signal was even sent to kill the process.
224
225A specific detail of the process of killing a process group on Linux and Mac is that a TERM signal is sent first. If the return value was 0, a signal was successfully sent. If it is 1 it failed in some way, including the possibility that process was already terminated. If neither of these, then it's assumed the return value indicates the termination signal was unsuccessful, and a KILL signal is sent next.
226
227Preliminary investigation of the weird behaviour on Mac has revealed the following:
228a. Sending a Ctrl-C is different in two ways from sending a kill -TERM signal.
229- Ctrl-C is "kill -2" or "kill -INT", whereas "kill -TERM" is the same as "kill -15" (and "kill -KILL" is "kill -9", which doesn't allow the killed process to do cleanup first).
230- Ctrl-C kills the foreground process of the progress group targeted by the Ctrl-C (or the process group indicated by pid). In contrast, kill -TERM or kill -KILL terminate the process with the given pid, which is likely to be a background process if any subprocesses were launched by it.
231
232b. Pressing Ctrl-C when full-import.pl is running may give the impression of having terminated the entire process group if you happen to issue the Ctrl-C at a point when full-import.pl hasn't launched any subprocesses. The parent process is killed, and therefore can't launch further subprocesses, so it looks like hte entire process tree was successfully terminated. However, if you issue Ctrl-C when a subprocess is running, that subprocess (the foreground process) is all that's terminated, while background processes are still running and can continue to launch subprocesses.
233
234Sending a regular kill -TERM or kill -KILL signal to the parent process of a process group will kill the parent process (but not the process group, unless you explicitly issued the command to terminate an entire process group). Killing the parent process will prevent future subprocesses from being launched by the parent process, but child processes remain runnning.
235
236The Java method Process.destroy(), used by GLI until the recent modifications to ensure the entire process group is terminated, behaves more like kill -TERM/kill -KILL than Ctrl-C: the actuall parent Process launched with Runtime.exec() is killed, but not subprocesses. This means that Ctrl-C is not a proper test of GLI's behaviour.
237
238Future investigation into the weird behaviour, if this ever needs to be explained, can proceed from the above. At present. issuing operating system commands to kill an entire process tree seem to work sufficiently in all cases tested, despite the weird distinctive behaviour of full-buildcol.pl on a Mac when run from GLI.
239
240__________________________________________________
241USAGE: CUSTOMISING
242__________________________________________________
243
244This section deals with if you want to provide customise behaviour for any of the worker threads that deal with the internal Process object's iostreams, or if you want to do some extra work at any major milestone in the outer SafeProcess instance's life cycle.
245
246
247CUSTOMISING THE BEHAVIOUR OF THE WORKER THREADS THAT HANDLE THE INTERNAL PROCESS' IOSTREAMS:
248
249Always bear in mind that each of the streams is handled in its own separate worker thread!
250So you'll need to take care of ThreadSafety yourself, if you're implementing a SafeProcess.LineByLineHandler or SafeProcess.CustomProcessHandler. You can maintain concurrency by using synchronization, for instance. See the section USEFUL SYNCHRONISATION NOTES.
251
252Customising the worker threads can be done in one of two ways:
253- you extend SafeProcess.LineByLineHandler (for the Process' stderr/stdout streams you're interested in) and then call the matching runProcess() method
254- if you want to do something drastically different, you extend SafeProcess.CustomProcessHandler (for any of the internal Process' iostreams you're interested in) and then call the matching runProcess() method
255
256 a. public int runProcess(SafeProcess.LineByLineHandler outLineByLineHandler, SafeProcess.LineByLineHandler errLineByLineHandler)
257
258If you want to deal with EACH LINE coming out of the internal Process' stdout and/or stderr streams yourself, implement SafeProcess.LineByLineHandler. You may want a different implementation if you want to deal with the Process' stdout and stderr streams differently, or the same implementation if you want to deal with them identically. Either way, you need a *separate* SafeProcess.LineByLineHandler instance for each of these two streams, since they're running in separate threads. Pass the distinct instances in to the runProcess method as parameters.
259
260You can pass null for either parameter, in which case the default behaviour for that iostream takes place, as described for the zero-argument runProcess() variant.
261
262
263Writing your own SafeProcess.LineByLineHandler:
264- extend SafeProcess.LineByLineHandler. You can make it a static inner class where you need it.
265- make the constructor call the super constructor, passing in one of SafeProcess.STDERR|STDOUT|STDIN, to indicate the specific iostream for any instantiated instance.
266- For debugging, in order to identify what stream (stderr or stdout) you're working with, you can print the thread's name by calling the CustomProcessHandler's method
267 public String getThreadNamePrefix()
268- Provide an implementation for the LineByLineHandler methods
269 public void gotLine(String line)
270 public void gotException(Exception e);
271
272Whenever the worker thread gets a line from that outputstream (stderr or stdout) of the internal process, it will call that stream's associated gotLine(line) implemention.
273Whenever the worker thread encounters an exception during the processing of that outputstream (stderr or stdout), it will call that stream's associated gotException(exception) implemention.
274
275Remember, you can use the same implementation (LineByLineHandler subclass) for both stderr and stdout outputstreams, but you must use different *instances* of the class for each stream!
276
277For EXAMPLES OF USE, see GLI's GShell.java.
278
279
280 b. public int runProcess(CustomProcessHandler procInHandler, CustomProcessHandler procOutHandler, CustomProcessHandler procErrHandler)
281
282If you want to completely override the default behaviour of any of SafeProcess' iostream related worker threads (such as if you want to read a char at a time from the stderr stream and do something, instead of the default behaviour of reading a line at a time from it), then call this method.
283
284You need to pass in an *instance* of SafeProcess.CustomProcessHandler for *each* of the 3 iostreams, since they're running in separate threads and so can't share the same instance.
285
286You can pass in null for any of these, if you want the default SafeProcess handling to apply for that stream's worker thread. For any iostream's default handling that you want to override, however, you'd implement SafeProcess.CustomProcessHandler.
287
288
289Writing your own SafeProcess.CustomProcessHandler:
290- extend SafeProcess.CustomProcessHandler. You can make it a static inner class where you need it.
291- make the constructor call the super constructor, passing in one of SafeProcess.STDERR|STDOUT|STDIN, to indicate the specific iostream for any instantiated instance.
292- implement the following method of CustomProcessHandler
293 public abstract void run(Closeable stream);
294
295Start by first casting the stream to InputStream, if the CustomProcessHandler is to read from the internal Process' stderr or stdout streams,
296else cast to Outputstream if CustomProcessHandler is to write to the internal Process' stdin stream.
297- Since you're implementing the "run()" method of the worker Thread for that iostream, you will write the code to handle any exceptions yourself
298- For debugging, in order to identify what stream you're working with, you can print the thread's name by calling the CustomProcessHandler's method
299 public String getThreadNamePrefix()
300
301Remember, you can use the same implementation (CustomProcessHandler subclass) for each of the internal process' iostreams, but you must use different *instances* of the class for each stream!
302
303For EXAMPLES OF USE, see GS3's GS2PerlConstructor.java and GLI's DownloadJob.java.
304
305
306ADDING CUSTOM HANDLERS TO HOOK INTO KEY MOMENTS OF THE EXECUTION OF THE PRIMARY THREAD:
307
308Remember that the primary thread is the thread in which the internal Process is executed in, which is whatever Thread the runProcess() method is called on your SafeProcess instance.
309
310If you just want to handle exceptions that may occur at any stage during the execution of the primary thread
311- provide an implementation of SafeProcess.ExceptionHandler:
312 public static interface ExceptionHandler {
313
314 public void gotException(Exception e);
315 }
316- configure your SafeProcess instance by calling setExceptionHandler(SafeProcess.ExceptionHandler eh) on it *before* calling a runProcess() method on that instance.
317- For EXAMPLES OF USE, see GLI's GShell.java.
318
319If, during the execution of the primary thread, you want to do some complex things during a cancel operation, such as or before and after a process is destroyed, then
320- implement SafeProcess.MainProcessHandler (see below)
321- configure your SafeProcess instance by calling setMainHandler(MainProcessHandler mph) on it *before* calling a runProcess() method on that instance.
322- For EXAMPLES OF USE, see GLI's DownloadJob.java.
323
324 public static interface MainProcessHandler {
325
326 /**
327 * Called before the streamgobbler join()s.
328 * If not overriding, the default implementation should be:
329 * public boolean beforeWaitingForStreamsToEnd(boolean forciblyTerminating) { return forciblyTerminating; }
330 * When overriding:
331 * @param forciblyTerminating is true if currently it's been decided that the process needs to be
332 * forcibly terminated. Return false if you don't want it to be. For a basic implementation,
333 * return the parameter.
334 * @return true if the process is still running and therefore still needs to be destroyed, or if
335 * you can't determine whether it's still running or not. Process.destroy() will then be called.
336 * @return false if the process has already naturally terminated by this stage. Process.destroy()
337 * won't be called, and neither will the before- and after- processDestroy methods of this class.
338 */
339 public boolean beforeWaitingForStreamsToEnd(boolean forciblyTerminating);
340
341 /**
342 * Called after the streamgobbler join()s have finished.
343 * If not overriding, the default implementation should be:
344 * public boolean afterStreamsEnded(boolean forciblyTerminating) { return forciblyTerminating; }
345 * When overriding:
346 * @param forciblyTerminating is true if currently it's been decided that the process needs to be
347 * forcibly terminated. Return false if you don't want it to be. For a basic implementation,
348 * return the parameter (usual case).
349 * @return true if the process is still running and therefore still needs to be destroyed, or if
350 * can't determine whether it's still running or not. Process.destroy() will then be called.
351 * @return false if the process has already naturally terminated by this stage. Process.destroy()
352 * won't be called, and neither will the before- and after- processDestroy methods of this class.
353 */
354 public boolean afterStreamsEnded(boolean forciblyTerminating);
355
356 /**
357 * called after join()s and before process.destroy()/destroyProcess(Process), iff forciblyTerminating
358 */
359 public void beforeProcessDestroy();
360
361 /**
362 * Called after process.destroy()/destroyProcess(Process), iff forciblyTerminating
363 */
364 public void afterProcessDestroy();
365
366 /**
367 * Always called after process ended: whether it got destroyed or not
368 */
369 public void doneCleanup(boolean wasForciblyTerminated);
370 }
371
372
373__________________________________________________
374USAGE: CANCELLING
375__________________________________________________
376
377For EXAMPLES OF USE, see GLI's GShell.java and DownloadJob.java.
378
379__________________________________________________
380USAGE: IMPLEMENTING HOOKS AROUND CANCELLING
381__________________________________________________
382
383For EXAMPLES OF USE, see GLI's DownloadJob.java.
384
385__________________________________________________
386USEFUL STATIC METHODS AND STATIC INNER CLASSES
387__________________________________________________
388
389public static boolean isAvailable(String programName)
390 - Runs the `which` cmd over the program and returns true or false
391 `which` is included in winbin for Windows, and is part of unix systems
392
393static public boolean processRunning(Process process)
394 - returns true if the Process is currently running and hasn't come to an end.
395 - It does not
396
397public static long getProcessID(Process p)
398 - uses java native access (JNA, version 4.1.0 from 2013). The JNA jar files have been included into gli's lib and GS3's lib.
399 - For more details, see gli/lib/README.txt, section "B. THE jna.jar and jna-platform.jar FILES"
400
401public static void destroyProcess(Process p)
402 - will terminate any subprocesses launched by the Process p
403 - uses java native access to get processid
404 - uses OS system calls to terminate the Process and any subprocesses
405
406public static boolean closeProcess(Process prcs)
407 - will attempt to close your Process iostreams and destroy() the Process object at the end.
408
409public static boolean closeResource(Closeable resourceHandle)
410 - will attempt to cleanly close your resource (file/stream handle), logging on Exception
411
412public static boolean closeSocket(Socket resourceHandle)
413 - will attempt to cleanly close your Socket, logging on Exception. In Java 6, Sockets didn't yet implement Closeable,
414 so this method is to ensure backwards compatibility
415
416public static class OutputStreamGobbler extends Thread
417 - A class that can write to a Process' inputstream from its own Thread.
418
419public static class InputStreamGobbler extends Thread
420 - Class that can read from a Process' output or error stream in its own Thread.
421 - A separate instance should be created for each stream, so that each stream has its own Thread.
422
423
424
425Package access:
426static methods to prematurely terminate any process denoted by processID and any subprocesses it may have launched:
427 static boolean killUnixProcessWithID(long processID)
428 static void killWinProcessWithID(long processID)
429
430
431__________________________________________________
432OTHER USEFUL INSTANCE METHODS
433__________________________________________________
434
435public synchronized boolean processRunning()
436 - returns true if the SafeProcess instance has started its internal Process or is still cleaning up on its termination
437
438public boolean cancelRunningProcess()
439 - cancel the SafeProcess instance
440 - untimately synchronized, so threadsafe
441__________________________________________________
442USEFUL SYNCHRONISATION NOTES
443__________________________________________________
444[Also add in links]
445
446
447__________________________________________________
448TICKETS, COMMITS, TAGS
449__________________________________________________
450
451
Note: See TracBrowser for help on using the repository browser.