src/eric7/DebugClients/Python/MultiProcessDebugExtension.py

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

eric ide

mercurial