src/eric7/DebugClients/Python/MultiProcessDebugExtension.py

branch
eric7
changeset 9221
bf71ee032bb4
parent 9209
b99e7fd55fd3
child 9473
3f23dbf37dbe
equal deleted inserted replaced
9220:e9e7eca7efee 9221:bf71ee032bb4
9 """ 9 """
10 10
11 import contextlib 11 import contextlib
12 12
13 from DebugUtilities import ( 13 from DebugUtilities import (
14 patchArguments, patchArgumentStringWindows, isPythonProgram, 14 patchArguments,
15 isWindowsPlatform 15 patchArgumentStringWindows,
16 isPythonProgram,
17 isWindowsPlatform,
16 ) 18 )
17 19
18 _debugClient = None 20 _debugClient = None
19 21
20 22
21 def _shallPatch(): 23 def _shallPatch():
22 """ 24 """
23 Function to determine, if the multiprocessing patches should be done. 25 Function to determine, if the multiprocessing patches should be done.
24 26
25 @return flag indicating patching should be performed 27 @return flag indicating patching should be performed
26 @rtype bool 28 @rtype bool
27 """ 29 """
28 return _debugClient.debugging and _debugClient.multiprocessSupport 30 return _debugClient.debugging and _debugClient.multiprocessSupport
29 31
30 32
31 def patchModule(module, functionName, createFunction): 33 def patchModule(module, functionName, createFunction):
32 """ 34 """
33 Function to replace a function of a module with a modified one. 35 Function to replace a function of a module with a modified one.
34 36
35 @param module reference to the module 37 @param module reference to the module
36 @type types.ModuleType 38 @type types.ModuleType
37 @param functionName name of the function to be replaced 39 @param functionName name of the function to be replaced
38 @type str 40 @type str
39 @param createFunction function creating the replacement 41 @param createFunction function creating the replacement
40 @type types.FunctionType 42 @type types.FunctionType
41 """ 43 """
42 if hasattr(module, functionName): 44 if hasattr(module, functionName):
43 originalName = 'original_' + functionName 45 originalName = "original_" + functionName
44 if not hasattr(module, originalName): 46 if not hasattr(module, originalName):
45 setattr(module, originalName, getattr(module, functionName)) 47 setattr(module, originalName, getattr(module, functionName))
46 setattr(module, functionName, createFunction(originalName)) 48 setattr(module, functionName, createFunction(originalName))
47 49
48 50
49 def createExecl(originalName): 51 def createExecl(originalName):
50 """ 52 """
51 Function to patch the 'execl' process creation functions. 53 Function to patch the 'execl' process creation functions.
52 54
53 <ul> 55 <ul>
54 <li>os.execl(path, arg0, arg1, ...)</li> 56 <li>os.execl(path, arg0, arg1, ...)</li>
55 <li>os.execle(path, arg0, arg1, ..., env)</li> 57 <li>os.execle(path, arg0, arg1, ..., env)</li>
56 <li>os.execlp(file, arg0, arg1, ...)</li> 58 <li>os.execlp(file, arg0, arg1, ...)</li>
57 <li>os.execlpe(file, arg0, arg1, ..., env)</li> 59 <li>os.execlpe(file, arg0, arg1, ..., env)</li>
58 </ul> 60 </ul>
59 61
60 @param originalName original name of the function to be patched 62 @param originalName original name of the function to be patched
61 @type str 63 @type str
62 @return function replacing the original one 64 @return function replacing the original one
63 @rtype function 65 @rtype function
64 """ 66 """
67
65 def newExecl(path, *args): 68 def newExecl(path, *args):
66 """ 69 """
67 Function replacing the 'execl' functions of the os module. 70 Function replacing the 'execl' functions of the os module.
68 """ 71 """
69 import os 72 import os
73
70 if _shallPatch(): 74 if _shallPatch():
71 args = patchArguments(_debugClient, args) 75 args = patchArguments(_debugClient, args)
72 if isPythonProgram(args[0]): 76 if isPythonProgram(args[0]):
73 path = args[0] 77 path = args[0]
74 return getattr(os, originalName)(path, *args) 78 return getattr(os, originalName)(path, *args)
79
75 return newExecl 80 return newExecl
76 81
77 82
78 def createExecv(originalName): 83 def createExecv(originalName):
79 """ 84 """
80 Function to patch the 'execv' process creation functions. 85 Function to patch the 'execv' process creation functions.
81 86
82 <ul> 87 <ul>
83 <li>os.execv(path, args)</li> 88 <li>os.execv(path, args)</li>
84 <li>os.execvp(file, args)</li> 89 <li>os.execvp(file, args)</li>
85 </ul> 90 </ul>
86 91
87 @param originalName original name of the function to be patched 92 @param originalName original name of the function to be patched
88 @type str 93 @type str
89 @return function replacing the original one 94 @return function replacing the original one
90 @rtype function 95 @rtype function
91 """ 96 """
97
92 def newExecv(path, args): 98 def newExecv(path, args):
93 """ 99 """
94 Function replacing the 'execv' functions of the os module. 100 Function replacing the 'execv' functions of the os module.
95 """ 101 """
96 import os 102 import os
103
97 if _shallPatch(): 104 if _shallPatch():
98 args = patchArguments(_debugClient, args) 105 args = patchArguments(_debugClient, args)
99 if isPythonProgram(args[0]): 106 if isPythonProgram(args[0]):
100 path = args[0] 107 path = args[0]
101 return getattr(os, originalName)(path, args) 108 return getattr(os, originalName)(path, args)
109
102 return newExecv 110 return newExecv
103 111
104 112
105 def createExecve(originalName): 113 def createExecve(originalName):
106 """ 114 """
107 Function to patch the 'execve' process creation functions. 115 Function to patch the 'execve' process creation functions.
108 116
109 <ul> 117 <ul>
110 <li>os.execve(path, args, env)</li> 118 <li>os.execve(path, args, env)</li>
111 <li>os.execvpe(file, args, env)</li> 119 <li>os.execvpe(file, args, env)</li>
112 </ul> 120 </ul>
113 121
114 @param originalName original name of the function to be patched 122 @param originalName original name of the function to be patched
115 @type str 123 @type str
116 @return function replacing the original one 124 @return function replacing the original one
117 @rtype function 125 @rtype function
118 """ 126 """
127
119 def newExecve(path, args, env): 128 def newExecve(path, args, env):
120 """ 129 """
121 Function replacing the 'execve' functions of the os module. 130 Function replacing the 'execve' functions of the os module.
122 """ 131 """
123 import os 132 import os
133
124 if _shallPatch(): 134 if _shallPatch():
125 args = patchArguments(_debugClient, args) 135 args = patchArguments(_debugClient, args)
126 if isPythonProgram(args[0]): 136 if isPythonProgram(args[0]):
127 path = args[0] 137 path = args[0]
128 return getattr(os, originalName)(path, args, env) 138 return getattr(os, originalName)(path, args, env)
139
129 return newExecve 140 return newExecve
130 141
131 142
132 def createSpawnl(originalName): 143 def createSpawnl(originalName):
133 """ 144 """
134 Function to patch the 'spawnl' process creation functions. 145 Function to patch the 'spawnl' process creation functions.
135 146
136 <ul> 147 <ul>
137 <li>os.spawnl(mode, path, arg0, arg1, ...)</li> 148 <li>os.spawnl(mode, path, arg0, arg1, ...)</li>
138 <li>os.spawnlp(mode, file, arg0, arg1, ...)</li> 149 <li>os.spawnlp(mode, file, arg0, arg1, ...)</li>
139 </ul> 150 </ul>
140 151
141 @param originalName original name of the function to be patched 152 @param originalName original name of the function to be patched
142 @type str 153 @type str
143 @return function replacing the original one 154 @return function replacing the original one
144 @rtype function 155 @rtype function
145 """ 156 """
157
146 def newSpawnl(mode, path, *args): 158 def newSpawnl(mode, path, *args):
147 """ 159 """
148 Function replacing the 'spawnl' functions of the os module. 160 Function replacing the 'spawnl' functions of the os module.
149 """ 161 """
150 import os 162 import os
163
151 args = patchArguments(_debugClient, args) 164 args = patchArguments(_debugClient, args)
152 return getattr(os, originalName)(mode, path, *args) 165 return getattr(os, originalName)(mode, path, *args)
166
153 return newSpawnl 167 return newSpawnl
154 168
155 169
156 def createSpawnv(originalName): 170 def createSpawnv(originalName):
157 """ 171 """
158 Function to patch the 'spawnv' process creation functions. 172 Function to patch the 'spawnv' process creation functions.
159 173
160 <ul> 174 <ul>
161 <li>os.spawnv(mode, path, args)</li> 175 <li>os.spawnv(mode, path, args)</li>
162 <li>os.spawnvp(mode, file, args)</li> 176 <li>os.spawnvp(mode, file, args)</li>
163 </ul> 177 </ul>
164 178
165 @param originalName original name of the function to be patched 179 @param originalName original name of the function to be patched
166 @type str 180 @type str
167 @return function replacing the original one 181 @return function replacing the original one
168 @rtype function 182 @rtype function
169 """ 183 """
184
170 def newSpawnv(mode, path, args): 185 def newSpawnv(mode, path, args):
171 """ 186 """
172 Function replacing the 'spawnv' functions of the os module. 187 Function replacing the 'spawnv' functions of the os module.
173 """ 188 """
174 import os 189 import os
190
175 args = patchArguments(_debugClient, args) 191 args = patchArguments(_debugClient, args)
176 return getattr(os, originalName)(mode, path, args) 192 return getattr(os, originalName)(mode, path, args)
193
177 return newSpawnv 194 return newSpawnv
178 195
179 196
180 def createSpawnve(originalName): 197 def createSpawnve(originalName):
181 """ 198 """
182 Function to patch the 'spawnve' process creation functions. 199 Function to patch the 'spawnve' process creation functions.
183 200
184 <ul> 201 <ul>
185 <li>os.spawnve(mode, path, args, env)</li> 202 <li>os.spawnve(mode, path, args, env)</li>
186 <li>os.spawnvpe(mode, file, args, env)</li> 203 <li>os.spawnvpe(mode, file, args, env)</li>
187 </ul> 204 </ul>
188 205
189 @param originalName original name of the function to be patched 206 @param originalName original name of the function to be patched
190 @type str 207 @type str
191 @return function replacing the original one 208 @return function replacing the original one
192 @rtype function 209 @rtype function
193 """ 210 """
211
194 def newSpawnve(mode, path, args, env): 212 def newSpawnve(mode, path, args, env):
195 """ 213 """
196 Function replacing the 'spawnve' functions of the os module. 214 Function replacing the 'spawnve' functions of the os module.
197 """ 215 """
198 import os 216 import os
217
199 args = patchArguments(_debugClient, args) 218 args = patchArguments(_debugClient, args)
200 return getattr(os, originalName)(mode, path, args, env) 219 return getattr(os, originalName)(mode, path, args, env)
220
201 return newSpawnve 221 return newSpawnve
202 222
203 223
204 def createPosixSpawn(originalName): 224 def createPosixSpawn(originalName):
205 """ 225 """
206 Function to patch the 'posix_spawn' process creation functions. 226 Function to patch the 'posix_spawn' process creation functions.
207 227
208 <ul> 228 <ul>
209 <li>os.posix_spawn(path, argv, env, *, file_actions=None, ... 229 <li>os.posix_spawn(path, argv, env, *, file_actions=None, ...
210 (6 more))</li> 230 (6 more))</li>
211 <li>os.posix_spawnp(path, argv, env, *, file_actions=None, ... 231 <li>os.posix_spawnp(path, argv, env, *, file_actions=None, ...
212 (6 more))</li> 232 (6 more))</li>
213 </ul> 233 </ul>
214 234
215 @param originalName original name of the function to be patched 235 @param originalName original name of the function to be patched
216 @type str 236 @type str
217 @return function replacing the original one 237 @return function replacing the original one
218 @rtype function 238 @rtype function
219 """ 239 """
240
220 def newPosixSpawn(path, argv, env, **kwargs): 241 def newPosixSpawn(path, argv, env, **kwargs):
221 """ 242 """
222 Function replacing the 'posix_spawn' functions of the os module. 243 Function replacing the 'posix_spawn' functions of the os module.
223 """ 244 """
224 import os 245 import os
246
225 argv = patchArguments(_debugClient, argv) 247 argv = patchArguments(_debugClient, argv)
226 return getattr(os, originalName)(path, argv, env, **kwargs) 248 return getattr(os, originalName)(path, argv, env, **kwargs)
249
227 return newPosixSpawn 250 return newPosixSpawn
228 251
229 252
230 def createForkExec(originalName): 253 def createForkExec(originalName):
231 """ 254 """
232 Function to patch the 'fork_exec' process creation functions. 255 Function to patch the 'fork_exec' process creation functions.
233 256
234 <ul> 257 <ul>
235 <li>_posixsubprocess.fork_exec(args, executable_list, close_fds, 258 <li>_posixsubprocess.fork_exec(args, executable_list, close_fds,
236 ... (13 more))</li> 259 ... (13 more))</li>
237 </ul> 260 </ul>
238 261
239 @param originalName original name of the function to be patched 262 @param originalName original name of the function to be patched
240 @type str 263 @type str
241 @return function replacing the original one 264 @return function replacing the original one
242 @rtype function 265 @rtype function
243 """ 266 """
267
244 def newForkExec(args, *other_args): 268 def newForkExec(args, *other_args):
245 """ 269 """
246 Function replacing the 'fork_exec' functions of the _posixsubprocess 270 Function replacing the 'fork_exec' functions of the _posixsubprocess
247 module. 271 module.
248 """ 272 """
249 import _posixsubprocess 273 import _posixsubprocess
274
250 if _shallPatch(): 275 if _shallPatch():
251 args = patchArguments(_debugClient, args) 276 args = patchArguments(_debugClient, args)
252 return getattr(_posixsubprocess, originalName)(args, *other_args) 277 return getattr(_posixsubprocess, originalName)(args, *other_args)
278
253 return newForkExec 279 return newForkExec
254 280
255 281
256 def createFork(originalName): 282 def createFork(originalName):
257 """ 283 """
258 Function to patch the 'fork' process creation functions. 284 Function to patch the 'fork' process creation functions.
259 285
260 <ul> 286 <ul>
261 <li>os.fork()</li> 287 <li>os.fork()</li>
262 </ul> 288 </ul>
263 289
264 @param originalName original name of the function to be patched 290 @param originalName original name of the function to be patched
265 @type str 291 @type str
266 @return function replacing the original one 292 @return function replacing the original one
267 @rtype function 293 @rtype function
268 """ 294 """
295
269 def newFork(): 296 def newFork():
270 """ 297 """
271 Function replacing the 'fork' function of the os module. 298 Function replacing the 'fork' function of the os module.
272 """ 299 """
273 import os 300 import os
274 import sys 301 import sys
275 302
276 # A simple fork will result in a new python process 303 # A simple fork will result in a new python process
277 isNewPythonProcess = True 304 isNewPythonProcess = True
278 frame = sys._getframe() 305 frame = sys._getframe()
279 306
280 multiprocess = _shallPatch() 307 multiprocess = _shallPatch()
281 308
282 isSubprocessFork = False 309 isSubprocessFork = False
283 isMultiprocessingPopen = False 310 isMultiprocessingPopen = False
284 while frame is not None: 311 while frame is not None:
285 if frame.f_code.co_name == "_Popen": 312 if frame.f_code.co_name == "_Popen":
286 # fork() was called from multiprocessing; ignore this here 313 # fork() was called from multiprocessing; ignore this here
287 # because it is handled in 'MultiprocessingExtension.py'. 314 # because it is handled in 'MultiprocessingExtension.py'.
288 isMultiprocessingPopen = True 315 isMultiprocessingPopen = True
289 break 316 break
290 317
291 elif ( 318 elif (
292 frame.f_code.co_name == '_execute_child' and 319 frame.f_code.co_name == "_execute_child"
293 'subprocess' in frame.f_code.co_filename 320 and "subprocess" in frame.f_code.co_filename
294 ): 321 ):
295 isSubprocessFork = True 322 isSubprocessFork = True
296 # If we're actually in subprocess.Popen creating a child, it 323 # If we're actually in subprocess.Popen creating a child, it
297 # may result in something which is not a Python process, (so, 324 # may result in something which is not a Python process, (so,
298 # we don't want to connect with it in the forked version). 325 # we don't want to connect with it in the forked version).
299 executable = frame.f_locals.get('executable') 326 executable = frame.f_locals.get("executable")
300 if executable is not None: 327 if executable is not None:
301 isNewPythonProcess = False 328 isNewPythonProcess = False
302 if isPythonProgram(executable): 329 if isPythonProgram(executable):
303 isNewPythonProcess = True 330 isNewPythonProcess = True
304 break 331 break
305 332
306 frame = frame.f_back 333 frame = frame.f_back
307 frame = None # Just make sure we don't hold on to it. 334 frame = None # Just make sure we don't hold on to it.
308 335
309 childProcess = getattr(os, originalName)() # fork 336 childProcess = getattr(os, originalName)() # fork
310 if ( 337 if not childProcess and not isMultiprocessingPopen and isNewPythonProcess:
311 not childProcess and 338 (
312 not isMultiprocessingPopen and 339 wd,
313 isNewPythonProcess 340 host,
314 ): 341 port,
315 (wd, host, port, exceptions, tracePython, redirect, 342 exceptions,
316 noencoding) = _debugClient.startOptions 343 tracePython,
344 redirect,
345 noencoding,
346 ) = _debugClient.startOptions
317 _debugClient.startDebugger( 347 _debugClient.startDebugger(
318 filename=sys.argv[0], 348 filename=sys.argv[0],
319 host=host, 349 host=host,
320 port=port, 350 port=port,
321 enableTrace=multiprocess and not isSubprocessFork, 351 enableTrace=multiprocess and not isSubprocessFork,
322 exceptions=exceptions, 352 exceptions=exceptions,
323 tracePython=tracePython, 353 tracePython=tracePython,
324 redirect=redirect, 354 redirect=redirect,
325 passive=False, 355 passive=False,
326 multiprocessSupport=multiprocess) 356 multiprocessSupport=multiprocess,
357 )
327 return childProcess 358 return childProcess
328 359
329 return newFork 360 return newFork
330 361
331 362
332 def createCreateProcess(originalName): 363 def createCreateProcess(originalName):
333 """ 364 """
334 Function to patch the 'CreateProcess' process creation function of 365 Function to patch the 'CreateProcess' process creation function of
335 Windows. 366 Windows.
336 367
337 @param originalName original name of the function to be patched 368 @param originalName original name of the function to be patched
338 @type str 369 @type str
339 @return function replacing the original one 370 @return function replacing the original one
340 @rtype function 371 @rtype function
341 """ 372 """
373
342 def newCreateProcess(appName, cmdline, *args): 374 def newCreateProcess(appName, cmdline, *args):
343 """ 375 """
344 Function replacing the 'CreateProcess' function of the _subprocess 376 Function replacing the 'CreateProcess' function of the _subprocess
345 or _winapi module. 377 or _winapi module.
346 """ 378 """
347 try: 379 try:
348 import _subprocess 380 import _subprocess
349 except ImportError: 381 except ImportError:
350 import _winapi as _subprocess 382 import _winapi as _subprocess
351 return getattr(_subprocess, originalName)( 383 return getattr(_subprocess, originalName)(
352 appName, patchArgumentStringWindows(_debugClient, cmdline), *args) 384 appName, patchArgumentStringWindows(_debugClient, cmdline), *args
385 )
386
353 return newCreateProcess 387 return newCreateProcess
354 388
355 389
356 def patchNewProcessFunctions(multiprocessEnabled, debugClient): 390 def patchNewProcessFunctions(multiprocessEnabled, debugClient):
357 """ 391 """
358 Function to patch the process creation functions to support multiprocess 392 Function to patch the process creation functions to support multiprocess
359 debugging. 393 debugging.
360 394
361 @param multiprocessEnabled flag indicating multiprocess support 395 @param multiprocessEnabled flag indicating multiprocess support
362 @type bool 396 @type bool
363 @param debugClient reference to the debug client object 397 @param debugClient reference to the debug client object
364 @type DebugClient 398 @type DebugClient
365 """ 399 """
366 global _debugClient 400 global _debugClient
367 401
368 if not multiprocessEnabled: 402 if not multiprocessEnabled:
369 # return without patching 403 # return without patching
370 return 404 return
371 405
372 import os 406 import os
373 import sys 407 import sys
374 408
375 # patch 'os.exec...()' functions 409 # patch 'os.exec...()' functions
376 #- patchModule(os, "execl", createExecl) 410 # - patchModule(os, "execl", createExecl)
377 #- patchModule(os, "execle", createExecl) 411 # - patchModule(os, "execle", createExecl)
378 #- patchModule(os, "execlp", createExecl) 412 # - patchModule(os, "execlp", createExecl)
379 #- patchModule(os, "execlpe", createExecl) 413 # - patchModule(os, "execlpe", createExecl)
380 #- patchModule(os, "execv", createExecv) 414 # - patchModule(os, "execv", createExecv)
381 #- patchModule(os, "execve", createExecve) 415 # - patchModule(os, "execve", createExecve)
382 #- patchModule(os, "execvp", createExecv) 416 # - patchModule(os, "execvp", createExecv)
383 #- patchModule(os, "execvpe", createExecve) 417 # - patchModule(os, "execvpe", createExecve)
384 418
385 # patch 'os.spawn...()' functions 419 # patch 'os.spawn...()' functions
386 patchModule(os, "spawnl", createSpawnl) 420 patchModule(os, "spawnl", createSpawnl)
387 patchModule(os, "spawnle", createSpawnl) 421 patchModule(os, "spawnle", createSpawnl)
388 patchModule(os, "spawnlp", createSpawnl) 422 patchModule(os, "spawnlp", createSpawnl)
389 patchModule(os, "spawnlpe", createSpawnl) 423 patchModule(os, "spawnlpe", createSpawnl)
390 patchModule(os, "spawnv", createSpawnv) 424 patchModule(os, "spawnv", createSpawnv)
391 patchModule(os, "spawnve", createSpawnve) 425 patchModule(os, "spawnve", createSpawnve)
392 patchModule(os, "spawnvp", createSpawnv) 426 patchModule(os, "spawnvp", createSpawnv)
393 patchModule(os, "spawnvpe", createSpawnve) 427 patchModule(os, "spawnvpe", createSpawnve)
394 428
395 # patch 'os.posix_spawn...()' functions 429 # patch 'os.posix_spawn...()' functions
396 if sys.version_info >= (3, 8) and not isWindowsPlatform(): 430 if sys.version_info >= (3, 8) and not isWindowsPlatform():
397 patchModule(os, "posix_spawn", createPosixSpawn) 431 patchModule(os, "posix_spawn", createPosixSpawn)
398 patchModule(os, "posix_spawnp", createPosixSpawn) 432 patchModule(os, "posix_spawnp", createPosixSpawn)
399 433
400 if isWindowsPlatform(): 434 if isWindowsPlatform():
401 try: 435 try:
402 import _subprocess 436 import _subprocess
403 except ImportError: 437 except ImportError:
404 import _winapi as _subprocess 438 import _winapi as _subprocess
405 patchModule(_subprocess, 'CreateProcess', createCreateProcess) 439 patchModule(_subprocess, "CreateProcess", createCreateProcess)
406 else: 440 else:
407 patchModule(os, "fork", createFork) 441 patchModule(os, "fork", createFork)
408 with contextlib.suppress(ImportError): 442 with contextlib.suppress(ImportError):
409 import _posixsubprocess 443 import _posixsubprocess
444
410 patchModule(_posixsubprocess, "fork_exec", createForkExec) 445 patchModule(_posixsubprocess, "fork_exec", createForkExec)
411 446
412 _debugClient = debugClient 447 _debugClient = debugClient

eric ide

mercurial