Files
Pavel Karateev dea1173a6e PY-83096 complete stdoutToServer and stderrToServer renaming
(cherry picked from commit f8d72fea4cf54d11f3fdb2e63c25191066e16b3a)

IJ-MR-171424

GitOrigin-RevId: 26db25e894cfd03ce4361aa7d0b819ec6b88af55
2025-08-08 16:57:39 +00:00

202 lines
7.8 KiB
Python

def get_main_thread_instance(threading):
if hasattr(threading, "main_thread"):
return threading.main_thread()
else:
# On Python 2 we don't really have an API to get the main thread,
# so, we just get it from the 'shutdown' bound method.
return threading._shutdown.im_self
def get_main_thread_id(unlikely_thread_id=None):
"""
:param unlikely_thread_id:
Pass to mark some thread id as not likely the main thread.
:return tuple(thread_id, critical_warning)
"""
import sys
import os
current_frames = sys._current_frames()
possible_thread_ids = []
for thread_ident, frame in current_frames.items():
while frame.f_back is not None:
frame = frame.f_back
basename = os.path.basename(frame.f_code.co_filename)
if basename.endswith((".pyc", ".pyo")):
basename = basename[:-1]
if (frame.f_code.co_name, basename) in [
("_run_module_as_main", "runpy.py"),
("_run_module_as_main", "<frozen runpy>"),
("run_module_as_main", "runpy.py"),
("run_module", "runpy.py"),
("run_path", "runpy.py"),
]:
# This is the case for python -m <module name> (this is an ideal match, so,
# let's return it).
return thread_ident, ""
if frame.f_code.co_name == "<module>":
if frame.f_globals.get("__name__") == "__main__":
possible_thread_ids.insert(0, thread_ident) # Add with higher priority
continue
# Usually the main thread will be started in the <module>, whereas others would
# be started in another place (but when Python is embedded, this may not be
# correct, so, just add to the available possibilities as we'll have to choose
# one if there are multiple).
possible_thread_ids.append(thread_ident)
if len(possible_thread_ids) > 0:
if len(possible_thread_ids) == 1:
return possible_thread_ids[0], "" # Ideal: only one match
while unlikely_thread_id in possible_thread_ids:
possible_thread_ids.remove(unlikely_thread_id)
if len(possible_thread_ids) == 1:
return possible_thread_ids[0], "" # Ideal: only one match
elif len(possible_thread_ids) > 1:
# Bad: we can't really be certain of anything at this point.
return possible_thread_ids[0], "Multiple thread ids found (%s). Choosing main thread id randomly (%s)." % (
possible_thread_ids,
possible_thread_ids[0],
)
# If we got here we couldn't discover the main thread id.
return None, "Unable to discover main thread id."
def fix_main_thread_id(on_warn=lambda msg: None, on_exception=lambda msg: None, on_critical=lambda msg: None):
# This means that we weren't able to import threading in the main thread (which most
# likely means that the main thread is paused or in some very long operation).
# In this case we'll import threading here and hotfix what may be wrong in the threading
# module (if we're on Windows where we create a thread to do the attach and on Linux
# we are not certain on which thread we're executing this code).
#
# The code below is a workaround for https://bugs.python.org/issue37416
import sys
import threading
# This is no longer needed in Py 3.13 (as the related issue is already fixed).
if sys.version_info[:2] >= (3, 13):
return
try:
with threading._active_limbo_lock:
main_thread_instance = get_main_thread_instance(threading)
if sys.platform == "win32":
# On windows this code would be called in a secondary thread, so,
# the current thread is unlikely to be the main thread.
if hasattr(threading, "_get_ident"):
unlikely_thread_id = threading._get_ident() # py2
else:
unlikely_thread_id = threading.get_ident() # py3
else:
unlikely_thread_id = None
main_thread_id, critical_warning = get_main_thread_id(unlikely_thread_id)
if main_thread_id is not None:
main_thread_id_attr = "_ident"
if not hasattr(main_thread_instance, main_thread_id_attr):
main_thread_id_attr = "_Thread__ident"
assert hasattr(main_thread_instance, main_thread_id_attr)
if main_thread_id != getattr(main_thread_instance, main_thread_id_attr):
# Note that we also have to reset the '_tstack_lock' for a regular lock.
# This is needed to avoid an error on shutdown because this lock is bound
# to the thread state and will be released when the secondary thread
# that initialized the lock is finished -- making an assert fail during
# process shutdown.
main_thread_instance._tstate_lock = threading._allocate_lock()
main_thread_instance._tstate_lock.acquire()
# Actually patch the thread ident as well as the threading._active dict
# (we should have the _active_limbo_lock to do that).
threading._active.pop(getattr(main_thread_instance, main_thread_id_attr), None)
setattr(main_thread_instance, main_thread_id_attr, main_thread_id)
threading._active[getattr(main_thread_instance, main_thread_id_attr)] = main_thread_instance
# Note: only import from pydevd after the patching is done (we want to do the minimum
# possible when doing that patching).
on_warn(
"The threading module was not imported by user code in the main thread. The debugger will attempt to work around https://bugs.python.org/issue37416."
)
if critical_warning:
on_critical("Issue found when debugger was trying to work around https://bugs.python.org/issue37416:\n%s" % (critical_warning,))
except:
on_exception("Error patching main thread id.")
def attach(port, host, protocol="", debug_mode=""):
try:
import sys
fix_main_thread = "threading" not in sys.modules
if fix_main_thread:
def on_warn(msg):
from _pydev_bundle import pydev_log
pydev_log.warn(msg)
def on_exception(msg):
from _pydev_bundle import pydev_log
pydev_log.exception(msg)
def on_critical(msg):
from _pydev_bundle import pydev_log
pydev_log.critical(msg)
fix_main_thread_id(on_warn=on_warn, on_exception=on_exception, on_critical=on_critical)
else:
from _pydev_bundle import pydev_log # @Reimport
pydev_log.debug("The threading module is already imported by user code.")
if protocol:
from _pydevd_bundle import pydevd_defaults
pydevd_defaults.PydevdCustomization.DEFAULT_PROTOCOL = protocol
if debug_mode:
from _pydevd_bundle import pydevd_defaults
pydevd_defaults.PydevdCustomization.DEBUG_MODE = debug_mode
import pydevd
# I.e.: disconnect/reset if already connected.
pydevd.SetupHolder.setup = None
py_db = pydevd.get_global_debugger()
if py_db is not None:
py_db.dispose_and_kill_all_pydevd_threads(wait=False)
# pydevd.DebugInfoHolder.DEBUG_TRACE_LEVEL = 3
pydevd.settrace(
port=port,
host=host,
stdout_to_server=True,
stderr_to_server=True,
overwrite_prev_trace=True,
suspend=False,
trace_only_current_thread=False,
patch_multiprocessing=False,
)
except:
import traceback
traceback.print_exc()