Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | :mod:`mozprocess` --- Launch and manage processes |
michael@0 | 2 | ================================================= |
michael@0 | 3 | |
michael@0 | 4 | Mozprocess is a process-handling module that provides some additional |
michael@0 | 5 | features beyond those available with python's subprocess: |
michael@0 | 6 | |
michael@0 | 7 | * better handling of child processes, especially on Windows |
michael@0 | 8 | * the ability to timeout the process after some absolute period, or some |
michael@0 | 9 | period without any data written to stdout/stderr |
michael@0 | 10 | * the ability to specify output handlers that will be called |
michael@0 | 11 | for each line of output produced by the process |
michael@0 | 12 | * the ability to specify handlers that will be called on process timeout |
michael@0 | 13 | and normal process termination |
michael@0 | 14 | |
michael@0 | 15 | Running a process |
michael@0 | 16 | ----------------- |
michael@0 | 17 | |
michael@0 | 18 | mozprocess consists of two classes: ProcessHandler inherits from ProcessHandlerMixin. |
michael@0 | 19 | |
michael@0 | 20 | Let's see how to run a process. |
michael@0 | 21 | First, the class should be instanciated with at least one argument which is a command (or a list formed by the command followed by its arguments). |
michael@0 | 22 | Then the process can be launched using the *run()* method. |
michael@0 | 23 | Finally the *wait()* method will wait until end of execution. |
michael@0 | 24 | |
michael@0 | 25 | .. code-block:: python |
michael@0 | 26 | |
michael@0 | 27 | from mozprocess import processhandler |
michael@0 | 28 | |
michael@0 | 29 | # under Windows replace by command = ['dir', '/a'] |
michael@0 | 30 | command = ['ls', '-l'] |
michael@0 | 31 | p = processhandler.ProcessHandler(command) |
michael@0 | 32 | print("execute command: %s" % p.commandline) |
michael@0 | 33 | p.run() |
michael@0 | 34 | p.wait() |
michael@0 | 35 | |
michael@0 | 36 | Note that using *ProcessHandler* instead of *ProcessHandlerMixin* will print the output of executed command. The attribute *commandline* provides the launched command. |
michael@0 | 37 | |
michael@0 | 38 | Collecting process output |
michael@0 | 39 | ------------------------- |
michael@0 | 40 | |
michael@0 | 41 | Let's now consider a basic shell script that will print numbers from 1 to 5 waiting 1 second between each. |
michael@0 | 42 | This script will be used as a command to launch in further examples. |
michael@0 | 43 | |
michael@0 | 44 | **proc_sleep_echo.sh**: |
michael@0 | 45 | |
michael@0 | 46 | .. code-block:: sh |
michael@0 | 47 | |
michael@0 | 48 | #!/bin/sh |
michael@0 | 49 | |
michael@0 | 50 | for i in 1 2 3 4 5 |
michael@0 | 51 | do |
michael@0 | 52 | echo $i |
michael@0 | 53 | sleep 1 |
michael@0 | 54 | done |
michael@0 | 55 | |
michael@0 | 56 | If you are running under Windows, you won't be able to use the previous script (unless using Cygwin). |
michael@0 | 57 | So you'll use the following script: |
michael@0 | 58 | |
michael@0 | 59 | **proc_sleep_echo.bat**: |
michael@0 | 60 | |
michael@0 | 61 | .. code-block:: bat |
michael@0 | 62 | |
michael@0 | 63 | @echo off |
michael@0 | 64 | FOR %%A IN (1 2 3 4 5) DO ( |
michael@0 | 65 | ECHO %%A |
michael@0 | 66 | REM if you have TIMEOUT then use it instead of PING |
michael@0 | 67 | REM TIMEOUT /T 1 /NOBREAK |
michael@0 | 68 | PING -n 2 127.0.0.1 > NUL |
michael@0 | 69 | ) |
michael@0 | 70 | |
michael@0 | 71 | Mozprocess allows the specification of custom output handlers to gather process output while running. |
michael@0 | 72 | ProcessHandler will by default write all outputs on stdout. You can also provide (to ProcessHandler or ProcessHandlerMixin) a function or a list of functions that will be used as callbacks on each output line generated by the process. |
michael@0 | 73 | |
michael@0 | 74 | In the following example the command's output will be stored in a file *output.log* and printed in stdout: |
michael@0 | 75 | |
michael@0 | 76 | .. code-block:: python |
michael@0 | 77 | |
michael@0 | 78 | import sys |
michael@0 | 79 | from mozprocess import processhandler |
michael@0 | 80 | |
michael@0 | 81 | fd = open('output.log', 'w') |
michael@0 | 82 | |
michael@0 | 83 | def tostdout(line): |
michael@0 | 84 | sys.stdout.write("<%s>\n" % line) |
michael@0 | 85 | |
michael@0 | 86 | def tofile(line): |
michael@0 | 87 | fd.write("<%s>\n" % line) |
michael@0 | 88 | |
michael@0 | 89 | # under Windows you'll replace by 'proc_sleep_echo.bat' |
michael@0 | 90 | command = './proc_sleep_echo.sh' |
michael@0 | 91 | outputs = [tostdout, tofile] |
michael@0 | 92 | |
michael@0 | 93 | p = processhandler.ProcessHandlerMixin(command, processOutputLine=outputs) |
michael@0 | 94 | p.run() |
michael@0 | 95 | p.wait() |
michael@0 | 96 | |
michael@0 | 97 | fd.close() |
michael@0 | 98 | |
michael@0 | 99 | The process output can be saved (*obj = ProcessHandler(..., storeOutput=True)*) so as it is possible to request it (*obj.output*) at any time. Note that the default value for *stroreOutput* is *True*, so it is not necessary to provide it in the parameters. |
michael@0 | 100 | |
michael@0 | 101 | .. code-block:: python |
michael@0 | 102 | |
michael@0 | 103 | import time |
michael@0 | 104 | import sys |
michael@0 | 105 | from mozprocess import processhandler |
michael@0 | 106 | |
michael@0 | 107 | command = './proc_sleep_echo.sh' # Windows: 'proc_sleep_echo.bat' |
michael@0 | 108 | |
michael@0 | 109 | p = processhandler.ProcessHandler(command, storeOutput=True) |
michael@0 | 110 | p.run() |
michael@0 | 111 | for i in xrange(10): |
michael@0 | 112 | print(p.output) |
michael@0 | 113 | time.sleep(0.5) |
michael@0 | 114 | p.wait() |
michael@0 | 115 | |
michael@0 | 116 | In previous example, you will see the *p.output* list growing. |
michael@0 | 117 | |
michael@0 | 118 | Execution |
michael@0 | 119 | --------- |
michael@0 | 120 | |
michael@0 | 121 | Status |
michael@0 | 122 | `````` |
michael@0 | 123 | |
michael@0 | 124 | It is possible to query the status of the process via *poll()* that will return None if the process is still running, 0 if it ended without failures and a negative value if it was killed by a signal (Unix-only). |
michael@0 | 125 | |
michael@0 | 126 | .. code-block:: python |
michael@0 | 127 | |
michael@0 | 128 | import time |
michael@0 | 129 | import signal |
michael@0 | 130 | from mozprocess import processhandler |
michael@0 | 131 | |
michael@0 | 132 | command = './proc_sleep_echo.sh' |
michael@0 | 133 | p = processhandler.ProcessHandler(command) |
michael@0 | 134 | p.run() |
michael@0 | 135 | time.sleep(2) |
michael@0 | 136 | print("poll status: %s" % p.poll()) |
michael@0 | 137 | time.sleep(1) |
michael@0 | 138 | p.kill(signal.SIGKILL) |
michael@0 | 139 | print("poll status: %s" % p.poll()) |
michael@0 | 140 | |
michael@0 | 141 | Timeout |
michael@0 | 142 | ``````` |
michael@0 | 143 | |
michael@0 | 144 | A timeout can be provided to the *run()* method. If the process last more than timeout seconds, it will be stopped. |
michael@0 | 145 | |
michael@0 | 146 | After execution, the property *timedOut* will be set to True if a timeout was reached. |
michael@0 | 147 | |
michael@0 | 148 | It is also possible to provide functions (*obj = ProcessHandler[Mixin](..., onTimeout=functions)*) that will be called if the timeout was reached. |
michael@0 | 149 | |
michael@0 | 150 | .. code-block:: python |
michael@0 | 151 | |
michael@0 | 152 | from mozprocess import processhandler |
michael@0 | 153 | |
michael@0 | 154 | def ontimeout(): |
michael@0 | 155 | print("REACHED TIMEOUT") |
michael@0 | 156 | |
michael@0 | 157 | command = './proc_sleep_echo.sh' # Windows: 'proc_sleep_echo.bat' |
michael@0 | 158 | functions = [ontimeout] |
michael@0 | 159 | p = processhandler.ProcessHandler(command, onTimeout=functions) |
michael@0 | 160 | p.run(timeout=2) |
michael@0 | 161 | p.wait() |
michael@0 | 162 | print("timedOut = %s" % p.timedOut) |
michael@0 | 163 | |
michael@0 | 164 | By default the process will be killed on timeout but it is possible to prevent this by setting *kill_on_timeout* to *False*. |
michael@0 | 165 | |
michael@0 | 166 | .. code-block:: python |
michael@0 | 167 | |
michael@0 | 168 | p = processhandler.ProcessHandler(command, onTimeout=functions, kill_on_timeout=False) |
michael@0 | 169 | p.run(timeout=2) |
michael@0 | 170 | p.wait() |
michael@0 | 171 | print("timedOut = %s" % p.timedOut) |
michael@0 | 172 | |
michael@0 | 173 | In this case, no output will be available after the timeout, but the process will still be running. |
michael@0 | 174 | |
michael@0 | 175 | Waiting |
michael@0 | 176 | ``````` |
michael@0 | 177 | |
michael@0 | 178 | It is possible to wait until the process exits as already seen with the method *wait()*, or until the end of a timeout if given. Note that in last case the process is still alive after the timeout. |
michael@0 | 179 | |
michael@0 | 180 | .. code-block:: python |
michael@0 | 181 | |
michael@0 | 182 | command = './proc_sleep_echo.sh' # Windows: 'proc_sleep_echo.bat' |
michael@0 | 183 | p = processhandler.ProcessHandler(command) |
michael@0 | 184 | p.run() |
michael@0 | 185 | p.wait(timeout=2) |
michael@0 | 186 | print("timedOut = %s" % p.timedOut) |
michael@0 | 187 | p.wait() |
michael@0 | 188 | |
michael@0 | 189 | Killing |
michael@0 | 190 | ``````` |
michael@0 | 191 | |
michael@0 | 192 | You can request to kill the process with the method *kill*. f the parameter "ignore_children" is set to False when the process handler class is initialized, all the process's children will be killed as well. |
michael@0 | 193 | |
michael@0 | 194 | Except on Windows, you can specify the signal with which to kill method the process (e.g.: *kill(signal.SIGKILL)*). |
michael@0 | 195 | |
michael@0 | 196 | .. code-block:: python |
michael@0 | 197 | |
michael@0 | 198 | import time |
michael@0 | 199 | from mozprocess import processhandler |
michael@0 | 200 | |
michael@0 | 201 | command = './proc_sleep_echo.sh' # Windows: 'proc_sleep_echo.bat' |
michael@0 | 202 | p = processhandler.ProcessHandler(command) |
michael@0 | 203 | p.run() |
michael@0 | 204 | time.sleep(2) |
michael@0 | 205 | p.kill() |
michael@0 | 206 | |
michael@0 | 207 | End of execution |
michael@0 | 208 | ```````````````` |
michael@0 | 209 | |
michael@0 | 210 | You can provide a function or a list of functions to call at the end of the process using the initilization parameter *onFinish*. |
michael@0 | 211 | |
michael@0 | 212 | .. code-block:: python |
michael@0 | 213 | |
michael@0 | 214 | from mozprocess import processhandler |
michael@0 | 215 | |
michael@0 | 216 | def finish(): |
michael@0 | 217 | print("Finished!!") |
michael@0 | 218 | |
michael@0 | 219 | command = './proc_sleep_echo.sh' # Windows: 'proc_sleep_echo.bat' |
michael@0 | 220 | |
michael@0 | 221 | p = processhandler.ProcessHandler(command, onFinish=finish) |
michael@0 | 222 | p.run() |
michael@0 | 223 | p.wait() |
michael@0 | 224 | |
michael@0 | 225 | Child management |
michael@0 | 226 | ---------------- |
michael@0 | 227 | |
michael@0 | 228 | Consider the following scripts: |
michael@0 | 229 | |
michael@0 | 230 | **proc_child.sh**: |
michael@0 | 231 | |
michael@0 | 232 | .. code-block:: sh |
michael@0 | 233 | |
michael@0 | 234 | #!/bin/sh |
michael@0 | 235 | for i in a b c d e |
michael@0 | 236 | do |
michael@0 | 237 | echo $i |
michael@0 | 238 | sleep 1 |
michael@0 | 239 | done |
michael@0 | 240 | |
michael@0 | 241 | **proc_parent.sh**: |
michael@0 | 242 | |
michael@0 | 243 | .. code-block:: sh |
michael@0 | 244 | |
michael@0 | 245 | #!/bin/sh |
michael@0 | 246 | ./proc_child.sh |
michael@0 | 247 | for i in 1 2 3 4 5 |
michael@0 | 248 | do |
michael@0 | 249 | echo $i |
michael@0 | 250 | sleep 1 |
michael@0 | 251 | done |
michael@0 | 252 | |
michael@0 | 253 | For windows users consider: |
michael@0 | 254 | |
michael@0 | 255 | **proc_child.bat**: |
michael@0 | 256 | |
michael@0 | 257 | .. code-block:: bat |
michael@0 | 258 | |
michael@0 | 259 | @echo off |
michael@0 | 260 | FOR %%A IN (a b c d e) DO ( |
michael@0 | 261 | ECHO %%A |
michael@0 | 262 | REM TIMEOUT /T 1 /NOBREAK |
michael@0 | 263 | PING -n 2 127.0.0.1 > NUL |
michael@0 | 264 | ) |
michael@0 | 265 | |
michael@0 | 266 | **proc_parent.bat**: |
michael@0 | 267 | |
michael@0 | 268 | .. code-block:: bat |
michael@0 | 269 | |
michael@0 | 270 | @echo off |
michael@0 | 271 | call proc_child.bat |
michael@0 | 272 | FOR %%A IN (1 2 3 4 5) DO ( |
michael@0 | 273 | ECHO %%A |
michael@0 | 274 | REM TIMEOUT /T 1 /NOBREAK |
michael@0 | 275 | PING -n 2 127.0.0.1 > NUL |
michael@0 | 276 | ) |
michael@0 | 277 | |
michael@0 | 278 | For processes that launch other processes, mozprocess allows you to get child running status, wait for child termination, and kill children. |
michael@0 | 279 | |
michael@0 | 280 | Ignoring children |
michael@0 | 281 | ````````````````` |
michael@0 | 282 | |
michael@0 | 283 | By default the *ignore_children* option is False. In that case, killing the main process will kill all its children at the same time. |
michael@0 | 284 | |
michael@0 | 285 | .. code-block:: python |
michael@0 | 286 | |
michael@0 | 287 | import time |
michael@0 | 288 | from mozprocess import processhandler |
michael@0 | 289 | |
michael@0 | 290 | def finish(): |
michael@0 | 291 | print("Finished") |
michael@0 | 292 | |
michael@0 | 293 | command = './proc_parent.sh' |
michael@0 | 294 | p = processhandler.ProcessHandler(command, ignore_children=False, onFinish=finish) |
michael@0 | 295 | p.run() |
michael@0 | 296 | time.sleep(2) |
michael@0 | 297 | print("kill") |
michael@0 | 298 | p.kill() |
michael@0 | 299 | |
michael@0 | 300 | If *ignore_children* is set to *True*, killing will apply only to the main process that will wait children end of execution before stoping (join). |
michael@0 | 301 | |
michael@0 | 302 | .. code-block:: python |
michael@0 | 303 | |
michael@0 | 304 | import time |
michael@0 | 305 | from mozprocess import processhandler |
michael@0 | 306 | |
michael@0 | 307 | def finish(): |
michael@0 | 308 | print("Finished") |
michael@0 | 309 | |
michael@0 | 310 | command = './proc_parent.sh' |
michael@0 | 311 | p = processhandler.ProcessHandler(command, ignore_children=True, onFinish=finish) |
michael@0 | 312 | p.run() |
michael@0 | 313 | time.sleep(2) |
michael@0 | 314 | print("kill") |
michael@0 | 315 | p.kill() |
michael@0 | 316 | |
michael@0 | 317 | API Documentation |
michael@0 | 318 | ----------------- |
michael@0 | 319 | |
michael@0 | 320 | .. module:: mozprocess |
michael@0 | 321 | .. autoclass:: ProcessHandlerMixin |
michael@0 | 322 | :members: __init__, timedOut, commandline, run, kill, processOutputLine, onTimeout, onFinish, wait |
michael@0 | 323 | .. autoclass:: ProcessHandler |
michael@0 | 324 | :members: |