eric6/DebugClients/Python/MultiProcessDebugExtension.py

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

eric ide

mercurial