eric6/DebugClients/Python/MultiProcessDebugExtension.py

branch
maintenance
changeset 8043
0acf98cd089a
parent 7973
e836d196e888
child 8222
5994b80b8760
diff -r 866adc8c315b -r 0acf98cd089a eric6/DebugClients/Python/MultiProcessDebugExtension.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/DebugClients/Python/MultiProcessDebugExtension.py	Mon Feb 01 10:38:16 2021 +0100
@@ -0,0 +1,410 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2002 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a function to patch the process creation functions to
+support multiprocess debugging.
+"""
+
+
+from DebugUtilities import (
+    patchArguments, patchArgumentStringWindows, isPythonProgram,
+    isWindowsPlatform
+)
+
+_debugClient = None
+
+
+def _shallPatch():
+    """
+    Function to determine, if the multiprocessing patches should be done.
+    
+    @return flag indicating patching should be performed
+    @rtype bool
+    """
+    return _debugClient.debugging and _debugClient.multiprocessSupport
+
+
+def patchModule(module, functionName, createFunction):
+    """
+    Function to replace a function of a module with a modified one.
+    
+    @param module reference to the module
+    @type types.ModuleType
+    @param functionName name of the function to be replaced
+    @type str
+    @param createFunction function creating the replacement
+    @type types.FunctionType
+    """
+    if hasattr(module, functionName):
+        originalName = 'original_' + functionName
+        if not hasattr(module, originalName):
+            setattr(module, originalName, getattr(module, functionName))
+            setattr(module, functionName, createFunction(originalName))
+
+
+def createExecl(originalName):
+    """
+    Function to patch the 'execl' process creation functions.
+    
+    <ul>
+        <li>os.execl(path, arg0, arg1, ...)</li>
+        <li>os.execle(path, arg0, arg1, ..., env)</li>
+        <li>os.execlp(file, arg0, arg1, ...)</li>
+        <li>os.execlpe(file, arg0, arg1, ..., env)</li>
+    </ul>
+    
+    @param originalName original name of the function to be patched
+    @type str
+    @return function replacing the original one
+    @rtype function
+    """
+    def newExecl(path, *args):
+        """
+        Function replacing the 'execl' functions of the os module.
+        """
+        import os
+        if _shallPatch():
+            args = patchArguments(_debugClient, args)
+            if isPythonProgram(args[0]):
+                path = args[0]
+        return getattr(os, originalName)(path, *args)
+    return newExecl
+
+
+def createExecv(originalName):
+    """
+    Function to patch the 'execv' process creation functions.
+    
+    <ul>
+        <li>os.execv(path, args)</li>
+        <li>os.execvp(file, args)</li>
+    </ul>
+    
+    @param originalName original name of the function to be patched
+    @type str
+    @return function replacing the original one
+    @rtype function
+    """
+    def newExecv(path, args):
+        """
+        Function replacing the 'execv' functions of the os module.
+        """
+        import os
+        if _shallPatch():
+            args = patchArguments(_debugClient, args)
+            if isPythonProgram(args[0]):
+                path = args[0]
+        return getattr(os, originalName)(path, args)
+    return newExecv
+
+
+def createExecve(originalName):
+    """
+    Function to patch the 'execve' process creation functions.
+    
+    <ul>
+        <li>os.execve(path, args, env)</li>
+        <li>os.execvpe(file, args, env)</li>
+    </ul>
+    
+    @param originalName original name of the function to be patched
+    @type str
+    @return function replacing the original one
+    @rtype function
+    """
+    def newExecve(path, args, env):
+        """
+        Function replacing the 'execve' functions of the os module.
+        """
+        import os
+        if _shallPatch():
+            args = patchArguments(_debugClient, args)
+            if isPythonProgram(args[0]):
+                path = args[0]
+        return getattr(os, originalName)(path, args, env)
+    return newExecve
+
+
+def createSpawnl(originalName):
+    """
+    Function to patch the 'spawnl' process creation functions.
+    
+    <ul>
+        <li>os.spawnl(mode, path, arg0, arg1, ...)</li>
+        <li>os.spawnlp(mode, file, arg0, arg1, ...)</li>
+    </ul>
+    
+    @param originalName original name of the function to be patched
+    @type str
+    @return function replacing the original one
+    @rtype function
+    """
+    def newSpawnl(mode, path, *args):
+        """
+        Function replacing the 'spawnl' functions of the os module.
+        """
+        import os
+        args = patchArguments(_debugClient, args)
+        return getattr(os, originalName)(mode, path, *args)
+    return newSpawnl
+
+
+def createSpawnv(originalName):
+    """
+    Function to patch the 'spawnv' process creation functions.
+    
+    <ul>
+        <li>os.spawnv(mode, path, args)</li>
+        <li>os.spawnvp(mode, file, args)</li>
+    </ul>
+    
+    @param originalName original name of the function to be patched
+    @type str
+    @return function replacing the original one
+    @rtype function
+    """
+    def newSpawnv(mode, path, args):
+        """
+        Function replacing the 'spawnv' functions of the os module.
+        """
+        import os
+        args = patchArguments(_debugClient, args)
+        return getattr(os, originalName)(mode, path, args)
+    return newSpawnv
+
+
+def createSpawnve(originalName):
+    """
+    Function to patch the 'spawnve' process creation functions.
+    
+    <ul>
+        <li>os.spawnve(mode, path, args, env)</li>
+        <li>os.spawnvpe(mode, file, args, env)</li>
+    </ul>
+    
+    @param originalName original name of the function to be patched
+    @type str
+    @return function replacing the original one
+    @rtype function
+    """
+    def newSpawnve(mode, path, args, env):
+        """
+        Function replacing the 'spawnve' functions of the os module.
+        """
+        import os
+        args = patchArguments(_debugClient, args)
+        return getattr(os, originalName)(mode, path, args, env)
+    return newSpawnve
+
+
+def createPosixSpawn(originalName):
+    """
+    Function to patch the 'posix_spawn' process creation functions.
+    
+    <ul>
+        <li>os.posix_spawn(path, argv, env, *, file_actions=None, ...
+            (6 more))</li>
+        <li>os.posix_spawnp(path, argv, env, *, file_actions=None, ...
+            (6 more))</li>
+    </ul>
+    
+    @param originalName original name of the function to be patched
+    @type str
+    @return function replacing the original one
+    @rtype function
+    """
+    def newPosixSpawn(path, argv, env, **kwargs):
+        """
+        Function replacing the 'posix_spawn' functions of the os module.
+        """
+        import os
+        argv = patchArguments(_debugClient, argv)
+        return getattr(os, originalName)(path, argv, env, **kwargs)
+    return newPosixSpawn
+
+
+def createForkExec(originalName):
+    """
+    Function to patch the 'fork_exec' process creation functions.
+    
+    <ul>
+        <li>_posixsubprocess.fork_exec(args, executable_list, close_fds,
+            ... (13 more))</li>
+    </ul>
+    
+    @param originalName original name of the function to be patched
+    @type str
+    @return function replacing the original one
+    @rtype function
+    """
+    def newForkExec(args, *other_args):
+        """
+        Function replacing the 'fork_exec' functions of the _posixsubprocess
+        module.
+        """
+        import _posixsubprocess
+        if _shallPatch():
+            args = patchArguments(_debugClient, args)
+        return getattr(_posixsubprocess, originalName)(args, *other_args)
+    return newForkExec
+
+
+def createFork(originalName):
+    """
+    Function to patch the 'fork' process creation functions.
+    
+    <ul>
+        <li>os.fork()</li>
+    </ul>
+    
+    @param originalName original name of the function to be patched
+    @type str
+    @return function replacing the original one
+    @rtype function
+    """
+    def newFork():
+        """
+        Function replacing the 'fork' function of the os module.
+        """
+        import os
+        import sys
+        
+        # A simple fork will result in a new python process
+        isNewPythonProcess = True
+        frame = sys._getframe()
+        
+        multiprocess = _shallPatch()
+        
+        isSubprocessFork = False
+        isMultiprocessingPopen = False
+        while frame is not None:
+            if frame.f_code.co_name == "_Popen":
+                # fork() was called from multiprocessing; ignore this here
+                # because it is handled in 'MultiprocessingExtension.py'.
+                isMultiprocessingPopen = True
+                break
+            
+            elif (
+                frame.f_code.co_name == '_execute_child' and
+                'subprocess' in frame.f_code.co_filename
+            ):
+                isSubprocessFork = True
+                # If we're actually in subprocess.Popen creating a child, it
+                # may result in something which is not a Python process, (so,
+                # we don't want to connect with it in the forked version).
+                executable = frame.f_locals.get('executable')
+                if executable is not None:
+                    isNewPythonProcess = False
+                    if isPythonProgram(executable):
+                        isNewPythonProcess = True
+                break
+            
+            frame = frame.f_back
+        frame = None    # Just make sure we don't hold on to it.
+        
+        childProcess = getattr(os, originalName)()     # fork
+        if not childProcess and not isMultiprocessingPopen:
+            if isNewPythonProcess:
+                (wd, host, port, exceptions, tracePython, redirect,
+                 noencoding) = _debugClient.startOptions
+                _debugClient.startDebugger(
+                    filename=sys.argv[0],
+                    host=host,
+                    port=port,
+                    enableTrace=multiprocess and not isSubprocessFork,
+                    exceptions=exceptions,
+                    tracePython=tracePython,
+                    redirect=redirect,
+                    passive=False,
+                    multiprocessSupport=multiprocess)
+        return childProcess
+    
+    return newFork
+
+
+def createCreateProcess(originalName):
+    """
+    Function to patch the 'CreateProcess' process creation function of
+    Windows.
+    
+    @param originalName original name of the function to be patched
+    @type str
+    @return function replacing the original one
+    @rtype function
+    """
+    def newCreateProcess(appName, cmdline, *args):
+        """
+        Function replacing the 'CreateProcess' function of the _subprocess
+        or _winapi module.
+        """
+        try:
+            import _subprocess
+        except ImportError:
+            import _winapi as _subprocess
+        return getattr(_subprocess, originalName)(
+            appName, patchArgumentStringWindows(_debugClient, cmdline), *args)
+    return newCreateProcess
+
+
+def patchNewProcessFunctions(multiprocessEnabled, debugClient):
+    """
+    Function to patch the process creation functions to support multiprocess
+    debugging.
+    
+    @param multiprocessEnabled flag indicating multiprocess support
+    @type bool
+    @param debugClient reference to the debug client object
+    @type DebugClient
+    """
+    global _debugClient
+    
+    if not multiprocessEnabled:
+        # return without patching
+        return
+    
+    import os
+    import sys
+    
+    # patch 'os.exec...()' functions
+#-    patchModule(os, "execl", createExecl)
+#-    patchModule(os, "execle", createExecl)
+#-    patchModule(os, "execlp", createExecl)
+#-    patchModule(os, "execlpe", createExecl)
+#-    patchModule(os, "execv", createExecv)
+#-    patchModule(os, "execve", createExecve)
+#-    patchModule(os, "execvp", createExecv)
+#-    patchModule(os, "execvpe", createExecve)
+    
+    # patch 'os.spawn...()' functions
+    patchModule(os, "spawnl", createSpawnl)
+    patchModule(os, "spawnle", createSpawnl)
+    patchModule(os, "spawnlp", createSpawnl)
+    patchModule(os, "spawnlpe", createSpawnl)
+    patchModule(os, "spawnv", createSpawnv)
+    patchModule(os, "spawnve", createSpawnve)
+    patchModule(os, "spawnvp", createSpawnv)
+    patchModule(os, "spawnvpe", createSpawnve)
+    
+    # patch 'os.posix_spawn...()' functions
+    if sys.version_info >= (3, 8) and not isWindowsPlatform():
+        patchModule(os, "posix_spawn", createPosixSpawn)
+        patchModule(os, "posix_spawnp", createPosixSpawn)
+    
+    if isWindowsPlatform():
+        try:
+            import _subprocess
+        except ImportError:
+            import _winapi as _subprocess
+        patchModule(_subprocess, 'CreateProcess', createCreateProcess)
+    else:
+        patchModule(os, "fork", createFork)
+        try:
+            import _posixsubprocess
+            patchModule(_posixsubprocess, "fork_exec", createForkExec)
+        except ImportError:
+            pass
+    
+    _debugClient = debugClient

eric ide

mercurial