Coverage for C:\Program Files\JetBrains\PyCharm Community Edition 2020.3.2\plugins\python-ce\helpers\pydev\_pydev_bundle\pydev_monkey.py : 1%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# License: EPL
2import os
3import sys
4import traceback
5from _pydev_imps._pydev_saved_modules import threading
6from _pydevd_bundle.pydevd_constants import get_global_debugger, IS_WINDOWS, IS_MACOS, IS_JYTHON, IS_PY36_OR_LESSER, IS_PY38_OR_GREATER, \
7 get_current_thread_id
8from _pydev_bundle import pydev_log
10try:
11 xrange
12except:
13 xrange = range
16PYTHON_NAMES = ['python', 'jython', 'pypy']
18#===============================================================================
19# Things that are dependent on having the pydevd debugger
20#===============================================================================
21def log_debug(msg):
22 pydev_log.debug(msg)
25def log_error_once(msg):
26 pydev_log.error_once(msg)
29pydev_src_dir = os.path.dirname(os.path.dirname(__file__))
32def _get_python_c_args(host, port, indC, args, setup):
33 host_literal = "'" + host + "'" if host is not None else 'None'
34 return ("import sys; sys.path.append(r'%s'); import pydevd; "
35 "pydevd.settrace(host=%s, port=%s, suspend=False, trace_only_current_thread=False, patch_multiprocessing=True); "
36 "from pydevd import SetupHolder; SetupHolder.setup = %s; %s"
37 ) % (
38 pydev_src_dir,
39 host_literal,
40 port,
41 setup,
42 args[indC + 1])
45def _get_host_port():
46 import pydevd
47 host, port = pydevd.dispatch()
48 return host, port
51def _is_managed_arg(arg):
52 return arg.endswith('pydevd.py')
55def _is_already_patched(args):
56 for arg in args:
57 if 'pydevd' in arg:
58 return True
59 return False
62def _is_py3_and_has_bytes_args(args):
63 if not isinstance('', type(u'')):
64 return False
65 for arg in args:
66 if isinstance(arg, bytes):
67 return True
68 return False
71def _on_forked_process():
72 import pydevd
73 pydevd.threadingCurrentThread().__pydevd_main_thread = True
74 pydevd.settrace_forked()
77def _on_set_trace_for_new_thread(global_debugger):
78 if global_debugger is not None:
79 global_debugger.enable_tracing()
82#===============================================================================
83# Things related to monkey-patching
84#===============================================================================
85def is_python_args(args):
86 return not _is_py3_and_has_bytes_args(args) and len(args) > 0 and is_python(args[0])
89def is_executable(path):
90 return os.access(os.path.abspath(path), os.EX_OK)
93def starts_with_python_shebang(path):
94 try:
95 with open(path) as f:
96 for line in f:
97 line = line.strip()
98 if line:
99 for name in PYTHON_NAMES:
100 if line.startswith('#!/usr/bin/env %s' % name):
101 return True
102 return False
103 except UnicodeDecodeError:
104 return False
105 except:
106 traceback.print_exc()
107 return False
110def is_python(path):
111 if path.endswith("'") or path.endswith('"'):
112 path = path[1:len(path) - 1]
113 filename = os.path.basename(path).lower()
114 for name in PYTHON_NAMES:
115 if filename.find(name) != -1:
116 return True
117 return not IS_WINDOWS and is_executable(path) and starts_with_python_shebang(path)
120def remove_quotes_from_args(args):
121 if sys.platform == "win32":
122 new_args = []
123 for x in args:
124 if len(x) > 1 and x.startswith('"') and x.endswith('"'):
125 x = x[1:-1]
126 new_args.append(x)
127 return new_args
128 else:
129 return args
132def quote_args(args):
133 if sys.platform == "win32":
134 quoted_args = []
135 for x in args:
136 if x.startswith('"') and x.endswith('"'):
137 quoted_args.append(x)
138 else:
139 if ' ' in x:
140 x = x.replace('"', '\\"')
141 quoted_args.append('"%s"' % x)
142 else:
143 quoted_args.append(x)
144 return quoted_args
145 else:
146 return args
149def get_c_option_index(args):
150 """
151 Get index of "-c" argument and check if it's interpreter's option
152 :param args: list of arguments
153 :return: index of "-c" if it's an interpreter's option and -1 if it doesn't exist or program's option
154 """
155 try:
156 ind_c = args.index('-c')
157 except ValueError:
158 return -1
159 else:
160 for i in range(1, ind_c):
161 if not args[i].startswith('-'):
162 # there is an arg without "-" before "-c", so it's not an interpreter's option
163 return -1
164 return ind_c
167def patch_args(args):
168 try:
169 log_debug("Patching args: %s" % str(args))
171 if _is_py3_and_has_bytes_args(args):
172 warn_bytes_args()
173 return args
175 args = remove_quotes_from_args(args)
177 from pydevd import SetupHolder
178 new_args = []
179 if len(args) == 0:
180 return args
182 if is_python(args[0]):
184 for name in PYTHON_NAMES:
185 if args[0].find(name) != -1:
186 break
187 else:
188 # Executable file with Python shebang.
189 args.insert(0, sys.executable)
191 ind_c = get_c_option_index(args)
193 if ind_c != -1:
194 if _is_already_patched(args):
195 return args
197 host, port = _get_host_port()
199 if port is not None:
200 new_args.extend(args)
201 new_args[ind_c + 1] = _get_python_c_args(host, port, ind_c, args, SetupHolder.setup)
202 new_args = quote_args(new_args)
203 log_debug("Patched args: %s" % str(new_args))
204 return new_args
205 else:
206 # Check for Python ZIP Applications and don't patch the args for them.
207 # Assumes the first non `-<flag>` argument is what we need to check.
208 # There's probably a better way to determine this but it works for most cases.
209 continue_next = False
210 for i in range(1, len(args)):
211 if continue_next:
212 continue_next = False
213 continue
215 arg = args[i]
216 if arg.startswith('-'):
217 # Skip the next arg too if this flag expects a value.
218 continue_next = arg in ['-m', '-W', '-X']
219 continue
221 if arg.rsplit('.')[-1] in ['zip', 'pyz', 'pyzw']:
222 log_debug('Executing a PyZip, returning')
223 return args
224 break
226 new_args.append(args[0])
227 else:
228 log_debug("Process is not python, returning.")
229 return args
231 i = 1
232 # Original args should be something as:
233 # ['X:\\pysrc\\pydevd.py', '--multiprocess', '--print-in-debugger-startup',
234 # '--vm_type', 'python', '--client', '127.0.0.1', '--port', '56352', '--file', 'x:\\snippet1.py']
235 from _pydevd_bundle.pydevd_command_line_handling import setup_to_argv
236 SetupHolder.setup['module'] = False # clean module param from parent process
237 original = setup_to_argv(SetupHolder.setup) + ['--file']
238 while i < len(args):
239 if args[i] == '-m':
240 # Always insert at pos == 1 (i.e.: pydevd "--module" --multiprocess ...)
241 original.insert(1, '--module')
242 else:
243 if args[i] == '-':
244 # this is the marker that input is going to be from stdin for Python
245 # for now we just disable the debugging here, don't crash but is not supported
246 return args
247 elif args[i].startswith('-'):
248 new_args.append(args[i])
249 else:
250 break
251 i += 1
253 # Note: undoing https://github.com/Elizaveta239/PyDev.Debugger/commit/053c9d6b1b455530bca267e7419a9f63bf51cddf
254 # (i >= len(args) instead of i < len(args))
255 # in practice it'd raise an exception here and would return original args, which is not what we want... providing
256 # a proper fix for https://youtrack.jetbrains.com/issue/PY-9767 elsewhere.
257 if i >= len(args) or _is_managed_arg(args[i]): # no need to add pydevd twice
258 log_debug("Patched args: %s" % str(args))
259 return args
261 for x in original:
262 new_args.append(x)
263 if x == '--file':
264 break
266 while i < len(args):
267 new_args.append(args[i])
268 i += 1
270 new_args = quote_args(new_args)
271 log_debug("Patched args: %s" % str(new_args))
272 return new_args
273 except:
274 traceback.print_exc()
275 return args
278def str_to_args_windows(args):
279 # see http:#msdn.microsoft.com/en-us/library/a1y7w461.aspx
280 result = []
282 DEFAULT = 0
283 ARG = 1
284 IN_DOUBLE_QUOTE = 2
286 state = DEFAULT
287 backslashes = 0
288 buf = ''
290 args_len = len(args)
291 for i in xrange(args_len):
292 ch = args[i]
293 if (ch == '\\'):
294 backslashes += 1
295 continue
296 elif (backslashes != 0):
297 if ch == '"':
298 while backslashes >= 2:
299 backslashes -= 2
300 buf += '\\'
301 if (backslashes == 1):
302 if (state == DEFAULT):
303 state = ARG
305 buf += '"'
306 backslashes = 0
307 continue
308 # else fall through to switch
309 else:
310 # false alarm, treat passed backslashes literally...
311 if (state == DEFAULT):
312 state = ARG
314 while backslashes > 0:
315 backslashes -= 1
316 buf += '\\'
317 # fall through to switch
318 if ch in (' ', '\t'):
319 if (state == DEFAULT):
320 # skip
321 continue
322 elif (state == ARG):
323 state = DEFAULT
324 result.append(buf)
325 buf = ''
326 continue
328 if state in (DEFAULT, ARG):
329 if ch == '"':
330 state = IN_DOUBLE_QUOTE
331 else:
332 state = ARG
333 buf += ch
335 elif state == IN_DOUBLE_QUOTE:
336 if ch == '"':
337 if (i + 1 < args_len and args[i + 1] == '"'):
338 # Undocumented feature in Windows:
339 # Two consecutive double quotes inside a double-quoted argument are interpreted as
340 # a single double quote.
341 buf += '"'
342 i += 1
343 elif len(buf) == 0:
344 # empty string on Windows platform. Account for bug in constructor of
345 # JDK's java.lang.ProcessImpl.
346 result.append("\"\"")
347 state = DEFAULT
348 else:
349 state = ARG
350 else:
351 buf += ch
353 else:
354 raise RuntimeError('Illegal condition')
356 if len(buf) > 0 or state != DEFAULT:
357 result.append(buf)
359 return result
362def patch_arg_str_win(arg_str):
363 args = str_to_args_windows(arg_str)
364 # Fix https://youtrack.jetbrains.com/issue/PY-9767 (args may be empty)
365 if not args or not is_python(args[0]):
366 return arg_str
367 arg_str = ' '.join(patch_args(args))
368 log_debug("New args: %s" % arg_str)
369 return arg_str
372def patch_fork_exec_executable_list(args, other_args):
373 # When calling a Python executable script with `subprocess.call` the latest uses the first argument as an executable for `fork_exec`.
374 # This leads to `subprocess.call(["foo.py", "bar", "baz"])` after patching the args will be transformed into something like
375 # foo.py pydevd.py --port 59043 --client 127.0.0.1 --multiproc --file foo.py bar baz.
376 # To fix the issue we need to look inside the `fork_exec` executable list and, if necessary, replace an executable script with Python.
377 i = 0
378 for arg in args:
379 i += 1
380 if arg == '--file':
381 break
382 else:
383 return other_args
384 executable_list = other_args[0]
385 if args[i].encode() in executable_list:
386 return ((sys.executable.encode(),),) + other_args[1:]
387 return other_args
390_ORIGINAL_PREFIX = 'original_'
393def monkey_patch_module(module, funcname, create_func):
394 if hasattr(module, funcname):
395 original_name = _ORIGINAL_PREFIX + funcname
396 if not hasattr(module, original_name):
397 setattr(module, original_name, getattr(module, funcname))
398 setattr(module, funcname, create_func(original_name))
401def monkey_patch_os(funcname, create_func):
402 monkey_patch_module(os, funcname, create_func)
405def warn_multiproc():
406 log_error_once(
407 "pydev debugger: New process is launching (breakpoints won't work in the new process).\n"
408 "pydev debugger: To debug that process please enable 'Attach to subprocess automatically while debugging?' option in the debugger settings.\n")
411def warn_bytes_args():
412 log_error_once(
413 "pydev debugger: bytes arguments were passed to a new process creation function. Breakpoints may not work correctly.\n")
416def create_warn_multiproc(original_name):
418 def new_warn_multiproc(*args):
419 import os
421 warn_multiproc()
423 result = getattr(os, original_name)(*args)
425 if original_name == _ORIGINAL_PREFIX + 'fork':
426 pid = result
427 # If automatic attaching to new processes is disabled, it is important to stop tracing in the child process. The reason is the
428 # "forked" instance of the debugger can potentially hit a breakpoint, which results in the process hanging.
429 if pid == 0:
430 debugger = get_global_debugger()
431 if debugger:
432 debugger.stoptrace()
433 return pid
434 else:
435 return result
437 return new_warn_multiproc
440def create_execl(original_name):
441 def new_execl(path, *args):
442 """
443 os.execl(path, arg0, arg1, ...)
444 os.execle(path, arg0, arg1, ..., env)
445 os.execlp(file, arg0, arg1, ...)
446 os.execlpe(file, arg0, arg1, ..., env)
447 """
448 import os
449 args = patch_args(args)
450 if is_python_args(args):
451 path = args[0]
452 send_process_will_be_substituted()
453 return getattr(os, original_name)(path, *args)
454 return new_execl
457def create_execv(original_name):
458 def new_execv(path, args):
459 """
460 os.execv(path, args)
461 os.execvp(file, args)
462 """
463 import os
464 args = patch_args(args)
465 if is_python_args(args):
466 path = args[0]
467 send_process_will_be_substituted()
468 return getattr(os, original_name)(path, args)
469 return new_execv
472def create_execve(original_name):
473 """
474 os.execve(path, args, env)
475 os.execvpe(file, args, env)
476 """
477 def new_execve(path, args, env):
478 import os
479 args = patch_args(args)
480 if is_python_args(args):
481 path = args[0]
482 send_process_will_be_substituted()
483 return getattr(os, original_name)(path, args, env)
484 return new_execve
487def create_spawnl(original_name):
488 def new_spawnl(mode, path, *args):
489 """
490 os.spawnl(mode, path, arg0, arg1, ...)
491 os.spawnlp(mode, file, arg0, arg1, ...)
492 """
493 import os
494 args = patch_args(args)
495 send_process_created_message()
496 return getattr(os, original_name)(mode, path, *args)
497 return new_spawnl
500def create_spawnv(original_name):
501 def new_spawnv(mode, path, args):
502 """
503 os.spawnv(mode, path, args)
504 os.spawnvp(mode, file, args)
505 """
506 import os
507 args = patch_args(args)
508 send_process_created_message()
509 return getattr(os, original_name)(mode, path, args)
510 return new_spawnv
513def create_spawnve(original_name):
514 """
515 os.spawnve(mode, path, args, env)
516 os.spawnvpe(mode, file, args, env)
517 """
518 def new_spawnve(mode, path, args, env):
519 import os
520 args = patch_args(args)
521 send_process_created_message()
522 return getattr(os, original_name)(mode, path, args, env)
523 return new_spawnve
526def create_posix_spawn(original_name):
527 """
528 os.posix_spawn(path, argv, env, *, file_actions=None, ... (6 more))
529 os.posix_spawnp(path, argv, env, *, file_actions=None, ... (6 more))
530 """
531 def new_posix_spawn(path, argv, env, **kwargs):
532 import os
533 argv = patch_args(argv)
534 send_process_created_message()
535 return getattr(os, original_name)(path, argv, env, **kwargs)
536 return new_posix_spawn
539def create_fork_exec(original_name):
540 """
541 _posixsubprocess.fork_exec(args, executable_list, close_fds, ... (13 more))
542 """
543 def new_fork_exec(args, *other_args):
544 import _posixsubprocess # @UnresolvedImport
545 args = patch_args(args)
546 send_process_created_message()
547 return getattr(_posixsubprocess, original_name)(args, *patch_fork_exec_executable_list(args, other_args))
548 return new_fork_exec
551def create_warn_fork_exec(original_name):
552 """
553 _posixsubprocess.fork_exec(args, executable_list, close_fds, ... (13 more))
554 """
555 def new_warn_fork_exec(*args):
556 try:
557 import _posixsubprocess
558 warn_multiproc()
559 return getattr(_posixsubprocess, original_name)(*args)
560 except:
561 pass
562 return new_warn_fork_exec
565def create_CreateProcess(original_name):
566 """
567 CreateProcess(*args, **kwargs)
568 """
569 def new_CreateProcess(app_name, cmd_line, *args):
570 try:
571 import _subprocess
572 except ImportError:
573 import _winapi as _subprocess
574 send_process_created_message()
575 return getattr(_subprocess, original_name)(app_name, patch_arg_str_win(cmd_line), *args)
576 return new_CreateProcess
579def create_CreateProcessWarnMultiproc(original_name):
580 """
581 CreateProcess(*args, **kwargs)
582 """
583 def new_CreateProcess(*args):
584 try:
585 import _subprocess
586 except ImportError:
587 import _winapi as _subprocess
588 warn_multiproc()
589 return getattr(_subprocess, original_name)(*args)
590 return new_CreateProcess
593def apply_foundation_framework_hack():
594 # Hack in order to prevent the crash on macOS - load the Foundation framework before any forking in the debugger.
595 # See: https://bugs.python.org/issue35219.
596 import ctypes
597 try:
598 ctypes.cdll.LoadLibrary('/System/Library/Frameworks/Foundation.framework/Foundation')
599 except OSError:
600 log_error_once('Failed to load the Foundation framework shared library. Debugging of code that uses `os.fork()` may not work.\n'
601 'Consider setting the `OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES` environment variable.')
602 else:
603 log_debug('Successfully loaded the Foundation framework shared library.')
606def create_fork(original_name):
607 def new_fork():
608 import os
610 # A simple fork will result in a new python process
611 is_new_python_process = True
612 frame = sys._getframe()
614 while frame is not None:
615 if frame.f_code.co_name == '_execute_child' and 'subprocess' in frame.f_code.co_filename:
616 # If we're actually in subprocess.Popen creating a child, it may
617 # result in something which is not a Python process, (so, we
618 # don't want to connect with it in the forked version).
619 executable = frame.f_locals.get('executable')
620 if executable is not None:
621 is_new_python_process = False
622 if is_python(executable):
623 is_new_python_process = True
624 break
626 frame = frame.f_back
627 frame = None # Just make sure we don't hold on to it.
629 if IS_MACOS and IS_PY36_OR_LESSER:
630 is_fork_safety_disabled = os.environ.get('OBJC_DISABLE_INITIALIZE_FORK_SAFETY') == 'YES'
631 if not is_fork_safety_disabled:
632 apply_foundation_framework_hack()
634 child_process = getattr(os, original_name)() # fork
635 if not child_process:
636 if is_new_python_process:
637 log_debug("A new child process with PID %d has been forked" % os.getpid())
638 _on_forked_process()
639 else:
640 if is_new_python_process:
641 send_process_created_message()
642 return child_process
643 return new_fork
646def send_process_created_message():
647 debugger = get_global_debugger()
648 if debugger is not None:
649 debugger.send_process_created_message()
652def send_process_will_be_substituted():
653 """Sends a message that a new process is going to be created.
654 When `PyDB` works in server mode this method also waits for the
655 response from IDE to be sure that IDE received this message.
656 """
657 from _pydevd_bundle.pydevd_comm import get_global_debugger
658 debugger = get_global_debugger()
659 if debugger is not None:
660 debugger.send_process_will_be_substituted()
663def patch_new_process_functions():
664 # os.execl(path, arg0, arg1, ...)
665 # os.execle(path, arg0, arg1, ..., env)
666 # os.execlp(file, arg0, arg1, ...)
667 # os.execlpe(file, arg0, arg1, ..., env)
668 # os.execv(path, args)
669 # os.execve(path, args, env)
670 # os.execvp(file, args)
671 # os.execvpe(file, args, env)
672 monkey_patch_os('execl', create_execl)
673 monkey_patch_os('execle', create_execl)
674 monkey_patch_os('execlp', create_execl)
675 monkey_patch_os('execlpe', create_execl)
676 monkey_patch_os('execv', create_execv)
677 monkey_patch_os('execve', create_execve)
678 monkey_patch_os('execvp', create_execv)
679 monkey_patch_os('execvpe', create_execve)
681 # os.spawnl(mode, path, ...)
682 # os.spawnle(mode, path, ..., env)
683 # os.spawnlp(mode, file, ...)
684 # os.spawnlpe(mode, file, ..., env)
685 # os.spawnv(mode, path, args)
686 # os.spawnve(mode, path, args, env)
687 # os.spawnvp(mode, file, args)
688 # os.spawnvpe(mode, file, args, env)
690 monkey_patch_os('spawnl', create_spawnl)
691 monkey_patch_os('spawnle', create_spawnl)
692 monkey_patch_os('spawnlp', create_spawnl)
693 monkey_patch_os('spawnlpe', create_spawnl)
694 monkey_patch_os('spawnv', create_spawnv)
695 monkey_patch_os('spawnve', create_spawnve)
696 monkey_patch_os('spawnvp', create_spawnv)
697 monkey_patch_os('spawnvpe', create_spawnve)
699 if IS_PY38_OR_GREATER and not IS_WINDOWS:
700 monkey_patch_os('posix_spawn', create_posix_spawn)
701 monkey_patch_os('posix_spawnp', create_posix_spawn)
703 if not IS_JYTHON:
704 if not IS_WINDOWS:
705 monkey_patch_os('fork', create_fork)
706 try:
707 import _posixsubprocess
708 monkey_patch_module(_posixsubprocess, 'fork_exec', create_fork_exec)
709 except ImportError:
710 pass
711 else:
712 # Windows
713 try:
714 import _subprocess
715 except ImportError:
716 import _winapi as _subprocess
717 monkey_patch_module(_subprocess, 'CreateProcess', create_CreateProcess)
720def patch_new_process_functions_with_warning():
721 monkey_patch_os('execl', create_warn_multiproc)
722 monkey_patch_os('execle', create_warn_multiproc)
723 monkey_patch_os('execlp', create_warn_multiproc)
724 monkey_patch_os('execlpe', create_warn_multiproc)
725 monkey_patch_os('execv', create_warn_multiproc)
726 monkey_patch_os('execve', create_warn_multiproc)
727 monkey_patch_os('execvp', create_warn_multiproc)
728 monkey_patch_os('execvpe', create_warn_multiproc)
729 monkey_patch_os('spawnl', create_warn_multiproc)
730 monkey_patch_os('spawnle', create_warn_multiproc)
731 monkey_patch_os('spawnlp', create_warn_multiproc)
732 monkey_patch_os('spawnlpe', create_warn_multiproc)
733 monkey_patch_os('spawnv', create_warn_multiproc)
734 monkey_patch_os('spawnve', create_warn_multiproc)
735 monkey_patch_os('spawnvp', create_warn_multiproc)
736 monkey_patch_os('spawnvpe', create_warn_multiproc)
738 if IS_PY38_OR_GREATER and not IS_WINDOWS:
739 monkey_patch_os('posix_spawn', create_warn_multiproc)
740 monkey_patch_os('posix_spawnp', create_warn_multiproc)
742 if not IS_JYTHON:
743 if not IS_WINDOWS:
744 monkey_patch_os('fork', create_warn_multiproc)
745 try:
746 import _posixsubprocess
747 monkey_patch_module(_posixsubprocess, 'fork_exec', create_warn_fork_exec)
748 except ImportError:
749 pass
750 else:
751 # Windows
752 try:
753 import _subprocess
754 except ImportError:
755 import _winapi as _subprocess
756 monkey_patch_module(_subprocess, 'CreateProcess', create_CreateProcessWarnMultiproc)
759class _NewThreadStartupWithTrace:
761 def __init__(self, original_func, args, kwargs):
762 self.original_func = original_func
763 self.args = args
764 self.kwargs = kwargs
766 def __call__(self):
767 # We monkey-patch the thread creation so that this function is called in the new thread. At this point
768 # we notify of its creation and start tracing it.
769 global_debugger = get_global_debugger()
771 thread_id = None
772 if global_debugger is not None:
773 # Note: if this is a thread from threading.py, we're too early in the boostrap process (because we mocked
774 # the start_new_thread internal machinery and thread._bootstrap has not finished), so, the code below needs
775 # to make sure that we use the current thread bound to the original function and not use
776 # threading.currentThread() unless we're sure it's a dummy thread.
777 t = getattr(self.original_func, '__self__', getattr(self.original_func, 'im_self', None))
778 if not isinstance(t, threading.Thread):
779 # This is not a threading.Thread but a Dummy thread (so, get it as a dummy thread using
780 # currentThread).
781 t = threading.currentThread()
783 if not getattr(t, 'is_pydev_daemon_thread', False):
784 thread_id = get_current_thread_id(t)
785 global_debugger.notify_thread_created(thread_id, t)
786 _on_set_trace_for_new_thread(global_debugger)
788 if getattr(global_debugger, 'thread_analyser', None) is not None:
789 try:
790 from pydevd_concurrency_analyser.pydevd_concurrency_logger import log_new_thread
791 log_new_thread(global_debugger, t)
792 except:
793 sys.stderr.write("Failed to detect new thread for visualization")
794 try:
795 ret = self.original_func(*self.args, **self.kwargs)
796 finally:
797 if thread_id is not None:
798 global_debugger.notify_thread_not_alive(thread_id)
800 return ret
803class _NewThreadStartupWithoutTrace:
805 def __init__(self, original_func, args, kwargs):
806 self.original_func = original_func
807 self.args = args
808 self.kwargs = kwargs
810 def __call__(self):
811 return self.original_func(*self.args, **self.kwargs)
813_UseNewThreadStartup = _NewThreadStartupWithTrace
816def _get_threading_modules_to_patch():
817 threading_modules_to_patch = []
819 try:
820 import thread as _thread
821 except:
822 import _thread
823 threading_modules_to_patch.append(_thread)
824 threading_modules_to_patch.append(threading)
826 return threading_modules_to_patch
828threading_modules_to_patch = _get_threading_modules_to_patch()
831def patch_thread_module(thread_module):
833 if getattr(thread_module, '_original_start_new_thread', None) is None:
834 if thread_module is threading:
835 if not hasattr(thread_module, '_start_new_thread'):
836 return # Jython doesn't have it.
837 _original_start_new_thread = thread_module._original_start_new_thread = thread_module._start_new_thread
838 else:
839 _original_start_new_thread = thread_module._original_start_new_thread = thread_module.start_new_thread
840 else:
841 _original_start_new_thread = thread_module._original_start_new_thread
843 class ClassWithPydevStartNewThread:
845 def pydev_start_new_thread(self, function, args=(), kwargs={}):
846 '''
847 We need to replace the original thread_module.start_new_thread with this function so that threads started
848 through it and not through the threading module are properly traced.
849 '''
850 return _original_start_new_thread(_UseNewThreadStartup(function, args, kwargs), ())
852 # This is a hack for the situation where the thread_module.start_new_thread is declared inside a class, such as the one below
853 # class F(object):
854 # start_new_thread = thread_module.start_new_thread
855 #
856 # def start_it(self):
857 # self.start_new_thread(self.function, args, kwargs)
858 # So, if it's an already bound method, calling self.start_new_thread won't really receive a different 'self' -- it
859 # does work in the default case because in builtins self isn't passed either.
860 pydev_start_new_thread = ClassWithPydevStartNewThread().pydev_start_new_thread
862 try:
863 # We need to replace the original thread_module.start_new_thread with this function so that threads started through
864 # it and not through the threading module are properly traced.
865 if thread_module is threading:
866 thread_module._start_new_thread = pydev_start_new_thread
867 else:
868 thread_module.start_new_thread = pydev_start_new_thread
869 thread_module.start_new = pydev_start_new_thread
870 except:
871 pass
874def patch_thread_modules():
875 for t in threading_modules_to_patch:
876 patch_thread_module(t)
879def undo_patch_thread_modules():
880 for t in threading_modules_to_patch:
881 try:
882 t.start_new_thread = t._original_start_new_thread
883 except:
884 pass
886 try:
887 t.start_new = t._original_start_new_thread
888 except:
889 pass
891 try:
892 t._start_new_thread = t._original_start_new_thread
893 except:
894 pass
897def disable_trace_thread_modules():
898 '''
899 Can be used to temporarily stop tracing threads created with thread.start_new_thread.
900 '''
901 global _UseNewThreadStartup
902 _UseNewThreadStartup = _NewThreadStartupWithoutTrace
905def enable_trace_thread_modules():
906 '''
907 Can be used to start tracing threads created with thread.start_new_thread again.
908 '''
909 global _UseNewThreadStartup
910 _UseNewThreadStartup = _NewThreadStartupWithTrace
913def get_original_start_new_thread(threading_module):
914 try:
915 return threading_module._original_start_new_thread
916 except:
917 return threading_module.start_new_thread