eric6/DebugClients/Python/MultiProcessDebugExtension.py

branch
multi_processing
changeset 7872
433dacbfa456
parent 7871
eb65864ca038
child 7873
cb2badbdf26c
equal deleted inserted replaced
7871:eb65864ca038 7872:433dacbfa456
13 patchArguments, patchArgumentStringWindows, isPythonProgram, 13 patchArguments, patchArgumentStringWindows, isPythonProgram,
14 isWindowsPlatform 14 isWindowsPlatform
15 ) 15 )
16 16
17 _debugClient = None 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
18 28
19 29
20 def patchModule(module, functionName, createFunction): 30 def patchModule(module, functionName, createFunction):
21 """ 31 """
22 Function to replace a function of a module with a modified one. 32 Function to replace a function of a module with a modified one.
43 <li>os.execl(path, arg0, arg1, ...)</li> 53 <li>os.execl(path, arg0, arg1, ...)</li>
44 <li>os.execle(path, arg0, arg1, ..., env)</li> 54 <li>os.execle(path, arg0, arg1, ..., env)</li>
45 <li>os.execlp(file, arg0, arg1, ...)</li> 55 <li>os.execlp(file, arg0, arg1, ...)</li>
46 <li>os.execlpe(file, arg0, arg1, ..., env)</li> 56 <li>os.execlpe(file, arg0, arg1, ..., env)</li>
47 </ul> 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 @type function
48 """ 63 """
49 def newExecl(path, *args): 64 def newExecl(path, *args):
50 """ 65 """
51 Function replacing the 'execl' functions of the os module. 66 Function replacing the 'execl' functions of the os module.
52 """ 67 """
53 import os 68 import os
54 if ( 69 if _shallPatch():
55 _debugClient.debugging and
56 _debugClient.multiprocessSupport
57 ):
58 args = patchArguments(_debugClient, args) 70 args = patchArguments(_debugClient, args)
59 if isPythonProgram(args[0]): 71 if isPythonProgram(args[0]):
60 path = args[0] 72 path = args[0]
61 return getattr(os, originalName)(path, *args) 73 return getattr(os, originalName)(path, *args)
62 return newExecl 74 return newExecl
68 80
69 <ul> 81 <ul>
70 <li>os.execv(path, args)</li> 82 <li>os.execv(path, args)</li>
71 <li>os.execvp(file, args)</li> 83 <li>os.execvp(file, args)</li>
72 </ul> 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 @type function
73 """ 90 """
74 def newExecv(path, args): 91 def newExecv(path, args):
75 """ 92 """
76 Function replacing the 'execv' functions of the os module. 93 Function replacing the 'execv' functions of the os module.
77 """ 94 """
78 import os 95 import os
79 if ( 96 if _shallPatch():
80 _debugClient.debugging and
81 _debugClient.multiprocessSupport
82 ):
83 args = patchArguments(_debugClient, args) 97 args = patchArguments(_debugClient, args)
84 if isPythonProgram(args[0]): 98 if isPythonProgram(args[0]):
85 path = args[0] 99 path = args[0]
86 return getattr(os, originalName)(path, args) 100 return getattr(os, originalName)(path, args)
87 return newExecv 101 return newExecv
93 107
94 <ul> 108 <ul>
95 <li>os.execve(path, args, env)</li> 109 <li>os.execve(path, args, env)</li>
96 <li>os.execvpe(file, args, env)</li> 110 <li>os.execvpe(file, args, env)</li>
97 </ul> 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 @type function
98 """ 117 """
99 def newExecve(path, args, env): 118 def newExecve(path, args, env):
100 """ 119 """
101 Function replacing the 'execve' functions of the os module. 120 Function replacing the 'execve' functions of the os module.
102 """ 121 """
103 import os 122 import os
104 if ( 123 if _shallPatch():
105 _debugClient.debugging and
106 _debugClient.multiprocessSupport
107 ):
108 args = patchArguments(_debugClient, args) 124 args = patchArguments(_debugClient, args)
109 if isPythonProgram(args[0]): 125 if isPythonProgram(args[0]):
110 path = args[0] 126 path = args[0]
111 return getattr(os, originalName)(path, args, env) 127 return getattr(os, originalName)(path, args, env)
112 return newExecve 128 return newExecve
113 129
114 130
115 # TODO: add createSpawn... 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 @type 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 @type 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 @type 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 @type 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
116 227
117 228
118 def createForkExec(originalName): 229 def createForkExec(originalName):
119 """ 230 """
120 Function to patch the 'fork_exec' process creation functions. 231 Function to patch the 'fork_exec' process creation functions.
121 232
122 <ul> 233 <ul>
123 <li>_posixsubprocess.fork_exec(args, executable_list, close_fds, 234 <li>_posixsubprocess.fork_exec(args, executable_list, close_fds,
124 ... (13 more))</li> 235 ... (13 more))</li>
125 </ul> 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 @type function
126 """ 242 """
127 def newForkExec(args, *other_args): 243 def newForkExec(args, *other_args):
128 """ 244 """
129 Function replacing the 'fork_exec' functions of the _posixsubprocess 245 Function replacing the 'fork_exec' functions of the _posixsubprocess
130 module. 246 module.
131 """ 247 """
132 import _posixsubprocess 248 import _posixsubprocess
133 if ( 249 if _shallPatch():
134 _debugClient.debugging and
135 _debugClient.multiprocessSupport
136 ):
137 args = patchArguments(_debugClient, args) 250 args = patchArguments(_debugClient, args)
138 return getattr(_posixsubprocess, originalName)(args, *other_args) 251 return getattr(_posixsubprocess, originalName)(args, *other_args)
139 return newForkExec 252 return newForkExec
140 253
141 254
142 def createFork(original_name): 255 def createFork(originalName):
143 """ 256 """
144 Function to patch the 'fork' process creation functions. 257 Function to patch the 'fork' process creation functions.
145 258
146 <ul> 259 <ul>
147 <li>os.fork()</li> 260 <li>os.fork()</li>
148 </ul> 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 @type function
149 """ 267 """
150 def new_fork(): 268 def new_fork():
151 """ 269 """
152 Function replacing the 'fork' function of the os module. 270 Function replacing the 'fork' function of the os module.
153 """ 271 """
156 274
157 # A simple fork will result in a new python process 275 # A simple fork will result in a new python process
158 isNewPythonProcess = True 276 isNewPythonProcess = True
159 frame = sys._getframe() 277 frame = sys._getframe()
160 278
161 multiprocess = ( 279 multiprocess = _shallPatch()
162 _debugClient.debugging and _debugClient.multiprocessSupport
163 )
164 280
165 isSubprocessFork = False 281 isSubprocessFork = False
282 isMultiprocessingPopen = False
166 while frame is not None: 283 while frame is not None:
167 if ( 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 (
168 frame.f_code.co_name == '_execute_child' and 291 frame.f_code.co_name == '_execute_child' and
169 'subprocess' in frame.f_code.co_filename 292 'subprocess' in frame.f_code.co_filename
170 ): 293 ):
171 isSubprocessFork = True 294 isSubprocessFork = True
172 # If we're actually in subprocess.Popen creating a child, it 295 # If we're actually in subprocess.Popen creating a child, it
180 break 303 break
181 304
182 frame = frame.f_back 305 frame = frame.f_back
183 frame = None # Just make sure we don't hold on to it. 306 frame = None # Just make sure we don't hold on to it.
184 307
185 childProcess = getattr(os, original_name)() # fork 308 childProcess = getattr(os, originalName)() # fork
186 if not childProcess: 309 if not childProcess and not isMultiprocessingPopen:
187 if isNewPythonProcess: 310 if isNewPythonProcess:
188 sys.settrace(None)
189 sys.setprofile(None)
190 _debugClient.sessionClose(False)
191 (wd, host, port, exceptions, tracePython, redirect, 311 (wd, host, port, exceptions, tracePython, redirect,
192 noencoding, fork_auto, fork_child) = _debugClient.startOptions 312 noencoding) = _debugClient.startOptions
193 _debugClient.startDebugger( 313 _debugClient.startDebugger(
194 filename=sys.argv[0], 314 filename=sys.argv[0],
195 host=host, 315 host=host,
196 port=port, 316 port=port,
197 enableTrace=multiprocess and not isSubprocessFork, 317 enableTrace=multiprocess and not isSubprocessFork,
199 tracePython=tracePython, 319 tracePython=tracePython,
200 redirect=redirect, 320 redirect=redirect,
201 passive=False, 321 passive=False,
202 multiprocessSupport=multiprocess) 322 multiprocessSupport=multiprocess)
203 return childProcess 323 return childProcess
204 324
205 return new_fork 325 return new_fork
206 326
207 327
208 def createCreateProcess(originalName): 328 def createCreateProcess(originalName):
209 """ 329 """
210 Function to patch the 'CreateProcess' process creation function of 330 Function to patch the 'CreateProcess' process creation function of
211 Windows. 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 @type function
212 """ 337 """
213 def newCreateProcess(appName, cmdline, *args): 338 def newCreateProcess(appName, cmdline, *args):
214 """ 339 """
215 Function replacing the 'CreateProcess' function of the _subprocess 340 Function replacing the 'CreateProcess' function of the _subprocess
216 or _winapi module. 341 or _winapi module.
222 return getattr(_subprocess, originalName)( 347 return getattr(_subprocess, originalName)(
223 appName, patchArgumentStringWindows(_debugClient, cmdline), *args) 348 appName, patchArgumentStringWindows(_debugClient, cmdline), *args)
224 return newCreateProcess 349 return newCreateProcess
225 350
226 351
227 # TODO: add 'createFork'
228
229
230 def patchNewProcessFunctions(multiprocessEnabled, debugClient): 352 def patchNewProcessFunctions(multiprocessEnabled, debugClient):
231 """ 353 """
232 Function to patch the process creation functions to support multiprocess 354 Function to patch the process creation functions to support multiprocess
233 debugging. 355 debugging.
234 356
242 if not multiprocessEnabled: 364 if not multiprocessEnabled:
243 # return without patching 365 # return without patching
244 return 366 return
245 367
246 import os 368 import os
369 import sys
247 370
248 # patch 'os.exec...()' functions 371 # patch 'os.exec...()' functions
249 patchModule(os, "execl", createExecl) 372 patchModule(os, "execl", createExecl)
250 patchModule(os, "execle", createExecl) 373 patchModule(os, "execle", createExecl)
251 patchModule(os, "execlp", createExecl) 374 patchModule(os, "execlp", createExecl)
253 patchModule(os, "execv", createExecv) 376 patchModule(os, "execv", createExecv)
254 patchModule(os, "execve", createExecve) 377 patchModule(os, "execve", createExecve)
255 patchModule(os, "execvp", createExecv) 378 patchModule(os, "execvp", createExecv)
256 patchModule(os, "execvpe", createExecve) 379 patchModule(os, "execvpe", createExecve)
257 380
258 # TODO: implement patching of the various functions of the os module 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)
259 395
260 if isWindowsPlatform(): 396 if isWindowsPlatform():
261 try: 397 try:
262 import _subprocess 398 import _subprocess
263 except ImportError: 399 except ImportError:

eric ide

mercurial