| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740 |
- """
- Entry point module (keep at root):
- This module starts the debugger.
- """
- import sys # @NoMove
- if sys.version_info[:2] < (3, 6):
- raise RuntimeError(
- "The PyDev.Debugger requires Python 3.6 onwards to be run. If you need to use an older Python version, use an older version of the debugger."
- )
- import os
- # On the first import of a pydevd module, add pydevd itself to the PYTHONPATH
- this_dir = os.path.dirname(os.path.abspath(__file__))
- sys.path.insert(0, this_dir)
- import _pydev_bundle
- # Import this first as it'll check for shadowed modules and will make sure that we import
- # things as needed for gevent.
- from _pydevd_bundle import pydevd_constants
- from typing import Optional, Tuple
- from types import FrameType
- import atexit
- import dis
- import io
- from collections import defaultdict
- from contextlib import contextmanager
- from functools import partial
- import itertools
- import traceback
- import weakref
- import getpass as getpass_mod
- import functools
- import pydevd_file_utils
- from _pydevd_bundle.pydevd_dont_trace_files import LIB_FILES_IN_DONT_TRACE_DIRS
- from _pydev_bundle import pydev_imports, pydev_log
- from _pydev_bundle._pydev_filesystem_encoding import getfilesystemencoding
- from _pydev_bundle.pydev_is_thread_alive import is_thread_alive
- from _pydev_bundle.pydev_override import overrides
- from _pydev_bundle._pydev_saved_modules import threading, time, thread, ThreadingEvent
- from _pydevd_bundle import pydevd_extension_utils, pydevd_frame_utils
- from _pydevd_bundle.pydevd_filtering import FilesFiltering, glob_matches_path
- from _pydevd_bundle import pydevd_io, pydevd_vm_type, pydevd_defaults
- from _pydevd_bundle import pydevd_utils
- from _pydevd_bundle import pydevd_runpy
- from _pydev_bundle.pydev_console_utils import DebugConsoleStdIn
- from _pydevd_bundle.pydevd_additional_thread_info import set_additional_thread_info, remove_additional_info
- from _pydevd_bundle.pydevd_breakpoints import ExceptionBreakpoint, get_exception_breakpoint
- from _pydevd_bundle.pydevd_comm_constants import (
- CMD_THREAD_SUSPEND,
- CMD_STEP_INTO,
- CMD_SET_BREAK,
- CMD_STEP_INTO_MY_CODE,
- CMD_STEP_OVER,
- CMD_SMART_STEP_INTO,
- CMD_RUN_TO_LINE,
- CMD_SET_NEXT_STATEMENT,
- CMD_STEP_RETURN,
- CMD_ADD_EXCEPTION_BREAK,
- CMD_STEP_RETURN_MY_CODE,
- CMD_STEP_OVER_MY_CODE,
- constant_to_str,
- CMD_STEP_INTO_COROUTINE,
- )
- from _pydevd_bundle.pydevd_constants import (
- get_thread_id,
- get_current_thread_id,
- DebugInfoHolder,
- PYTHON_SUSPEND,
- STATE_SUSPEND,
- STATE_RUN,
- get_frame,
- clear_cached_thread_id,
- INTERACTIVE_MODE_AVAILABLE,
- SHOW_DEBUG_INFO_ENV,
- NULL,
- NO_FTRACE,
- IS_IRONPYTHON,
- JSON_PROTOCOL,
- IS_CPYTHON,
- HTTP_JSON_PROTOCOL,
- USE_CUSTOM_SYS_CURRENT_FRAMES_MAP,
- call_only_once,
- ForkSafeLock,
- IGNORE_BASENAMES_STARTING_WITH,
- EXCEPTION_TYPE_UNHANDLED,
- SUPPORT_GEVENT,
- PYDEVD_IPYTHON_COMPATIBLE_DEBUGGING,
- PYDEVD_IPYTHON_CONTEXT,
- PYDEVD_USE_SYS_MONITORING,
- IS_PY314_OR_GREATER,
- )
- from _pydevd_bundle.pydevd_defaults import PydevdCustomization # Note: import alias used on pydev_monkey.
- from _pydevd_bundle.pydevd_custom_frames import CustomFramesContainer, custom_frames_container_init
- from _pydevd_bundle.pydevd_dont_trace_files import DONT_TRACE, PYDEV_FILE, LIB_FILE, DONT_TRACE_DIRS
- from _pydevd_bundle.pydevd_extension_api import DebuggerEventHandler
- from _pydevd_bundle.pydevd_frame_utils import add_exception_to_frame, remove_exception_from_frame, short_stack
- from _pydevd_bundle.pydevd_net_command_factory_xml import NetCommandFactory
- from _pydevd_bundle.pydevd_trace_dispatch import (
- trace_dispatch as _trace_dispatch,
- global_cache_skips,
- global_cache_frame_skips,
- fix_top_level_trace_and_get_trace_func,
- USING_CYTHON,
- )
- from _pydevd_bundle.pydevd_utils import save_main_module, is_current_thread_main_thread, import_attr_from_module
- from _pydevd_frame_eval.pydevd_frame_eval_main import frame_eval_func, dummy_trace_dispatch, USING_FRAME_EVAL
- import pydev_ipython # @UnusedImport
- from _pydevd_bundle.pydevd_source_mapping import SourceMapping
- from _pydevd_bundle.pydevd_concurrency_analyser.pydevd_concurrency_logger import (
- ThreadingLogger,
- AsyncioLogger,
- send_concurrency_message,
- cur_time,
- )
- from _pydevd_bundle.pydevd_concurrency_analyser.pydevd_thread_wrappers import wrap_threads
- from pydevd_file_utils import (
- get_abs_path_real_path_and_base_from_frame,
- get_abs_path_real_path_and_base_from_file,
- NORM_PATHS_AND_BASE_CONTAINER,
- )
- from pydevd_file_utils import get_fullname, get_package_dir
- from os.path import abspath as os_path_abspath
- import pydevd_tracing
- from _pydevd_bundle.pydevd_comm import InternalThreadCommand, InternalThreadCommandForAnyThread, create_server_socket, FSNotifyThread
- from _pydevd_bundle.pydevd_comm import (
- InternalConsoleExec,
- _queue,
- ReaderThread,
- GetGlobalDebugger,
- get_global_debugger,
- set_global_debugger,
- WriterThread,
- start_client,
- start_server,
- InternalGetBreakpointException,
- InternalSendCurrExceptionTrace,
- InternalSendCurrExceptionTraceProceeded,
- )
- from _pydevd_bundle.pydevd_daemon_thread import PyDBDaemonThread, mark_as_pydevd_daemon_thread
- from _pydevd_bundle.pydevd_process_net_command_json import PyDevJsonCommandProcessor
- from _pydevd_bundle.pydevd_process_net_command import process_net_command
- from _pydevd_bundle.pydevd_net_command import NetCommand, NULL_NET_COMMAND
- from _pydevd_bundle.pydevd_breakpoints import stop_on_unhandled_exception
- from _pydevd_bundle.pydevd_collect_bytecode_info import collect_try_except_info, collect_return_info, collect_try_except_info_from_source
- from _pydevd_bundle.pydevd_suspended_frames import SuspendedFramesManager
- from socket import SHUT_RDWR
- from _pydevd_bundle.pydevd_api import PyDevdAPI
- from _pydevd_bundle.pydevd_timeout import TimeoutTracker
- from _pydevd_bundle.pydevd_thread_lifecycle import suspend_all_threads, mark_thread_suspended
- if PYDEVD_USE_SYS_MONITORING:
- from _pydevd_sys_monitoring import pydevd_sys_monitoring
- pydevd_gevent_integration = None
- if SUPPORT_GEVENT:
- try:
- from _pydevd_bundle import pydevd_gevent_integration
- except:
- pydev_log.exception(
- "pydevd: GEVENT_SUPPORT is set but gevent is not available in the environment.\n"
- "Please unset GEVENT_SUPPORT from the environment variables or install gevent."
- )
- else:
- pydevd_gevent_integration.log_gevent_debug_info()
- if USE_CUSTOM_SYS_CURRENT_FRAMES_MAP:
- from _pydevd_bundle.pydevd_constants import constructed_tid_to_last_frame
- __version_info__ = (3, 2, 3)
- __version_info_str__ = []
- for v in __version_info__:
- __version_info_str__.append(str(v))
- __version__ = ".".join(__version_info_str__)
- # IMPORTANT: pydevd_constants must be the 1st thing defined because it'll keep a reference to the original sys._getframe
- def install_breakpointhook(pydevd_breakpointhook=None):
- if pydevd_breakpointhook is None:
- def pydevd_breakpointhook(*args, **kwargs):
- hookname = os.getenv("PYTHONBREAKPOINT")
- if (
- hookname is not None
- and len(hookname) > 0
- and hasattr(sys, "__breakpointhook__")
- and sys.__breakpointhook__ != pydevd_breakpointhook
- ):
- sys.__breakpointhook__(*args, **kwargs)
- else:
- settrace(*args, **kwargs)
- if sys.version_info[0:2] >= (3, 7):
- # There are some choices on how to provide the breakpoint hook. Namely, we can provide a
- # PYTHONBREAKPOINT which provides the import path for a method to be executed or we
- # can override sys.breakpointhook.
- # pydevd overrides sys.breakpointhook instead of providing an environment variable because
- # it's possible that the debugger starts the user program but is not available in the
- # PYTHONPATH (and would thus fail to be imported if PYTHONBREAKPOINT was set to pydevd.settrace).
- # Note that the implementation still takes PYTHONBREAKPOINT in account (so, if it was provided
- # by someone else, it'd still work).
- sys.breakpointhook = pydevd_breakpointhook
- else:
- if sys.version_info[0] >= 3:
- import builtins as __builtin__ # Py3 noqa
- else:
- import __builtin__ # noqa
- # In older versions, breakpoint() isn't really available, so, install the hook directly
- # in the builtins.
- __builtin__.breakpoint = pydevd_breakpointhook
- sys.__breakpointhook__ = pydevd_breakpointhook
- # Install the breakpoint hook at import time.
- install_breakpointhook()
- from _pydevd_bundle.pydevd_plugin_utils import PluginManager
- threadingEnumerate = threading.enumerate
- threadingCurrentThread = threading.current_thread
- try:
- "dummy".encode("utf-8") # Added because otherwise Jython 2.2.1 wasn't finding the encoding (if it wasn't loaded in the main thread).
- except:
- pass
- _global_redirect_stdout_to_server = False
- _global_redirect_stderr_to_server = False
- file_system_encoding = getfilesystemencoding()
- _CACHE_FILE_TYPE = {}
- pydev_log.debug("Using GEVENT_SUPPORT: %s", pydevd_constants.SUPPORT_GEVENT)
- pydev_log.debug("Using GEVENT_SHOW_PAUSED_GREENLETS: %s", pydevd_constants.GEVENT_SHOW_PAUSED_GREENLETS)
- pydev_log.debug("pydevd __file__: %s", os.path.abspath(__file__))
- pydev_log.debug("Using PYDEVD_IPYTHON_COMPATIBLE_DEBUGGING: %s", pydevd_constants.PYDEVD_IPYTHON_COMPATIBLE_DEBUGGING)
- if pydevd_constants.PYDEVD_IPYTHON_COMPATIBLE_DEBUGGING:
- pydev_log.debug("PYDEVD_IPYTHON_CONTEXT: %s", pydevd_constants.PYDEVD_IPYTHON_CONTEXT)
- TIMEOUT_SLOW = 0.2
- TIMEOUT_FAST = 1.0 / 50
- # =======================================================================================================================
- # PyDBCommandThread
- # =======================================================================================================================
- class PyDBCommandThread(PyDBDaemonThread):
- def __init__(self, py_db):
- PyDBDaemonThread.__init__(self, py_db)
- self._py_db_command_thread_event = py_db._py_db_command_thread_event
- self.name = "pydevd.CommandThread"
- @overrides(PyDBDaemonThread._on_run)
- def _on_run(self):
- # Delay a bit this initialization to wait for the main program to start.
- self._py_db_command_thread_event.wait(TIMEOUT_SLOW)
- if self._kill_received:
- return
- try:
- while not self._kill_received:
- try:
- self.py_db.process_internal_commands(("*",))
- except:
- pydev_log.info("Finishing debug communication...(2)")
- self._py_db_command_thread_event.clear()
- self._py_db_command_thread_event.wait(TIMEOUT_SLOW)
- except:
- try:
- pydev_log.debug(sys.exc_info()[0])
- except:
- # In interpreter shutdown many things can go wrong (any module variables may
- # be None, streams can be closed, etc).
- pass
- # only got this error in interpreter shutdown
- # pydev_log.info('Finishing debug communication...(3)')
- @overrides(PyDBDaemonThread.do_kill_pydev_thread)
- def do_kill_pydev_thread(self):
- PyDBDaemonThread.do_kill_pydev_thread(self)
- # Set flag so that it can exit before the usual timeout.
- self._py_db_command_thread_event.set()
- # =======================================================================================================================
- # CheckAliveThread
- # Non-daemon thread: guarantees that all data is written even if program is finished
- # =======================================================================================================================
- class CheckAliveThread(PyDBDaemonThread):
- def __init__(self, py_db):
- PyDBDaemonThread.__init__(self, py_db)
- self.name = "pydevd.CheckAliveThread"
- self.daemon = False
- self._wait_event = ThreadingEvent()
- @overrides(PyDBDaemonThread._on_run)
- def _on_run(self):
- py_db = self.py_db
- def can_exit():
- with py_db._main_lock:
- # Note: it's important to get the lock besides checking that it's empty (this
- # means that we're not in the middle of some command processing).
- writer = py_db.writer
- writer_empty = writer is not None and writer.empty()
- return not py_db.has_user_threads_alive() and writer_empty
- try:
- while not self._kill_received:
- self._wait_event.wait(TIMEOUT_SLOW)
- if can_exit():
- break
- py_db.check_output_redirect()
- if can_exit():
- pydev_log.debug("No threads alive, finishing debug session")
- py_db.dispose_and_kill_all_pydevd_threads()
- except:
- pydev_log.exception()
- def join(self, timeout=None):
- # If someone tries to join this thread, mark it to be killed.
- # This is the case for CherryPy when auto-reload is turned on.
- self.do_kill_pydev_thread()
- PyDBDaemonThread.join(self, timeout=timeout)
- @overrides(PyDBDaemonThread.do_kill_pydev_thread)
- def do_kill_pydev_thread(self):
- PyDBDaemonThread.do_kill_pydev_thread(self)
- # Set flag so that it can exit before the usual timeout.
- self._wait_event.set()
- class AbstractSingleNotificationBehavior(object):
- """
- The basic usage should be:
- # Increment the request time for the suspend.
- single_notification_behavior.increment_suspend_time()
- # Notify that this is a pause request (when a pause, not a breakpoint).
- single_notification_behavior.on_pause()
- # Mark threads to be suspended.
- set_suspend(...)
- # On do_wait_suspend, use notify_thread_suspended:
- def do_wait_suspend(...):
- with single_notification_behavior.notify_thread_suspended(thread_id, thread, reason):
- ...
- """
- __slots__ = [
- "_last_resume_notification_time",
- "_last_suspend_notification_time",
- "_lock",
- "_next_request_time",
- "_suspend_time_request",
- "_suspended_thread_id_to_thread",
- "_pause_requested",
- "_py_db",
- ]
- NOTIFY_OF_PAUSE_TIMEOUT = 0.5
- def __init__(self, py_db):
- self._py_db = weakref.ref(py_db)
- self._next_request_time = partial(next, itertools.count())
- self._last_suspend_notification_time = -1
- self._last_resume_notification_time = -1
- self._suspend_time_request = self._next_request_time()
- self._lock = thread.allocate_lock()
- self._suspended_thread_id_to_thread = {}
- self._pause_requested = False
- def send_suspend_notification(self, thread_id, thread, stop_reason):
- raise AssertionError("abstract: subclasses must override.")
- def send_resume_notification(self, thread_id):
- raise AssertionError("abstract: subclasses must override.")
- def increment_suspend_time(self):
- with self._lock:
- self._suspend_time_request = self._next_request_time()
- def on_pause(self):
- # Upon a pause, we should force sending new suspend notifications
- # if no notification is sent after some time and there's some thread already stopped.
- with self._lock:
- self._pause_requested = True
- global_suspend_time = self._suspend_time_request
- py_db = self._py_db()
- if py_db is not None:
- py_db.timeout_tracker.call_on_timeout(
- self.NOTIFY_OF_PAUSE_TIMEOUT, self._notify_after_timeout, kwargs={"global_suspend_time": global_suspend_time}
- )
- def _notify_after_timeout(self, global_suspend_time):
- with self._lock:
- if self._suspended_thread_id_to_thread:
- if global_suspend_time > self._last_suspend_notification_time:
- self._last_suspend_notification_time = global_suspend_time
- # Notify about any thread which is currently suspended.
- pydev_log.info("Sending suspend notification after timeout.")
- thread_id, thread = next(iter(self._suspended_thread_id_to_thread.items()))
- self.send_suspend_notification(thread_id, thread, CMD_THREAD_SUSPEND)
- def on_thread_suspend(self, thread_id, thread, stop_reason):
- with self._lock:
- pause_requested = self._pause_requested
- if pause_requested:
- # When a suspend notification is sent, reset the pause flag.
- self._pause_requested = False
- self._suspended_thread_id_to_thread[thread_id] = thread
- # CMD_THREAD_SUSPEND should always be a side-effect of a break, so, only
- # issue for a CMD_THREAD_SUSPEND if a pause is pending.
- if stop_reason != CMD_THREAD_SUSPEND or pause_requested:
- if self._suspend_time_request > self._last_suspend_notification_time:
- pydev_log.info("Sending suspend notification.")
- self._last_suspend_notification_time = self._suspend_time_request
- self.send_suspend_notification(thread_id, thread, stop_reason)
- else:
- pydev_log.info(
- "Suspend not sent (it was already sent). Last suspend % <= Last resume %s",
- self._last_suspend_notification_time,
- self._last_resume_notification_time,
- )
- else:
- pydev_log.info(
- "Suspend not sent because stop reason is thread suspend and pause was not requested.",
- )
- def on_thread_resume(self, thread_id, thread):
- # on resume (step, continue all):
- with self._lock:
- self._suspended_thread_id_to_thread.pop(thread_id)
- if self._last_resume_notification_time < self._last_suspend_notification_time:
- pydev_log.info("Sending resume notification.")
- self._last_resume_notification_time = self._last_suspend_notification_time
- self.send_resume_notification(thread_id)
- else:
- pydev_log.info(
- "Resume not sent (it was already sent). Last resume %s >= Last suspend %s",
- self._last_resume_notification_time,
- self._last_suspend_notification_time,
- )
- @contextmanager
- def notify_thread_suspended(self, thread_id, thread, stop_reason):
- self.on_thread_suspend(thread_id, thread, stop_reason)
- try:
- yield # At this point the thread must be actually suspended.
- finally:
- self.on_thread_resume(thread_id, thread)
- class ThreadsSuspendedSingleNotification(AbstractSingleNotificationBehavior):
- __slots__ = AbstractSingleNotificationBehavior.__slots__ + ["multi_threads_single_notification", "_callbacks", "_callbacks_lock"]
- def __init__(self, py_db):
- AbstractSingleNotificationBehavior.__init__(self, py_db)
- # If True, pydevd will send a single notification when all threads are suspended/resumed.
- self.multi_threads_single_notification = False
- self._callbacks_lock = threading.Lock()
- self._callbacks = []
- def add_on_resumed_callback(self, callback):
- with self._callbacks_lock:
- self._callbacks.append(callback)
- @overrides(AbstractSingleNotificationBehavior.send_resume_notification)
- def send_resume_notification(self, thread_id):
- py_db = self._py_db()
- if py_db is not None:
- py_db.writer.add_command(py_db.cmd_factory.make_thread_resume_single_notification(thread_id))
- with self._callbacks_lock:
- callbacks = self._callbacks
- self._callbacks = []
- for callback in callbacks:
- callback()
- @overrides(AbstractSingleNotificationBehavior.send_suspend_notification)
- def send_suspend_notification(self, thread_id, thread, stop_reason):
- py_db = self._py_db()
- if py_db is not None:
- py_db.writer.add_command(py_db.cmd_factory.make_thread_suspend_single_notification(py_db, thread_id, thread, stop_reason))
- @overrides(AbstractSingleNotificationBehavior.notify_thread_suspended)
- @contextmanager
- def notify_thread_suspended(self, thread_id, thread, stop_reason):
- if self.multi_threads_single_notification:
- pydev_log.info("Thread suspend mode: single notification")
- with AbstractSingleNotificationBehavior.notify_thread_suspended(self, thread_id, thread, stop_reason):
- yield
- else:
- pydev_log.info("Thread suspend mode: NOT single notification")
- yield
- class _Authentication(object):
- __slots__ = ["access_token", "client_access_token", "_authenticated", "_wrong_attempts"]
- def __init__(self):
- # A token to be send in the command line or through the settrace api -- when such token
- # is given, the first message sent to the IDE must pass the same token to authenticate.
- # Note that if a disconnect is sent, the same message must be resent to authenticate.
- self.access_token = None
- # This token is the one that the client requires to accept a connection from pydevd
- # (it's stored here and just passed back when required, it's not used internally
- # for anything else).
- self.client_access_token = None
- self._authenticated = None
- self._wrong_attempts = 0
- def is_authenticated(self):
- if self._authenticated is None:
- return self.access_token is None
- return self._authenticated
- def login(self, access_token):
- if self._wrong_attempts >= 10: # A user can fail to authenticate at most 10 times.
- return
- self._authenticated = access_token == self.access_token
- if not self._authenticated:
- self._wrong_attempts += 1
- else:
- self._wrong_attempts = 0
- def logout(self):
- self._authenticated = None
- self._wrong_attempts = 0
- class PyDB(object):
- """Main debugging class
- Lots of stuff going on here:
- PyDB starts two threads on startup that connect to remote debugger (RDB)
- The threads continuously read & write commands to RDB.
- PyDB communicates with these threads through command queues.
- Every RDB command is processed by calling process_net_command.
- Every PyDB net command is sent to the net by posting NetCommand to WriterThread queue
- Some commands need to be executed on the right thread (suspend/resume & friends)
- These are placed on the internal command queue.
- """
- # Direct child pids which should not be terminated when terminating processes.
- # Note: class instance because it should outlive PyDB instances.
- dont_terminate_child_pids = set()
- def __init__(self, set_as_global=True):
- if set_as_global:
- pydevd_tracing.replace_sys_set_trace_func()
- self.authentication = _Authentication()
- self.reader = None
- self.writer = None
- self._fsnotify_thread = None
- self.created_pydb_daemon_threads = {}
- self._waiting_for_connection_thread = None
- self._on_configuration_done_event = ThreadingEvent()
- self.check_alive_thread = None
- self.py_db_command_thread = None
- self.quitting = None
- self.cmd_factory = NetCommandFactory()
- self._cmd_queue = defaultdict(_queue.Queue) # Key is thread id or '*', value is Queue
- self._thread_events = defaultdict(ThreadingEvent) # Key is thread id or '*', value is Event
- self.suspended_frames_manager = SuspendedFramesManager()
- self._files_filtering = FilesFiltering()
- self.timeout_tracker = TimeoutTracker(self)
- # Note: when the source mapping is changed we also have to clear the file types cache
- # (because if a given file is a part of the project or not may depend on it being
- # defined in the source mapping).
- self.source_mapping = SourceMapping(on_source_mapping_changed=self._clear_caches)
- # Determines whether we should terminate child processes when asked to terminate.
- self.terminate_child_processes = True
- # Determines whether we should try to do a soft terminate (i.e.: interrupt the main
- # thread with a KeyboardInterrupt).
- self.terminate_keyboard_interrupt = False
- # Set to True after a keyboard interrupt is requested the first time.
- self.keyboard_interrupt_requested = False
- # These are the breakpoints received by the PyDevdAPI. They are meant to store
- # the breakpoints in the api -- its actual contents are managed by the api.
- self.api_received_breakpoints = {}
- # These are the breakpoints meant to be consumed during runtime.
- self.breakpoints = {}
- self.function_breakpoint_name_to_breakpoint = {}
- # Set communication protocol
- PyDevdAPI().set_protocol(self, 0, PydevdCustomization.DEFAULT_PROTOCOL)
- self.variable_presentation = PyDevdAPI.VariablePresentation()
- # mtime to be raised when something that will affect the
- # tracing in place (such as breakpoints change or filtering).
- self.mtime = 0
- self.file_to_id_to_line_breakpoint = {}
- self.file_to_id_to_plugin_breakpoint = {}
- # Note: breakpoints dict should not be mutated: a copy should be created
- # and later it should be assigned back (to prevent concurrency issues).
- self.break_on_uncaught_exceptions = {}
- self.break_on_caught_exceptions = {}
- self.break_on_user_uncaught_exceptions = {}
- self.ready_to_run = False
- self._main_lock = thread.allocate_lock()
- self._lock_running_thread_ids = thread.allocate_lock()
- self._lock_create_fs_notify = thread.allocate_lock()
- self._py_db_command_thread_event = ThreadingEvent()
- if set_as_global:
- CustomFramesContainer._py_db_command_thread_event = self._py_db_command_thread_event
- self.pydb_disposed = False
- self._wait_for_threads_to_finish_called = False
- self._wait_for_threads_to_finish_called_lock = thread.allocate_lock()
- self._wait_for_threads_to_finish_called_event = ThreadingEvent()
- self.terminate_requested = False
- self._disposed_lock = thread.allocate_lock()
- self.signature_factory = None
- self.SetTrace = pydevd_tracing.SetTrace
- self.skip_on_exceptions_thrown_in_same_context = False
- self.ignore_exceptions_thrown_in_lines_with_ignore_exception = True
- # Suspend debugger even if breakpoint condition raises an exception.
- # May be changed with CMD_PYDEVD_JSON_CONFIG.
- self.skip_suspend_on_breakpoint_exception = () # By default suspend on any Exception.
- self.skip_print_breakpoint_exception = () # By default print on any Exception.
- # By default user can step into properties getter/setter/deleter methods
- self.disable_property_trace = False
- self.disable_property_getter_trace = False
- self.disable_property_setter_trace = False
- self.disable_property_deleter_trace = False
- # this is a dict of thread ids pointing to thread ids. Whenever a command is passed to the java end that
- # acknowledges that a thread was created, the thread id should be passed here -- and if at some time we do not
- # find that thread alive anymore, we must remove it from this list and make the java side know that the thread
- # was killed.
- self._running_thread_ids = {}
- # Note: also access '_enable_thread_notifications' with '_lock_running_thread_ids'
- self._enable_thread_notifications = False
- self._set_breakpoints_with_id = False
- # This attribute holds the file-> lines which have an @IgnoreException.
- self.filename_to_lines_where_exceptions_are_ignored = {}
- # working with plugins (lazily initialized)
- self.plugin = None
- self.has_plugin_line_breaks = False
- self.has_plugin_exception_breaks = False
- self.thread_analyser = None
- self.asyncio_analyser = None
- # The GUI event loop that's going to run.
- # Possible values:
- # matplotlib - Whatever GUI backend matplotlib is using.
- # 'wx'/'qt'/'none'/... - GUI toolkits that have bulitin support. See pydevd_ipython/inputhook.py:24.
- # Other - A custom function that'll be imported and run.
- self._gui_event_loop = "matplotlib"
- self._installed_gui_support = False
- self.gui_in_use = False
- # GUI event loop support in debugger
- self.activate_gui_function = None
- # matplotlib support in debugger and debug console
- self.mpl_hooks_in_debug_console = False
- self.mpl_modules_for_patching = {}
- self._filename_to_not_in_scope = {}
- self.first_breakpoint_reached = False
- self._exclude_filters_enabled = self._files_filtering.use_exclude_filters()
- self._is_libraries_filter_enabled = self._files_filtering.use_libraries_filter()
- self.is_files_filter_enabled = self._exclude_filters_enabled or self._is_libraries_filter_enabled
- self.show_return_values = False
- self.remove_return_values_flag = False
- self.redirect_output = False
- # Note that besides the `redirect_output` flag, we also need to consider that someone
- # else is already redirecting (i.e.: debugpy).
- self.is_output_redirected = False
- # this flag disables frame evaluation even if it's available
- self.use_frame_eval = True
- # If True, pydevd will send a single notification when all threads are suspended/resumed.
- self._threads_suspended_single_notification = ThreadsSuspendedSingleNotification(self)
- # If True a step command will do a step in one thread and will also resume all other threads.
- self.stepping_resumes_all_threads = False
- self._local_thread_trace_func = threading.local()
- self._client_socket = None
- self._server_socket_ready_event = ThreadingEvent()
- self._server_socket_name = None
- # Bind many locals to the debugger because upon teardown those names may become None
- # in the namespace (and thus can't be relied upon unless the reference was previously
- # saved).
- if IS_IRONPYTHON:
- # A partial() cannot be used in IronPython for sys.settrace.
- def new_trace_dispatch(frame, event, arg):
- return _trace_dispatch(self, frame, event, arg)
- self.trace_dispatch = new_trace_dispatch
- else:
- self.trace_dispatch = partial(_trace_dispatch, self)
- self.fix_top_level_trace_and_get_trace_func = fix_top_level_trace_and_get_trace_func
- self.frame_eval_func = frame_eval_func
- self.dummy_trace_dispatch = dummy_trace_dispatch
- # Note: this is different from pydevd_constants.thread_get_ident because we want Jython
- # to be None here because it also doesn't have threading._active.
- try:
- self.threading_get_ident = threading.get_ident # Python 3
- self.threading_active = threading._active
- except:
- try:
- self.threading_get_ident = threading._get_ident # Python 2 noqa
- self.threading_active = threading._active
- except:
- self.threading_get_ident = None # Jython
- self.threading_active = None
- self.threading_current_thread = threading.currentThread
- self.set_additional_thread_info = set_additional_thread_info
- self.stop_on_unhandled_exception = stop_on_unhandled_exception
- self.collect_return_info = collect_return_info
- self.get_exception_breakpoint = get_exception_breakpoint
- self._dont_trace_get_file_type = DONT_TRACE.get
- self._dont_trace_dirs_get_file_type = DONT_TRACE_DIRS.get
- self.PYDEV_FILE = PYDEV_FILE
- self.LIB_FILE = LIB_FILE
- self._in_project_scope_cache = {}
- self._exclude_by_filter_cache = {}
- self._apply_filter_cache = {}
- self._ignore_system_exit_codes = set()
- # DAP related
- self._dap_messages_listeners = []
- if set_as_global:
- # Set as the global instance only after it's initialized.
- set_global_debugger(self)
- pydevd_defaults.on_pydb_init(self)
- # Stop the tracing as the last thing before the actual shutdown for a clean exit.
- atexit.register(stoptrace)
- def collect_try_except_info(self, code_obj):
- filename = code_obj.co_filename
- try:
- if os.path.exists(filename):
- pydev_log.debug("Collecting try..except info from source for %s", filename)
- try_except_infos = collect_try_except_info_from_source(filename)
- if try_except_infos:
- # Filter for the current function
- max_line = -1
- min_line = sys.maxsize
- for _, line in dis.findlinestarts(code_obj):
- if line is not None:
- if line > max_line:
- max_line = line
- if line < min_line:
- min_line = line
- try_except_infos = [x for x in try_except_infos if min_line <= x.try_line <= max_line]
- return try_except_infos
- except:
- pydev_log.exception("Error collecting try..except info from source (%s)", filename)
- pydev_log.debug("Collecting try..except info from bytecode for %s", filename)
- return collect_try_except_info(code_obj)
- def setup_auto_reload_watcher(self, enable_auto_reload, watch_dirs, poll_target_time, exclude_patterns, include_patterns):
- try:
- with self._lock_create_fs_notify:
- # When setting up, dispose of the previous one (if any).
- if self._fsnotify_thread is not None:
- self._fsnotify_thread.do_kill_pydev_thread()
- self._fsnotify_thread = None
- if not enable_auto_reload:
- return
- exclude_patterns = tuple(exclude_patterns)
- include_patterns = tuple(include_patterns)
- def accept_directory(absolute_filename, cache={}):
- try:
- return cache[absolute_filename]
- except:
- if absolute_filename and absolute_filename[-1] not in ("/", "\\"):
- # I.e.: for directories we always end with '/' or '\\' so that
- # we match exclusions such as "**/node_modules/**"
- absolute_filename += os.path.sep
- # First include what we want
- for include_pattern in include_patterns:
- if glob_matches_path(absolute_filename, include_pattern):
- cache[absolute_filename] = True
- return True
- # Then exclude what we don't want
- for exclude_pattern in exclude_patterns:
- if glob_matches_path(absolute_filename, exclude_pattern):
- cache[absolute_filename] = False
- return False
- # By default track all directories not excluded.
- cache[absolute_filename] = True
- return True
- def accept_file(absolute_filename, cache={}):
- try:
- return cache[absolute_filename]
- except:
- # First include what we want
- for include_pattern in include_patterns:
- if glob_matches_path(absolute_filename, include_pattern):
- cache[absolute_filename] = True
- return True
- # Then exclude what we don't want
- for exclude_pattern in exclude_patterns:
- if glob_matches_path(absolute_filename, exclude_pattern):
- cache[absolute_filename] = False
- return False
- # By default don't track files not included.
- cache[absolute_filename] = False
- return False
- self._fsnotify_thread = FSNotifyThread(self, PyDevdAPI(), watch_dirs)
- watcher = self._fsnotify_thread.watcher
- watcher.accept_directory = accept_directory
- watcher.accept_file = accept_file
- watcher.target_time_for_single_scan = poll_target_time
- watcher.target_time_for_notification = poll_target_time
- self._fsnotify_thread.start()
- except:
- pydev_log.exception("Error setting up auto-reload.")
- def get_arg_ppid(self):
- try:
- setup = SetupHolder.setup
- if setup:
- return int(setup.get("ppid", 0))
- except:
- pydev_log.exception("Error getting ppid.")
- return 0
- def wait_for_ready_to_run(self):
- while not self.ready_to_run:
- # busy wait until we receive run command
- self.process_internal_commands()
- self._py_db_command_thread_event.clear()
- self._py_db_command_thread_event.wait(TIMEOUT_FAST)
- def on_initialize(self):
- """
- Note: only called when using the DAP (Debug Adapter Protocol).
- """
- self._on_configuration_done_event.clear()
- def on_configuration_done(self):
- """
- Note: only called when using the DAP (Debug Adapter Protocol).
- """
- self._on_configuration_done_event.set()
- self._py_db_command_thread_event.set()
- def is_attached(self):
- return self._on_configuration_done_event.is_set()
- def on_disconnect(self):
- """
- Note: only called when using the DAP (Debug Adapter Protocol).
- """
- self.authentication.logout()
- self._on_configuration_done_event.clear()
- def set_ignore_system_exit_codes(self, ignore_system_exit_codes):
- assert isinstance(ignore_system_exit_codes, (list, tuple, set))
- self._ignore_system_exit_codes = set(ignore_system_exit_codes)
- def ignore_system_exit_code(self, system_exit_exc):
- if hasattr(system_exit_exc, "code"):
- return system_exit_exc.code in self._ignore_system_exit_codes
- else:
- return system_exit_exc in self._ignore_system_exit_codes
- def block_until_configuration_done(self, cancel=None):
- if cancel is None:
- cancel = NULL
- while not cancel.is_set():
- if self._on_configuration_done_event.is_set():
- cancel.set() # Set cancel to prevent reuse
- return
- self.process_internal_commands()
- self._py_db_command_thread_event.clear()
- self._py_db_command_thread_event.wait(TIMEOUT_FAST)
- def add_fake_frame(self, thread_id, frame_id, frame):
- self.suspended_frames_manager.add_fake_frame(thread_id, frame_id, frame)
- def handle_breakpoint_condition(self, info, pybreakpoint, new_frame):
- condition = pybreakpoint.condition
- try:
- if pybreakpoint.handle_hit_condition(new_frame):
- return True
- if not condition:
- return False
- return eval(condition, new_frame.f_globals, new_frame.f_locals)
- except Exception as e:
- if not isinstance(e, self.skip_print_breakpoint_exception):
- stack_trace = io.StringIO()
- etype, value, tb = sys.exc_info()
- traceback.print_exception(etype, value, tb.tb_next, file=stack_trace)
- msg = "Error while evaluating expression in conditional breakpoint: %s\n%s" % (condition, stack_trace.getvalue())
- api = PyDevdAPI()
- api.send_error_message(self, msg)
- if not isinstance(e, self.skip_suspend_on_breakpoint_exception):
- try:
- # add exception_type and stacktrace into thread additional info
- etype, value, tb = sys.exc_info()
- error = "".join(traceback.format_exception_only(etype, value))
- stack = traceback.extract_stack(f=tb.tb_frame.f_back)
- # On self.set_suspend(thread, CMD_SET_BREAK) this info will be
- # sent to the client.
- info.conditional_breakpoint_exception = ("Condition:\n" + condition + "\n\nError:\n" + error, stack)
- except:
- pydev_log.exception()
- return True
- return False
- finally:
- etype, value, tb = None, None, None
- def handle_breakpoint_expression(self, pybreakpoint, info, new_frame):
- try:
- try:
- val = eval(pybreakpoint.expression, new_frame.f_globals, new_frame.f_locals)
- except:
- val = sys.exc_info()[1]
- finally:
- if val is not None:
- info.pydev_message = str(val)
- def _internal_get_file_type(self, abs_real_path_and_basename):
- basename = abs_real_path_and_basename[-1]
- if basename.startswith(IGNORE_BASENAMES_STARTING_WITH) or abs_real_path_and_basename[0].startswith(IGNORE_BASENAMES_STARTING_WITH):
- # Note: these are the files that are completely ignored (they aren't shown to the user
- # as user nor library code as it's usually just noise in the frame stack).
- return self.PYDEV_FILE
- file_type = self._dont_trace_get_file_type(basename)
- if file_type is not None:
- return file_type
- if basename.startswith("__init__.py") or basename in LIB_FILES_IN_DONT_TRACE_DIRS:
- # i.e.: ignore the __init__ files inside pydevd (the other
- # files are ignored just by their name).
- abs_path = abs_real_path_and_basename[0]
- i = max(abs_path.rfind("/"), abs_path.rfind("\\"))
- if i:
- abs_path = abs_path[0:i]
- i = max(abs_path.rfind("/"), abs_path.rfind("\\"))
- if i:
- dirname = abs_path[i + 1:]
- # At this point, something as:
- # "my_path\_pydev_runfiles\__init__.py"
- # is now "_pydev_runfiles".
- return self._dont_trace_dirs_get_file_type(dirname)
- return None
- def dont_trace_external_files(self, abs_path):
- """
- :param abs_path:
- The result from get_abs_path_real_path_and_base_from_file or
- get_abs_path_real_path_and_base_from_frame.
- :return
- True :
- If files should NOT be traced.
- False:
- If files should be traced.
- """
- # By default all external files are traced. Note: this function is expected to
- # be changed for another function in PyDevdAPI.set_dont_trace_start_end_patterns.
- return False
- def get_file_type(self, frame, abs_real_path_and_basename=None, _cache_file_type=_CACHE_FILE_TYPE):
- """
- :param abs_real_path_and_basename:
- The result from get_abs_path_real_path_and_base_from_file or
- get_abs_path_real_path_and_base_from_frame.
- :return
- _pydevd_bundle.pydevd_dont_trace_files.PYDEV_FILE:
- If it's a file internal to the debugger which shouldn't be
- traced nor shown to the user.
- _pydevd_bundle.pydevd_dont_trace_files.LIB_FILE:
- If it's a file in a library which shouldn't be traced.
- None:
- If it's a regular user file which should be traced.
- """
- if abs_real_path_and_basename is None:
- try:
- # Make fast path faster!
- abs_real_path_and_basename = NORM_PATHS_AND_BASE_CONTAINER[frame.f_code.co_filename]
- except:
- abs_real_path_and_basename = get_abs_path_real_path_and_base_from_frame(frame)
- # Note 1: we have to take into account that we may have files as '<string>', and that in
- # this case the cache key can't rely only on the filename. With the current cache, there's
- # still a potential miss if 2 functions which have exactly the same content are compiled
- # with '<string>', but in practice as we only separate the one from python -c from the rest
- # this shouldn't be a problem in practice.
- # Note 2: firstlineno added to make misses faster in the first comparison.
- # Note 3: this cache key is repeated in pydevd_frame_evaluator.pyx:get_func_code_info (for
- # speedups).
- cache_key = (frame.f_code.co_firstlineno, abs_real_path_and_basename[0], frame.f_code)
- try:
- return _cache_file_type[cache_key]
- except:
- if abs_real_path_and_basename[0] == "<string>":
- f = frame.f_back
- while f is not None:
- if self.get_file_type(f) != self.PYDEV_FILE and pydevd_file_utils.basename(f.f_code.co_filename) not in (
- "runpy.py",
- "<string>",
- ):
- # We found some back frame that's not internal, which means we must consider
- # this a library file.
- # This is done because we only want to trace files as <string> if they don't
- # have any back frame (which is the case for python -c ...), for all other
- # cases we don't want to trace them because we can't show the source to the
- # user (at least for now...).
- # Note that we return as a LIB_FILE and not PYDEV_FILE because we still want
- # to show it in the stack.
- _cache_file_type[cache_key] = LIB_FILE
- return LIB_FILE
- f = f.f_back
- else:
- # This is a top-level file (used in python -c), so, trace it as usual... we
- # still won't be able to show the sources, but some tests require this to work.
- _cache_file_type[cache_key] = None
- return None
- file_type = self._internal_get_file_type(abs_real_path_and_basename)
- if file_type is None:
- if self.dont_trace_external_files(abs_real_path_and_basename[0]):
- file_type = PYDEV_FILE
- _cache_file_type[cache_key] = file_type
- return file_type
- def is_cache_file_type_empty(self):
- return not _CACHE_FILE_TYPE
- def get_cache_file_type(self, _cache=_CACHE_FILE_TYPE): # i.e.: Make it local.
- return _cache
- def get_thread_local_trace_func(self):
- try:
- thread_trace_func = self._local_thread_trace_func.thread_trace_func
- except AttributeError:
- thread_trace_func = self.trace_dispatch
- return thread_trace_func
- def enable_tracing(self, thread_trace_func=None, apply_to_all_threads=False):
- """
- Enables tracing.
- If in regular mode (tracing), will set the tracing function to the tracing
- function for this thread -- by default it's `PyDB.trace_dispatch`, but after
- `PyDB.enable_tracing` is called with a `thread_trace_func`, the given function will
- be the default for the given thread.
- :param bool apply_to_all_threads:
- If True we'll set the tracing function in all threads, not only in the current thread.
- If False only the tracing for the current function should be changed.
- In general apply_to_all_threads should only be true if this is the first time
- this function is called on a multi-threaded program (either programmatically or attach
- to pid).
- """
- if PYDEVD_USE_SYS_MONITORING:
- pydevd_sys_monitoring.start_monitoring(all_threads=apply_to_all_threads)
- return
- if pydevd_gevent_integration is not None:
- pydevd_gevent_integration.enable_gevent_integration()
- if self.frame_eval_func is not None:
- self.frame_eval_func()
- pydevd_tracing.SetTrace(self.dummy_trace_dispatch)
- if IS_CPYTHON and apply_to_all_threads:
- pydevd_tracing.set_trace_to_threads(self.dummy_trace_dispatch)
- return
- if apply_to_all_threads:
- # If applying to all threads, don't use the local thread trace function.
- assert thread_trace_func is not None
- else:
- if thread_trace_func is None:
- thread_trace_func = self.get_thread_local_trace_func()
- else:
- self._local_thread_trace_func.thread_trace_func = thread_trace_func
- pydevd_tracing.SetTrace(thread_trace_func)
- if IS_CPYTHON and apply_to_all_threads:
- pydevd_tracing.set_trace_to_threads(thread_trace_func)
- def disable_tracing(self):
- if PYDEVD_USE_SYS_MONITORING:
- pydevd_sys_monitoring.stop_monitoring(all_threads=False)
- else:
- pydevd_tracing.SetTrace(None)
- def on_breakpoints_changed(self, removed=False):
- """
- When breakpoints change, we have to re-evaluate all the assumptions we've made so far.
- """
- if not self.ready_to_run:
- # No need to do anything if we're still not running.
- return
- self.mtime += 1
- if not removed:
- # When removing breakpoints we can leave tracing as was, but if a breakpoint was added
- # we have to reset the tracing for the existing functions to be re-evaluated.
- # The caches also need to be cleared because of django breakpoints use case,
- # where adding a file needs to start tracking a context which was previously
- # untracked.
- self._clear_caches()
- self.set_tracing_for_untraced_contexts(breakpoints_changed=True)
- def set_tracing_for_untraced_contexts(self, breakpoints_changed=False):
- # Enable the tracing for existing threads (because there may be frames being executed that
- # are currently untraced).
- if PYDEVD_USE_SYS_MONITORING and breakpoints_changed:
- pydevd_sys_monitoring.update_monitor_events()
- if IS_CPYTHON:
- # Note: use sys._current_frames instead of threading.enumerate() because this way
- # we also see C/C++ threads, not only the ones visible to the threading module.
- tid_to_frame = sys._current_frames()
- ignore_thread_ids = set(
- t.ident
- for t in threadingEnumerate()
- if getattr(t, "is_pydev_daemon_thread", False) or getattr(t, "pydev_do_not_trace", False)
- )
- for thread_ident, frame in tid_to_frame.items():
- if thread_ident not in ignore_thread_ids:
- self.set_trace_for_frame_and_parents(thread_ident, frame)
- else:
- try:
- threads = threadingEnumerate()
- for t in threads:
- if getattr(t, "is_pydev_daemon_thread", False) or getattr(t, "pydev_do_not_trace", False):
- continue
- additional_info = set_additional_thread_info(t)
- frame = additional_info.get_topmost_frame(t)
- try:
- if frame is not None:
- self.set_trace_for_frame_and_parents(t.ident, frame)
- finally:
- frame = None
- finally:
- frame = None
- t = None
- threads = None
- additional_info = None
- if PYDEVD_USE_SYS_MONITORING:
- pydevd_sys_monitoring.restart_events()
- @property
- def multi_threads_single_notification(self):
- return self._threads_suspended_single_notification.multi_threads_single_notification
- @multi_threads_single_notification.setter
- def multi_threads_single_notification(self, notify):
- self._threads_suspended_single_notification.multi_threads_single_notification = notify
- @property
- def threads_suspended_single_notification(self):
- return self._threads_suspended_single_notification
- def get_plugin_lazy_init(self):
- if self.plugin is None:
- self.plugin = PluginManager(self)
- return self.plugin
- def in_project_scope(self, frame, absolute_filename=None):
- """
- Note: in general this method should not be used (apply_files_filter should be used
- in most cases as it also handles the project scope check).
- :param frame:
- The frame we want to check.
- :param absolute_filename:
- Must be the result from get_abs_path_real_path_and_base_from_frame(frame)[0] (can
- be used to speed this function a bit if it's already available to the caller, but
- in general it's not needed).
- """
- try:
- if absolute_filename is None:
- try:
- # Make fast path faster!
- abs_real_path_and_basename = NORM_PATHS_AND_BASE_CONTAINER[frame.f_code.co_filename]
- except:
- abs_real_path_and_basename = get_abs_path_real_path_and_base_from_frame(frame)
- absolute_filename = abs_real_path_and_basename[0]
- cache_key = (frame.f_code.co_firstlineno, absolute_filename, frame.f_code)
- return self._in_project_scope_cache[cache_key]
- except KeyError:
- cache = self._in_project_scope_cache
- try:
- abs_real_path_and_basename # If we've gotten it previously, use it again.
- except NameError:
- abs_real_path_and_basename = get_abs_path_real_path_and_base_from_frame(frame)
- # pydevd files are never considered to be in the project scope.
- file_type = self.get_file_type(frame, abs_real_path_and_basename)
- if file_type == self.PYDEV_FILE:
- cache[cache_key] = False
- elif IS_PY314_OR_GREATER and frame.f_code.co_name == "__annotate__":
- # Special handling for __annotate__ functions (PEP 649 in Python 3.14+).
- # These are compiler-generated functions that can raise NotImplementedError
- # when called with unsupported format arguments by inspect.call_annotate_function.
- # They should be treated as library code to avoid false positives in exception handling.
- # Note: PEP 649 reserves the __annotate__ name for compiler-generated functions,
- # so user-defined functions with this name are discouraged and will also be treated
- # as library code to maintain consistency with the language design.
- cache[cache_key] = False
- elif absolute_filename == "<string>":
- # Special handling for '<string>'
- if file_type == self.LIB_FILE:
- cache[cache_key] = False
- else:
- cache[cache_key] = True
- elif self.source_mapping.has_mapping_entry(absolute_filename):
- cache[cache_key] = True
- else:
- cache[cache_key] = self._files_filtering.in_project_roots(absolute_filename)
- return cache[cache_key]
- def in_project_roots_filename_uncached(self, absolute_filename):
- return self._files_filtering.in_project_roots(absolute_filename)
- def _clear_caches(self):
- # Skip caches
- global_cache_skips.clear()
- global_cache_frame_skips.clear()
- # Filter caches
- self._in_project_scope_cache.clear()
- self._exclude_by_filter_cache.clear()
- self._apply_filter_cache.clear()
- self._exclude_filters_enabled = self._files_filtering.use_exclude_filters()
- self._is_libraries_filter_enabled = self._files_filtering.use_libraries_filter()
- self.is_files_filter_enabled = self._exclude_filters_enabled or self._is_libraries_filter_enabled
- self.mtime += 1
- if PYDEVD_USE_SYS_MONITORING:
- pydevd_sys_monitoring.update_monitor_events()
- pydevd_sys_monitoring.restart_events()
- def clear_dont_trace_start_end_patterns_caches(self):
- # When start/end patterns are changed we must clear all caches which would be
- # affected by a change in get_file_type() and reset the tracing function
- # as places which were traced may no longer need to be traced and vice-versa.
- self.on_breakpoints_changed()
- _CACHE_FILE_TYPE.clear()
- self._clear_caches()
- def _exclude_by_filter(self, frame, absolute_filename):
- """
- :return: True if it should be excluded, False if it should be included and None
- if no rule matched the given file.
- :note: it'll be normalized as needed inside of this method.
- """
- cache_key = (absolute_filename, frame.f_code.co_name, frame.f_code.co_firstlineno)
- try:
- return self._exclude_by_filter_cache[cache_key]
- except KeyError:
- cache = self._exclude_by_filter_cache
- # pydevd files are always filtered out
- if self.get_file_type(frame) == self.PYDEV_FILE:
- cache[cache_key] = True
- else:
- module_name = None
- if self._files_filtering.require_module:
- module_name = frame.f_globals.get("__name__", "")
- cache[cache_key] = self._files_filtering.exclude_by_filter(absolute_filename, module_name)
- return cache[cache_key]
- def apply_files_filter(self, frame, original_filename, force_check_project_scope):
- """
- Should only be called if `self.is_files_filter_enabled == True` or `force_check_project_scope == True`.
- Note that it covers both the filter by specific paths includes/excludes as well
- as the check which filters out libraries if not in the project scope.
- :param original_filename:
- Note can either be the original filename or the absolute version of that filename.
- :param force_check_project_scope:
- Check that the file is in the project scope even if the global setting
- is off.
- :return bool:
- True if it should be excluded when stepping and False if it should be
- included.
- """
- cache_key = (frame.f_code.co_firstlineno, original_filename, force_check_project_scope, frame.f_code)
- try:
- return self._apply_filter_cache[cache_key]
- except KeyError:
- DEBUG = True # 'defaulttags' in original_filename
- if self.plugin is not None and (self.has_plugin_line_breaks or self.has_plugin_exception_breaks):
- # If it's explicitly needed by some plugin, we can't skip it.
- if not self.plugin.can_skip(self, frame):
- if DEBUG:
- pydev_log.debug_once("File traced (included by plugins): %s", original_filename)
- self._apply_filter_cache[cache_key] = False
- return False
- if self._exclude_filters_enabled:
- absolute_filename = pydevd_file_utils.absolute_path(original_filename)
- exclude_by_filter = self._exclude_by_filter(frame, absolute_filename)
- if exclude_by_filter is not None:
- if exclude_by_filter:
- # ignore files matching stepping filters
- if DEBUG:
- pydev_log.debug_once("File not traced (excluded by filters): %s", original_filename)
- self._apply_filter_cache[cache_key] = True
- return True
- else:
- if DEBUG:
- pydev_log.debug_once("File traced (explicitly included by filters): %s", original_filename)
- self._apply_filter_cache[cache_key] = False
- return False
- if (self._is_libraries_filter_enabled or force_check_project_scope) and not self.in_project_scope(frame):
- # ignore library files while stepping
- self._apply_filter_cache[cache_key] = True
- if force_check_project_scope:
- if DEBUG:
- pydev_log.debug_once("File not traced (not in project): %s", original_filename)
- else:
- if DEBUG:
- pydev_log.debug_once("File not traced (not in project - force_check_project_scope): %s", original_filename)
- return True
- if force_check_project_scope:
- if DEBUG:
- pydev_log.debug_once("File traced: %s (force_check_project_scope)", original_filename)
- else:
- if DEBUG:
- pydev_log.debug_once("File traced: %s", original_filename)
- self._apply_filter_cache[cache_key] = False
- return False
- def exclude_exception_by_filter(self, exception_breakpoint, trace):
- if not exception_breakpoint.ignore_libraries and not self._exclude_filters_enabled:
- return False
- if trace is None:
- return True
- ignore_libraries = exception_breakpoint.ignore_libraries
- exclude_filters_enabled = self._exclude_filters_enabled
- if (ignore_libraries and not self.in_project_scope(trace.tb_frame)) or (
- exclude_filters_enabled
- and self._exclude_by_filter(trace.tb_frame, pydevd_file_utils.absolute_path(trace.tb_frame.f_code.co_filename))
- ):
- return True
- return False
- def set_project_roots(self, project_roots):
- self._files_filtering.set_project_roots(project_roots)
- self._clear_caches()
- def set_exclude_filters(self, exclude_filters):
- self._files_filtering.set_exclude_filters(exclude_filters)
- self._clear_caches()
- def set_use_libraries_filter(self, use_libraries_filter):
- self._files_filtering.set_use_libraries_filter(use_libraries_filter)
- self._clear_caches()
- def get_use_libraries_filter(self):
- return self._files_filtering.use_libraries_filter()
- def get_require_module_for_filters(self):
- return self._files_filtering.require_module
- def has_user_threads_alive(self):
- for t in pydevd_utils.get_non_pydevd_threads():
- if isinstance(t, PyDBDaemonThread):
- pydev_log.error_once("Error in debugger: Found PyDBDaemonThread not marked with is_pydev_daemon_thread=True.\n")
- if is_thread_alive(t):
- if not t.daemon or hasattr(t, "__pydevd_main_thread"):
- return True
- return False
- def initialize_network(self, sock, terminate_on_socket_close=True):
- assert sock is not None
- try:
- sock.settimeout(None) # infinite, no timeouts from now on - jython does not have it
- except:
- pass
- curr_reader = getattr(self, "reader", None)
- curr_writer = getattr(self, "writer", None)
- if curr_reader:
- curr_reader.do_kill_pydev_thread()
- if curr_writer:
- curr_writer.do_kill_pydev_thread()
- self.writer = WriterThread(sock, self, terminate_on_socket_close=terminate_on_socket_close)
- self.reader = ReaderThread(
- sock,
- self,
- PyDevJsonCommandProcessor=PyDevJsonCommandProcessor,
- process_net_command=process_net_command,
- terminate_on_socket_close=terminate_on_socket_close,
- )
- self.writer.start()
- self.reader.start()
- time.sleep(0.1) # give threads time to start
- def connect(self, host, port):
- if host:
- s = start_client(host, port)
- self._client_socket = s
- else:
- s = start_server(port)
- self.initialize_network(s)
- def create_wait_for_connection_thread(self):
- if self._waiting_for_connection_thread is not None:
- raise AssertionError("There is already another thread waiting for a connection.")
- self._server_socket_ready_event.clear()
- self._waiting_for_connection_thread = self._WaitForConnectionThread(self)
- self._waiting_for_connection_thread.start()
- def set_server_socket_ready(self):
- self._server_socket_ready_event.set()
- def wait_for_server_socket_ready(self):
- self._server_socket_ready_event.wait()
- @property
- def dap_messages_listeners(self):
- return self._dap_messages_listeners
- def add_dap_messages_listener(self, listener):
- self._dap_messages_listeners.append(listener)
- class _WaitForConnectionThread(PyDBDaemonThread):
- def __init__(self, py_db):
- PyDBDaemonThread.__init__(self, py_db)
- self._server_socket = None
- def run(self):
- host = SetupHolder.setup["client"]
- port = SetupHolder.setup["port"]
- self._server_socket = create_server_socket(host=host, port=port)
- self.py_db._server_socket_name = self._server_socket.getsockname()
- self.py_db.set_server_socket_ready()
- while not self._kill_received:
- try:
- s = self._server_socket
- if s is None:
- return
- s.listen(1)
- new_socket, _addr = s.accept()
- if self._kill_received:
- pydev_log.info("Connection (from wait_for_attach) accepted but ignored as kill was already received.")
- return
- pydev_log.info("Connection (from wait_for_attach) accepted.")
- reader = getattr(self.py_db, "reader", None)
- if reader is not None:
- # This is needed if a new connection is done without the client properly
- # sending a disconnect for the previous connection.
- api = PyDevdAPI()
- api.request_disconnect(self.py_db, resume_threads=False)
- self.py_db.initialize_network(new_socket, terminate_on_socket_close=False)
- except:
- if DebugInfoHolder.DEBUG_TRACE_LEVEL > 0:
- pydev_log.exception()
- pydev_log.debug("Exiting _WaitForConnectionThread: %s\n", port)
- def do_kill_pydev_thread(self):
- PyDBDaemonThread.do_kill_pydev_thread(self)
- s = self._server_socket
- if s is not None:
- try:
- s.close()
- except:
- pass
- self._server_socket = None
- def get_internal_queue_and_event(self, thread_id) -> Tuple[_queue.Queue, ThreadingEvent]:
- """returns internal command queue for a given thread.
- if new queue is created, notify the RDB about it"""
- if thread_id.startswith("__frame__"):
- thread_id = thread_id[thread_id.rfind("|") + 1:]
- return self._cmd_queue[thread_id], self._thread_events[thread_id]
- def post_method_as_internal_command(self, thread_id, method, *args, **kwargs):
- if thread_id == "*":
- internal_cmd = InternalThreadCommandForAnyThread(thread_id, method, *args, **kwargs)
- else:
- internal_cmd = InternalThreadCommand(thread_id, method, *args, **kwargs)
- self.post_internal_command(internal_cmd, thread_id)
- def post_internal_command(self, int_cmd, thread_id):
- """if thread_id is *, post to the '*' queue"""
- queue, event = self.get_internal_queue_and_event(thread_id)
- queue.put(int_cmd)
- if thread_id == "*":
- self._py_db_command_thread_event.set()
- else:
- event.set()
- def enable_output_redirection(self, redirect_stdout, redirect_stderr):
- global _global_redirect_stdout_to_server
- global _global_redirect_stderr_to_server
- _global_redirect_stdout_to_server = redirect_stdout
- _global_redirect_stderr_to_server = redirect_stderr
- self.redirect_output = redirect_stdout or redirect_stderr
- if _global_redirect_stdout_to_server:
- _init_stdout_redirect()
- if _global_redirect_stderr_to_server:
- _init_stderr_redirect()
- def check_output_redirect(self):
- global _global_redirect_stdout_to_server
- global _global_redirect_stderr_to_server
- if _global_redirect_stdout_to_server:
- _init_stdout_redirect()
- if _global_redirect_stderr_to_server:
- _init_stderr_redirect()
- def init_gui_support(self):
- if self._installed_gui_support:
- return
- self._installed_gui_support = True
- # enable_gui and enable_gui_function in activate_matplotlib should be called in main thread. Unlike integrated console,
- # in the debug console we have no interpreter instance with exec_queue, but we run this code in the main
- # thread and can call it directly.
- class _ReturnGuiLoopControlHelper:
- _return_control_osc = False
- def return_control():
- # Some of the input hooks (e.g. Qt4Agg) check return control without doing
- # a single operation, so we don't return True on every
- # call when the debug hook is in place to allow the GUI to run
- _ReturnGuiLoopControlHelper._return_control_osc = not _ReturnGuiLoopControlHelper._return_control_osc
- return _ReturnGuiLoopControlHelper._return_control_osc
- from pydev_ipython.inputhook import set_return_control_callback, enable_gui
- set_return_control_callback(return_control)
- if self._gui_event_loop == "matplotlib":
- # prepare debugger for matplotlib integration with GUI event loop
- from pydev_ipython.matplotlibtools import activate_matplotlib, activate_pylab, activate_pyplot, do_enable_gui
- self.mpl_modules_for_patching = {
- "matplotlib": lambda: activate_matplotlib(do_enable_gui),
- "matplotlib.pyplot": activate_pyplot,
- "pylab": activate_pylab,
- }
- else:
- self.activate_gui_function = enable_gui
- def _activate_gui_if_needed(self):
- if self.gui_in_use:
- return
- if len(self.mpl_modules_for_patching) > 0:
- if is_current_thread_main_thread(): # Note that we call only in the main thread.
- for module in list(self.mpl_modules_for_patching):
- if module in sys.modules:
- activate_function = self.mpl_modules_for_patching.pop(module, None)
- if activate_function is not None:
- activate_function()
- self.gui_in_use = True
- if self.activate_gui_function:
- if is_current_thread_main_thread(): # Only call enable_gui in the main thread.
- try:
- # First try to activate builtin GUI event loops.
- self.activate_gui_function(self._gui_event_loop)
- self.activate_gui_function = None
- self.gui_in_use = True
- except ValueError:
- # The user requested a custom GUI event loop, try to import it.
- from pydev_ipython.inputhook import set_inputhook
- try:
- inputhook_function = import_attr_from_module(self._gui_event_loop)
- set_inputhook(inputhook_function)
- self.gui_in_use = True
- except Exception as e:
- pydev_log.debug("Cannot activate custom GUI event loop {}: {}".format(self._gui_event_loop, e))
- finally:
- self.activate_gui_function = None
- def _call_input_hook(self):
- try:
- from pydev_ipython.inputhook import get_inputhook
- inputhook = get_inputhook()
- if inputhook:
- inputhook()
- except:
- pass
- def notify_skipped_step_in_because_of_filters(self, frame):
- self.writer.add_command(self.cmd_factory.make_skipped_step_in_because_of_filters(self, frame))
- def notify_thread_created(self, thread_id, thread, use_lock=True):
- if self.writer is None:
- # Protect about threads being created before the communication structure is in place
- # (note that they will appear later on anyways as pydevd does reconcile live/dead threads
- # when processing internal commands, albeit it may take longer and in general this should
- # not be usual as it's expected that the debugger is live before other threads are created).
- return
- with self._lock_running_thread_ids if use_lock else NULL:
- if not self._enable_thread_notifications:
- return
- if thread_id in self._running_thread_ids:
- return
- additional_info = set_additional_thread_info(thread)
- if additional_info.pydev_notify_kill:
- # After we notify it should be killed, make sure we don't notify it's alive (on a racing condition
- # this could happen as we may notify before the thread is stopped internally).
- return
- self._running_thread_ids[thread_id] = thread
- self.writer.add_command(self.cmd_factory.make_thread_created_message(thread))
- def notify_thread_not_alive(self, thread_id, use_lock=True):
- """if thread is not alive, cancel trace_dispatch processing"""
- if self.writer is None:
- return
- with self._lock_running_thread_ids if use_lock else NULL:
- if not self._enable_thread_notifications:
- return
- thread = self._running_thread_ids.pop(thread_id, None)
- if thread is None:
- return
- additional_info = set_additional_thread_info(thread)
- was_notified = additional_info.pydev_notify_kill
- if not was_notified:
- additional_info.pydev_notify_kill = True
- remove_additional_info(additional_info)
- self.writer.add_command(self.cmd_factory.make_thread_killed_message(thread_id))
- def set_enable_thread_notifications(self, enable):
- with self._lock_running_thread_ids:
- if self._enable_thread_notifications != enable:
- self._enable_thread_notifications = enable
- if enable:
- # As it was previously disabled, we have to notify about existing threads again
- # (so, clear the cache related to that).
- self._running_thread_ids = {}
- def process_internal_commands(self, process_thread_ids: Optional[tuple]=None):
- """
- This function processes internal commands.
- """
- # If this method is being called before the debugger is ready to run we should not notify
- # about threads and should only process commands sent to all threads.
- ready_to_run = self.ready_to_run
- dispose = False
- with self._main_lock:
- program_threads_alive = {}
- if ready_to_run:
- self.check_output_redirect()
- all_threads = threadingEnumerate()
- program_threads_dead = []
- with self._lock_running_thread_ids:
- reset_cache = not self._running_thread_ids
- for t in all_threads:
- if getattr(t, "is_pydev_daemon_thread", False):
- pass # I.e.: skip the DummyThreads created from pydev daemon threads
- elif isinstance(t, PyDBDaemonThread):
- pydev_log.error_once("Error in debugger: Found PyDBDaemonThread not marked with is_pydev_daemon_thread=True.")
- elif is_thread_alive(t):
- if reset_cache:
- # Fix multiprocessing debug with breakpoints in both main and child processes
- # (https://youtrack.jetbrains.com/issue/PY-17092) When the new process is created, the main
- # thread in the new process already has the attribute 'pydevd_id', so the new thread doesn't
- # get new id with its process number and the debugger loses access to both threads.
- # Therefore we should update thread_id for every main thread in the new process.
- clear_cached_thread_id(t)
- thread_id = get_thread_id(t)
- program_threads_alive[thread_id] = t
- self.notify_thread_created(thread_id, t, use_lock=False)
- # Compute and notify about threads which are no longer alive.
- thread_ids = list(self._running_thread_ids.keys())
- for thread_id in thread_ids:
- if thread_id not in program_threads_alive:
- program_threads_dead.append(thread_id)
- for thread_id in program_threads_dead:
- self.notify_thread_not_alive(thread_id, use_lock=False)
- cmds_to_execute = []
- # Without self._lock_running_thread_ids
- if len(program_threads_alive) == 0 and ready_to_run:
- dispose = True
- else:
- curr_thread_id = get_current_thread_id(threadingCurrentThread())
- if process_thread_ids is None:
- # Actually process the commands now (make sure we don't have a lock for _lock_running_thread_ids
- # acquired at this point as it could lead to a deadlock if some command evaluated tried to
- # create a thread and wait for it -- which would try to notify about it getting that lock).
- if ready_to_run:
- process_thread_ids = (curr_thread_id, "*")
- else:
- process_thread_ids = ("*",)
- for thread_id in process_thread_ids:
- queue, _event = self.get_internal_queue_and_event(thread_id)
- # some commands must be processed by the thread itself... if that's the case,
- # we will re-add the commands to the queue after executing.
- cmds_to_add_back = []
- try:
- while True:
- internal_cmd = queue.get(False)
- try:
- if internal_cmd.can_be_executed_by(curr_thread_id):
- cmds_to_execute.append(internal_cmd)
- else:
- pydev_log.verbose("NOT processing internal command: %s ", internal_cmd)
- cmds_to_add_back.append(internal_cmd)
- except:
- pydev_log.exception()
- raise
- except _queue.Empty: # @UndefinedVariable
- # this is how we exit
- for internal_cmd in cmds_to_add_back:
- queue.put(internal_cmd)
- if dispose:
- # Note: must be called without the main lock to avoid deadlocks.
- self.dispose_and_kill_all_pydevd_threads()
- else:
- # Actually execute the commands without the main lock!
- for internal_cmd in cmds_to_execute:
- pydev_log.verbose("processing internal command: %s", internal_cmd)
- try:
- internal_cmd.do_it(self)
- except:
- pydev_log.exception("Error processing internal command.")
- def consolidate_breakpoints(self, canonical_normalized_filename, id_to_breakpoint, file_to_line_to_breakpoints):
- break_dict = {}
- for _breakpoint_id, pybreakpoint in id_to_breakpoint.items():
- break_dict[pybreakpoint.line] = pybreakpoint
- file_to_line_to_breakpoints[canonical_normalized_filename] = break_dict
- self._clear_caches()
- def add_break_on_exception(
- self,
- exception,
- condition,
- expression,
- notify_on_handled_exceptions,
- notify_on_unhandled_exceptions,
- notify_on_user_unhandled_exceptions,
- notify_on_first_raise_only,
- ignore_libraries=False,
- ):
- try:
- eb = ExceptionBreakpoint(
- exception,
- condition,
- expression,
- notify_on_handled_exceptions,
- notify_on_unhandled_exceptions,
- notify_on_user_unhandled_exceptions,
- notify_on_first_raise_only,
- ignore_libraries,
- )
- except ImportError:
- pydev_log.critical("Error unable to add break on exception for: %s (exception could not be imported).", exception)
- return None
- if eb.notify_on_unhandled_exceptions:
- cp = self.break_on_uncaught_exceptions.copy()
- cp[exception] = eb
- pydev_log.info("Exceptions to hook on terminate: %s.", cp)
- self.break_on_uncaught_exceptions = cp
- if eb.notify_on_handled_exceptions:
- cp = self.break_on_caught_exceptions.copy()
- cp[exception] = eb
- pydev_log.info("Exceptions to hook always: %s.", cp)
- self.break_on_caught_exceptions = cp
- if eb.notify_on_user_unhandled_exceptions:
- cp = self.break_on_user_uncaught_exceptions.copy()
- cp[exception] = eb
- pydev_log.info("Exceptions to hook on user uncaught code: %s.", cp)
- self.break_on_user_uncaught_exceptions = cp
- return eb
- def set_suspend(
- self,
- thread,
- stop_reason: int,
- suspend_other_threads: bool=False,
- is_pause=False,
- original_step_cmd: int=-1,
- suspend_requested: bool=False,
- ):
- """
- :param thread:
- The thread which should be suspended.
- :param stop_reason:
- Reason why the thread was suspended.
- :param suspend_other_threads:
- Whether to force other threads to be suspended (i.e.: when hitting a breakpoint
- with a suspend all threads policy).
- :param is_pause:
- If this is a pause to suspend all threads, any thread can be considered as the 'main'
- thread paused.
- :param original_step_cmd:
- If given we may change the stop reason to this.
- :param suspend_requested:
- If the execution will be suspended right away then this may be false, otherwise,
- if the thread should be stopped due to this suspend at a later time then it
- should be true.
- """
- self._threads_suspended_single_notification.increment_suspend_time()
- if is_pause:
- self._threads_suspended_single_notification.on_pause()
- info = mark_thread_suspended(thread, stop_reason, original_step_cmd=original_step_cmd)
- if (suspend_requested or is_pause) and PYDEVD_USE_SYS_MONITORING:
- pydevd_sys_monitoring.update_monitor_events(suspend_requested=True)
- if is_pause:
- # Must set tracing after setting the state to suspend.
- frame = info.get_topmost_frame(thread)
- if frame is not None:
- # Where suspend was requested
- # traceback.print_stack(frame)
- try:
- self.set_trace_for_frame_and_parents(thread.ident, frame)
- finally:
- frame = None
- # If conditional breakpoint raises any exception during evaluation send the details to the client.
- if stop_reason == CMD_SET_BREAK and info.conditional_breakpoint_exception is not None:
- conditional_breakpoint_exception_tuple = info.conditional_breakpoint_exception
- info.conditional_breakpoint_exception = None
- self._send_breakpoint_condition_exception(thread, conditional_breakpoint_exception_tuple)
- if not suspend_other_threads and self.multi_threads_single_notification:
- # In the mode which gives a single notification when all threads are
- # stopped, stop all threads whenever a set_suspend is issued.
- suspend_other_threads = True
- if suspend_other_threads:
- # Suspend all except the current one (which we're currently suspending already).
- suspend_all_threads(self, except_thread=thread)
- if PYDEVD_USE_SYS_MONITORING:
- pydevd_sys_monitoring.restart_events()
- def _send_breakpoint_condition_exception(self, thread, conditional_breakpoint_exception_tuple):
- """If conditional breakpoint raises an exception during evaluation
- send exception details to java
- """
- thread_id = get_thread_id(thread)
- # conditional_breakpoint_exception_tuple - should contain 2 values (exception_type, stacktrace)
- if conditional_breakpoint_exception_tuple and len(conditional_breakpoint_exception_tuple) == 2:
- exc_type, stacktrace = conditional_breakpoint_exception_tuple
- int_cmd = InternalGetBreakpointException(thread_id, exc_type, stacktrace)
- self.post_internal_command(int_cmd, thread_id)
- def send_caught_exception_stack(self, thread, arg, curr_frame_id):
- """Sends details on the exception which was caught (and where we stopped) to the java side.
- arg is: exception type, description, traceback object
- """
- thread_id = get_thread_id(thread)
- int_cmd = InternalSendCurrExceptionTrace(thread_id, arg, curr_frame_id)
- self.post_internal_command(int_cmd, thread_id)
- def send_caught_exception_stack_proceeded(self, thread):
- """Sends that some thread was resumed and is no longer showing an exception trace."""
- thread_id = get_thread_id(thread)
- int_cmd = InternalSendCurrExceptionTraceProceeded(thread_id)
- self.post_internal_command(int_cmd, thread_id)
- self.process_internal_commands()
- def send_process_created_message(self):
- """Sends a message that a new process has been created."""
- if self.writer is None or self.cmd_factory is None:
- return
- cmd = self.cmd_factory.make_process_created_message()
- self.writer.add_command(cmd)
- def send_process_about_to_be_replaced(self):
- """Sends a message that a new process has been created."""
- if self.writer is None or self.cmd_factory is None:
- return
- cmd = self.cmd_factory.make_process_about_to_be_replaced_message()
- if cmd is NULL_NET_COMMAND:
- return
- sent = [False]
- def after_sent(*args, **kwargs):
- sent[0] = True
- cmd.call_after_send(after_sent)
- self.writer.add_command(cmd)
- timeout = 5 # Wait up to 5 seconds
- initial_time = time.time()
- while not sent[0]:
- time.sleep(0.05)
- if (time.time() - initial_time) > timeout:
- pydev_log.critical("pydevd: Sending message related to process being replaced timed-out after %s seconds", timeout)
- break
- def set_next_statement(self, frame, event, func_name, next_line):
- stop = False
- response_msg = ""
- old_line = frame.f_lineno
- if event == "line" or event == "exception":
- # If we're already in the correct context, we have to stop it now, because we can act only on
- # line events -- if a return was the next statement it wouldn't work (so, we have this code
- # repeated at pydevd_frame).
- curr_func_name = frame.f_code.co_name
- # global context is set with an empty name
- if curr_func_name in ("?", "<module>"):
- curr_func_name = ""
- if func_name == "*" or curr_func_name == func_name:
- line = next_line
- frame.f_trace = self.trace_dispatch
- frame.f_lineno = line
- stop = True
- else:
- response_msg = "jump is available only within the bottom frame"
- return stop, old_line, response_msg
- def cancel_async_evaluation(self, thread_id, frame_id):
- with self._main_lock:
- try:
- all_threads = threadingEnumerate()
- for t in all_threads:
- if (
- getattr(t, "is_pydev_daemon_thread", False)
- and hasattr(t, "cancel_event")
- and t.thread_id == thread_id
- and t.frame_id == frame_id
- ):
- t.cancel_event.set()
- except:
- pydev_log.exception()
- def find_frame(self, thread_id, frame_id):
- """returns a frame on the thread that has a given frame_id"""
- return self.suspended_frames_manager.find_frame(thread_id, frame_id)
- def do_wait_suspend(self, thread, frame, event, arg, exception_type=None): # @UnusedVariable
- """busy waits until the thread state changes to RUN
- it expects thread's state as attributes of the thread.
- Upon running, processes any outstanding Stepping commands.
- :param exception_type:
- If pausing due to an exception, its type.
- """
- if USE_CUSTOM_SYS_CURRENT_FRAMES_MAP:
- constructed_tid_to_last_frame[thread.ident] = sys._getframe()
- # Only process from all threads, not for current one (we'll do that later on in this method).
- self.process_internal_commands(("*",))
- thread_id = get_current_thread_id(thread)
- # if DebugInfoHolder.DEBUG_TRACE_LEVEL >= 2:
- # pydev_log.debug('do_wait_suspend %s %s %s %s %s %s (%s)' % (frame.f_lineno, frame.f_code.co_name, frame.f_code.co_filename, event, arg, constant_to_str(thread.additional_info.pydev_step_cmd), constant_to_str(thread.additional_info.pydev_original_step_cmd)))
- # pydev_log.debug('--- internal stack ---')
- # _f = sys._getframe()
- # while _f is not None:
- # pydev_log.debug(' -> %s' % (_f))
- # _f = _f.f_back
- # pydev_log.debug('--- end internal stack ---')
- # Send the suspend message
- message = thread.additional_info.pydev_message
- trace_suspend_type = thread.additional_info.trace_suspend_type
- thread.additional_info.trace_suspend_type = "trace" # Reset to trace mode for next call.
- stop_reason = thread.stop_reason
- frames_list = None
- if arg is not None and event == "exception":
- # arg must be the exception info (tuple(exc_type, exc, traceback))
- exc_type, exc_desc, trace_obj = arg
- if trace_obj is not None:
- frames_list = pydevd_frame_utils.create_frames_list_from_traceback(
- trace_obj, frame, exc_type, exc_desc, exception_type=exception_type
- )
- if frames_list is None:
- frames_list = pydevd_frame_utils.create_frames_list_from_frame(frame)
- if DebugInfoHolder.DEBUG_TRACE_LEVEL > 2:
- pydev_log.debug(
- "PyDB.do_wait_suspend\nname: %s (line: %s)\n file: %s\n event: %s\n arg: %s\n step: %s (original step: %s)\n thread: %s, thread id: %s, id(thread): %s",
- frame.f_code.co_name,
- frame.f_lineno,
- frame.f_code.co_filename,
- event,
- arg,
- constant_to_str(thread.additional_info.pydev_step_cmd),
- constant_to_str(thread.additional_info.pydev_original_step_cmd),
- thread,
- thread_id,
- id(thread),
- )
- for f in frames_list:
- pydev_log.debug(" Stack: %s, %s, %s", f.f_code.co_filename, f.f_code.co_name, f.f_lineno)
- with self.suspended_frames_manager.track_frames(self) as frames_tracker:
- frames_tracker.track(thread_id, frames_list)
- cmd = frames_tracker.create_thread_suspend_command(
- thread_id, stop_reason, message, trace_suspend_type, thread, thread.additional_info
- )
- self.writer.add_command(cmd)
- with CustomFramesContainer.custom_frames_lock: # @UndefinedVariable
- from_this_thread = []
- for frame_custom_thread_id, custom_frame in CustomFramesContainer.custom_frames.items():
- if custom_frame.thread_id == thread.ident:
- frames_tracker.track(
- thread_id,
- pydevd_frame_utils.create_frames_list_from_frame(custom_frame.frame),
- frame_custom_thread_id=frame_custom_thread_id,
- )
- # print('Frame created as thread: %s' % (frame_custom_thread_id,))
- self.writer.add_command(
- self.cmd_factory.make_custom_frame_created_message(frame_custom_thread_id, custom_frame.name)
- )
- self.writer.add_command(
- frames_tracker.create_thread_suspend_command(
- frame_custom_thread_id, CMD_THREAD_SUSPEND, "", trace_suspend_type, thread, thread.additional_info
- )
- )
- from_this_thread.append(frame_custom_thread_id)
- with self._threads_suspended_single_notification.notify_thread_suspended(thread_id, thread, stop_reason):
- keep_suspended = self._do_wait_suspend(thread, frame, event, arg, trace_suspend_type, from_this_thread, frames_tracker)
- frames_list = None
- if keep_suspended:
- # This means that we should pause again after a set next statement.
- self._threads_suspended_single_notification.increment_suspend_time()
- self.do_wait_suspend(thread, frame, event, arg, exception_type)
- if DebugInfoHolder.DEBUG_TRACE_LEVEL > 2:
- pydev_log.debug("Leaving PyDB.do_wait_suspend: %s (%s) %s", thread, thread_id, id(thread))
- def _do_wait_suspend(self, thread, frame, event, arg, trace_suspend_type, from_this_thread, frames_tracker):
- info = thread.additional_info
- try:
- info.is_in_wait_loop = True
- info.update_stepping_info()
- info.step_in_initial_location = None
- keep_suspended = False
- with self._main_lock: # Use lock to check if suspended state changed
- activate_gui = info.pydev_state == STATE_SUSPEND and not self.pydb_disposed
- in_main_thread = is_current_thread_main_thread()
- if activate_gui and in_main_thread:
- # before every stop check if matplotlib modules were imported inside script code
- # or some GUI event loop needs to be activated
- self._activate_gui_if_needed()
- # self.process_internal_commands(): processes for all the threads
- # and updates running threads. This was called once in `do_wait_suspend`
- # At this point it's just processing for this thread.
- # Note that clients may not post an actual event (for instance, it
- # could just set the internal state and signal the event instead
- # of posting a command to the queue). In any case, if an item is
- # put in the queue, the event must be set too.
- curr_thread_id = get_current_thread_id(threadingCurrentThread())
- queue, notify_event = self.get_internal_queue_and_event(curr_thread_id)
- wait_timeout = TIMEOUT_SLOW
- while True:
- with self._main_lock: # Use lock to check if suspended state changed
- if info.pydev_state != STATE_SUSPEND or (self.pydb_disposed and not self.terminate_requested):
- # Note: we can't exit here if terminate was requested while a breakpoint was hit.
- break
- if in_main_thread and self.gui_in_use:
- wait_timeout = TIMEOUT_FAST
- # call input hooks if only GUI is in use
- self._call_input_hook()
- # No longer process commands for '*' at this point, just the
- # ones related to this thread.
- try:
- internal_cmd = queue.get(False)
- except _queue.Empty:
- pass
- else:
- if internal_cmd.can_be_executed_by(curr_thread_id):
- pydev_log.verbose("processing internal command: %s", internal_cmd)
- try:
- internal_cmd.do_it(self)
- except:
- pydev_log.exception("Error processing internal command.")
- else:
- # This shouldn't really happen...
- pydev_log.verbose("NOT processing internal command: %s ", internal_cmd)
- queue.put(internal_cmd)
- wait_timeout = TIMEOUT_FAST
- notify_event.wait(wait_timeout)
- notify_event.clear()
- finally:
- info.is_in_wait_loop = False
- info.update_stepping_info()
- self.cancel_async_evaluation(get_current_thread_id(thread), str(id(frame)))
- # process any stepping instructions
- if info.pydev_step_cmd in (CMD_STEP_INTO, CMD_STEP_INTO_MY_CODE):
- info.step_in_initial_location = (frame, frame.f_lineno)
- if frame.f_code.co_flags & 0x80: # CO_COROUTINE = 0x80
- # When in a coroutine we switch to CMD_STEP_INTO_COROUTINE.
- info.pydev_step_cmd = CMD_STEP_INTO_COROUTINE
- info.pydev_step_stop = frame
- self.set_trace_for_frame_and_parents(thread.ident, frame)
- else:
- info.pydev_step_stop = None
- self.set_trace_for_frame_and_parents(thread.ident, frame)
- elif info.pydev_step_cmd in (CMD_STEP_OVER, CMD_STEP_OVER_MY_CODE, CMD_SMART_STEP_INTO):
- info.pydev_step_stop = frame
- self.set_trace_for_frame_and_parents(thread.ident, frame)
- elif info.pydev_step_cmd == CMD_RUN_TO_LINE or info.pydev_step_cmd == CMD_SET_NEXT_STATEMENT:
- info.pydev_step_stop = None
- self.set_trace_for_frame_and_parents(thread.ident, frame)
- stop = False
- response_msg = ""
- try:
- stop, _old_line, response_msg = self.set_next_statement(frame, event, info.pydev_func_name, info.pydev_next_line)
- except ValueError as e:
- response_msg = "%s" % e
- finally:
- seq = info.pydev_message
- cmd = self.cmd_factory.make_set_next_stmnt_status_message(seq, stop, response_msg)
- self.writer.add_command(cmd)
- info.pydev_message = ""
- if stop:
- # Uninstall the current frames tracker before running it.
- frames_tracker.untrack_all()
- cmd = self.cmd_factory.make_thread_run_message(self, get_current_thread_id(thread), info.pydev_step_cmd)
- self.writer.add_command(cmd)
- info.pydev_state = STATE_SUSPEND
- thread.stop_reason = CMD_SET_NEXT_STATEMENT
- keep_suspended = True
- else:
- # Set next did not work...
- info.pydev_original_step_cmd = -1
- info.pydev_step_cmd = -1
- info.pydev_state = STATE_SUSPEND
- thread.stop_reason = CMD_THREAD_SUSPEND
- # return to the suspend state and wait for other command (without sending any
- # additional notification to the client).
- return self._do_wait_suspend(thread, frame, event, arg, trace_suspend_type, from_this_thread, frames_tracker)
- elif info.pydev_step_cmd in (CMD_STEP_RETURN, CMD_STEP_RETURN_MY_CODE):
- back_frame = frame.f_back
- force_check_project_scope = info.pydev_step_cmd == CMD_STEP_RETURN_MY_CODE
- if force_check_project_scope or self.is_files_filter_enabled:
- while back_frame is not None:
- if self.apply_files_filter(back_frame, back_frame.f_code.co_filename, force_check_project_scope):
- frame = back_frame
- back_frame = back_frame.f_back
- else:
- break
- if back_frame is not None:
- # steps back to the same frame (in a return call it will stop in the 'back frame' for the user)
- info.pydev_step_stop = frame
- self.set_trace_for_frame_and_parents(thread.ident, frame)
- else:
- # No back frame?!? -- this happens in jython when we have some frame created from an awt event
- # (the previous frame would be the awt event, but this doesn't make part of 'jython', only 'java')
- # so, if we're doing a step return in this situation, it's the same as just making it run
- info.pydev_step_stop = None
- info.pydev_original_step_cmd = -1
- info.pydev_step_cmd = -1
- info.pydev_state = STATE_RUN
- if PYDEVD_IPYTHON_COMPATIBLE_DEBUGGING:
- info.pydev_use_scoped_step_frame = False
- if info.pydev_step_cmd in (CMD_STEP_OVER, CMD_STEP_OVER_MY_CODE, CMD_STEP_INTO, CMD_STEP_INTO_MY_CODE):
- # i.e.: We're stepping: check if the stepping should be scoped (i.e.: in ipython
- # each line is executed separately in a new frame, in which case we need to consider
- # the next line as if it was still in the same frame).
- f = frame.f_back
- if f and f.f_code.co_name == PYDEVD_IPYTHON_CONTEXT[1]:
- f = f.f_back
- if f and f.f_code.co_name == PYDEVD_IPYTHON_CONTEXT[2]:
- info.pydev_use_scoped_step_frame = True
- pydev_log.info("Using (ipython) scoped stepping.")
- del f
- del frame
- cmd = self.cmd_factory.make_thread_run_message(self, get_current_thread_id(thread), info.pydev_step_cmd)
- self.writer.add_command(cmd)
- with CustomFramesContainer.custom_frames_lock:
- # The ones that remained on last_running must now be removed.
- for frame_id in from_this_thread:
- # print('Removing created frame: %s' % (frame_id,))
- self.writer.add_command(self.cmd_factory.make_thread_killed_message(frame_id))
- info.update_stepping_info()
- return keep_suspended
- def do_stop_on_unhandled_exception(self, thread, frame, frames_byid, arg):
- pydev_log.debug("We are stopping in unhandled exception.")
- try:
- add_exception_to_frame(frame, arg)
- self.send_caught_exception_stack(thread, arg, id(frame))
- try:
- self.set_suspend(thread, CMD_ADD_EXCEPTION_BREAK)
- self.do_wait_suspend(thread, frame, "exception", arg, EXCEPTION_TYPE_UNHANDLED)
- except:
- self.send_caught_exception_stack_proceeded(thread)
- except:
- pydev_log.exception("We've got an error while stopping in unhandled exception: %s.", arg[0])
- finally:
- remove_exception_from_frame(frame)
- frame = None
- def set_trace_for_frame_and_parents(self, thread_ident: Optional[int], frame, **kwargs):
- disable = kwargs.pop("disable", False)
- assert not kwargs
- DEBUG = True # 'defaulttags' in frame.f_code.co_filename
- while frame is not None:
- if not isinstance(frame, FrameType):
- # This is the case for django/jinja frames.
- frame = frame.f_back
- continue
- # Don't change the tracing on debugger-related files
- file_type = self.get_file_type(frame)
- if PYDEVD_USE_SYS_MONITORING:
- if file_type is None:
- if disable:
- if DEBUG:
- pydev_log.debug("Disable tracing of frame: %s - %s", frame.f_code.co_filename, frame.f_code.co_name)
- pydevd_sys_monitoring.disable_code_tracing(frame.f_code)
- else:
- if DEBUG:
- pydev_log.debug("Set tracing of frame: %s - %s", frame.f_code.co_filename, frame.f_code.co_name)
- pydevd_sys_monitoring.enable_code_tracing(thread_ident, frame.f_code, frame)
- else:
- if DEBUG:
- pydev_log.debug("SKIP set tracing of frame: %s - %s", frame.f_code.co_filename, frame.f_code.co_name)
- else:
- # Not using sys.monitoring.
- if file_type is None:
- if disable:
- if DEBUG:
- pydev_log.debug("Disable tracing of frame: %s - %s", frame.f_code.co_filename, frame.f_code.co_name)
- if frame.f_trace is not None and frame.f_trace is not NO_FTRACE:
- frame.f_trace = NO_FTRACE
- elif frame.f_trace is not self.trace_dispatch:
- if DEBUG:
- pydev_log.debug("Set tracing of frame: %s - %s", frame.f_code.co_filename, frame.f_code.co_name)
- frame.f_trace = self.trace_dispatch
- else:
- if DEBUG:
- pydev_log.debug("SKIP set tracing of frame: %s - %s", frame.f_code.co_filename, frame.f_code.co_name)
- frame = frame.f_back
- del frame
- def _create_pydb_command_thread(self):
- curr_pydb_command_thread = self.py_db_command_thread
- if curr_pydb_command_thread is not None:
- curr_pydb_command_thread.do_kill_pydev_thread()
- new_pydb_command_thread = self.py_db_command_thread = PyDBCommandThread(self)
- new_pydb_command_thread.start()
- def _create_check_output_thread(self):
- curr_output_checker_thread = self.check_alive_thread
- if curr_output_checker_thread is not None:
- curr_output_checker_thread.do_kill_pydev_thread()
- check_alive_thread = self.check_alive_thread = CheckAliveThread(self)
- check_alive_thread.start()
- def start_auxiliary_daemon_threads(self):
- self._create_pydb_command_thread()
- self._create_check_output_thread()
- def __wait_for_threads_to_finish(self, timeout):
- try:
- with self._wait_for_threads_to_finish_called_lock:
- wait_for_threads_to_finish_called = self._wait_for_threads_to_finish_called
- self._wait_for_threads_to_finish_called = True
- if wait_for_threads_to_finish_called:
- # Make sure that we wait for the previous call to be finished.
- self._wait_for_threads_to_finish_called_event.wait(timeout=timeout)
- else:
- try:
- def get_pydb_daemon_threads_to_wait():
- pydb_daemon_threads = set(self.created_pydb_daemon_threads)
- pydb_daemon_threads.discard(self.check_alive_thread)
- pydb_daemon_threads.discard(threading.current_thread())
- return pydb_daemon_threads
- pydev_log.debug("PyDB.dispose_and_kill_all_pydevd_threads waiting for pydb daemon threads to finish")
- started_at = time.time()
- # Note: we wait for all except the check_alive_thread (which is not really a daemon
- # thread and it can call this method itself).
- while time.time() < started_at + timeout:
- if len(get_pydb_daemon_threads_to_wait()) == 0:
- break
- time.sleep(1 / 10.0)
- else:
- thread_names = [t.name for t in get_pydb_daemon_threads_to_wait()]
- if thread_names:
- pydev_log.debug("The following pydb threads may not have finished correctly: %s", ", ".join(thread_names))
- finally:
- self._wait_for_threads_to_finish_called_event.set()
- except:
- pydev_log.exception()
- def dispose_and_kill_all_pydevd_threads(self, wait=True, timeout=0.5):
- """
- When this method is called we finish the debug session, terminate threads
- and if this was registered as the global instance, unregister it -- afterwards
- it should be possible to create a new instance and set as global to start
- a new debug session.
- :param bool wait:
- If True we'll wait for the threads to be actually finished before proceeding
- (based on the available timeout).
- Note that this must be thread-safe and if one thread is waiting the other thread should
- also wait.
- """
- try:
- back_frame = sys._getframe().f_back
- pydev_log.debug(
- 'PyDB.dispose_and_kill_all_pydevd_threads (called from: File "%s", line %s, in %s)',
- back_frame.f_code.co_filename,
- back_frame.f_lineno,
- back_frame.f_code.co_name,
- )
- back_frame = None
- with self._disposed_lock:
- disposed = self.pydb_disposed
- self.pydb_disposed = True
- if disposed:
- if wait:
- pydev_log.debug("PyDB.dispose_and_kill_all_pydevd_threads (already disposed - wait)")
- self.__wait_for_threads_to_finish(timeout)
- else:
- pydev_log.debug("PyDB.dispose_and_kill_all_pydevd_threads (already disposed - no wait)")
- return
- pydev_log.debug("PyDB.dispose_and_kill_all_pydevd_threads (first call)")
- # Wait until a time when there are no commands being processed to kill the threads.
- started_at = time.time()
- while time.time() < started_at + timeout:
- with self._main_lock:
- writer = self.writer
- if writer is None or writer.empty():
- pydev_log.debug("PyDB.dispose_and_kill_all_pydevd_threads no commands being processed.")
- break
- else:
- pydev_log.debug("PyDB.dispose_and_kill_all_pydevd_threads timed out waiting for writer to be empty.")
- pydb_daemon_threads = set(self.created_pydb_daemon_threads)
- for t in pydb_daemon_threads:
- if hasattr(t, "do_kill_pydev_thread"):
- pydev_log.debug("PyDB.dispose_and_kill_all_pydevd_threads killing thread: %s", t)
- t.do_kill_pydev_thread()
- if wait:
- self.__wait_for_threads_to_finish(timeout)
- else:
- pydev_log.debug("PyDB.dispose_and_kill_all_pydevd_threads: no wait")
- py_db = get_global_debugger()
- if py_db is self:
- set_global_debugger(None)
- except:
- pydev_log.debug("PyDB.dispose_and_kill_all_pydevd_threads: exception")
- try:
- if DebugInfoHolder.DEBUG_TRACE_LEVEL >= 3:
- pydev_log.exception()
- except:
- pass
- finally:
- if self._client_socket:
- self._client_socket.close()
- self._client_socket = None
- pydev_log.debug("PyDB.dispose_and_kill_all_pydevd_threads: finished")
- def prepare_to_run(self):
- """Shared code to prepare debugging by installing traces and registering threads"""
- self.patch_threads()
- self.start_auxiliary_daemon_threads()
- def patch_threads(self):
- if PYDEVD_USE_SYS_MONITORING:
- pydevd_sys_monitoring.start_monitoring(all_threads=True)
- else:
- try:
- # not available in jython!
- threading.settrace(self.trace_dispatch) # for all future threads
- except:
- pass
- from _pydev_bundle.pydev_monkey import patch_thread_modules
- patch_thread_modules()
- def run(self, file, globals=None, locals=None, is_module=False, set_trace=True):
- module_name = None
- entry_point_fn = ""
- if is_module:
- # When launching with `python -m <module>`, python automatically adds
- # an empty path to the PYTHONPATH which resolves files in the current
- # directory, so, depending how pydevd itself is launched, we may need
- # to manually add such an entry to properly resolve modules in the
- # current directory (see: https://github.com/Microsoft/ptvsd/issues/1010).
- if "" not in sys.path:
- sys.path.insert(0, "")
- file, _, entry_point_fn = file.partition(":")
- module_name = file
- filename = get_fullname(file)
- if filename is None:
- mod_dir = get_package_dir(module_name)
- if mod_dir is None:
- sys.stderr.write("No module named %s\n" % file)
- return
- else:
- filename = get_fullname("%s.__main__" % module_name)
- if filename is None:
- sys.stderr.write("No module named %s\n" % file)
- return
- else:
- file = filename
- else:
- file = filename
- mod_dir = os.path.dirname(filename)
- main_py = os.path.join(mod_dir, "__main__.py")
- main_pyc = os.path.join(mod_dir, "__main__.pyc")
- if filename.endswith("__init__.pyc"):
- if os.path.exists(main_pyc):
- filename = main_pyc
- elif os.path.exists(main_py):
- filename = main_py
- elif filename.endswith("__init__.py"):
- if os.path.exists(main_pyc) and not os.path.exists(main_py):
- filename = main_pyc
- elif os.path.exists(main_py):
- filename = main_py
- sys.argv[0] = filename
- if os.path.isdir(file):
- new_target = os.path.join(file, "__main__.py")
- if os.path.isfile(new_target):
- file = new_target
- m = None
- if globals is None:
- m = save_main_module(file, "pydevd")
- globals = m.__dict__
- try:
- globals["__builtins__"] = __builtins__
- except NameError:
- pass # Not there on Jython...
- if locals is None:
- locals = globals
- # Predefined (writable) attributes: __name__ is the module's name;
- # __doc__ is the module's documentation string, or None if unavailable;
- # __file__ is the pathname of the file from which the module was loaded,
- # if it was loaded from a file. The __file__ attribute is not present for
- # C modules that are statically linked into the interpreter; for extension modules
- # loaded dynamically from a shared library, it is the pathname of the shared library file.
- # I think this is an ugly hack, bug it works (seems to) for the bug that says that sys.path should be the same in
- # debug and run.
- if sys.path[0] != "" and m is not None and m.__file__.startswith(sys.path[0]):
- # print >> sys.stderr, 'Deleting: ', sys.path[0]
- del sys.path[0]
- if not is_module:
- # now, the local directory has to be added to the pythonpath
- # sys.path.insert(0, os.getcwd())
- # Changed: it's not the local directory, but the directory of the file launched
- # The file being run must be in the pythonpath (even if it was not before)
- sys.path.insert(0, os.path.split(os_path_abspath(file))[0])
- if set_trace:
- self.wait_for_ready_to_run()
- # call prepare_to_run when we already have all information about breakpoints
- self.prepare_to_run()
- t = threadingCurrentThread()
- thread_id = get_current_thread_id(t)
- if self.thread_analyser is not None:
- wrap_threads()
- self.thread_analyser.set_start_time(cur_time())
- send_concurrency_message("threading_event", 0, t.name, thread_id, "thread", "start", file, 1, None, parent=thread_id)
- if self.asyncio_analyser is not None:
- # we don't have main thread in asyncio graph, so we should add a fake event
- send_concurrency_message("asyncio_event", 0, "Task", "Task", "thread", "stop", file, 1, frame=None, parent=None)
- try:
- if INTERACTIVE_MODE_AVAILABLE:
- self.init_gui_support()
- except:
- pydev_log.exception("Matplotlib support in debugger failed")
- if hasattr(sys, "exc_clear"):
- # we should clean exception information in Python 2, before user's code execution
- sys.exc_clear()
- # Notify that the main thread is created.
- self.notify_thread_created(thread_id, t)
- # Note: important: set the tracing right before calling _exec.
- if set_trace:
- self.enable_tracing()
- return self._exec(is_module, entry_point_fn, module_name, file, globals, locals)
- def _exec(self, is_module, entry_point_fn, module_name, file, globals, locals):
- """
- This function should have frames tracked by unhandled exceptions (the `_exec` name is important).
- """
- t = threading.current_thread() # Keep in 't' local variable to be accessed afterwards from frame.f_locals.
- if not is_module:
- globals = pydevd_runpy.run_path(file, globals, "__main__")
- else:
- # treat ':' as a separator between module and entry point function
- # if there is no entry point we run we same as with -m switch. Otherwise we perform
- # an import and execute the entry point
- if entry_point_fn:
- mod = __import__(module_name, level=0, fromlist=[entry_point_fn], globals=globals, locals=locals)
- func = getattr(mod, entry_point_fn)
- func()
- else:
- # Run with the -m switch
- globals = pydevd_runpy._run_module_as_main(module_name, alter_argv=False)
- return globals
- def wait_for_commands(self, globals):
- self._activate_gui_if_needed()
- thread = threading.current_thread()
- from _pydevd_bundle import pydevd_frame_utils
- frame = pydevd_frame_utils.Frame(
- None, -1, pydevd_frame_utils.FCode("Console", os.path.abspath(os.path.dirname(__file__))), globals, globals
- )
- thread_id = get_current_thread_id(thread)
- self.add_fake_frame(thread_id, id(frame), frame)
- cmd = self.cmd_factory.make_show_console_message(self, thread_id, frame)
- if self.writer is not None:
- self.writer.add_command(cmd)
- while True:
- if self.gui_in_use:
- # call input hooks if only GUI is in use
- self._call_input_hook()
- self.process_internal_commands()
- time.sleep(0.01)
- class IDAPMessagesListener(object):
- def before_send(self, message_as_dict):
- """
- Called just before a message is sent to the IDE.
- :type message_as_dict: dict
- """
- def after_receive(self, message_as_dict):
- """
- Called just after a message is received from the IDE.
- :type message_as_dict: dict
- """
- def add_dap_messages_listener(dap_messages_listener):
- """
- Adds a listener for the DAP (debug adapter protocol) messages.
- :type dap_messages_listener: IDAPMessagesListener
- :note: messages from the xml backend are not notified through this API.
- :note: the notifications are sent from threads and they are not synchronized (so,
- it's possible that a message is sent and received from different threads at the same time).
- """
- py_db = get_global_debugger()
- if py_db is None:
- raise AssertionError("PyDB is still not setup.")
- py_db.add_dap_messages_listener(dap_messages_listener)
- def send_json_message(msg):
- """
- API to send some custom json message.
- :param dict|pydevd_schema.BaseSchema msg:
- The custom message to be sent.
- :return bool:
- True if the message was added to the queue to be sent and False otherwise.
- """
- py_db = get_global_debugger()
- if py_db is None:
- return False
- writer = py_db.writer
- if writer is None:
- return False
- cmd = NetCommand(-1, 0, msg, is_json=True)
- writer.add_command(cmd)
- return True
- def enable_qt_support(qt_support_mode):
- from _pydev_bundle import pydev_monkey_qt
- pydev_monkey_qt.patch_qt(qt_support_mode)
- def start_dump_threads_thread(filename_template, timeout, recurrent):
- """
- Helper to dump threads after a timeout.
- :param filename_template:
- A template filename, such as 'c:/temp/thread_dump_%s.txt', where the %s will
- be replaced by the time for the dump.
- :param timeout:
- The timeout (in seconds) for the dump.
- :param recurrent:
- If True we'll keep on doing thread dumps.
- """
- assert filename_template.count("%s") == 1, "Expected one %%s to appear in: %s" % (filename_template,)
- def _threads_on_timeout():
- try:
- while True:
- time.sleep(timeout)
- filename = filename_template % (time.time(),)
- try:
- os.makedirs(os.path.dirname(filename))
- except Exception:
- pass
- with open(filename, "w") as stream:
- dump_threads(stream)
- if not recurrent:
- return
- except Exception:
- pydev_log.exception()
- t = threading.Thread(target=_threads_on_timeout)
- mark_as_pydevd_daemon_thread(t)
- t.start()
- def dump_threads(stream=None):
- """
- Helper to dump thread info (default is printing to stderr).
- """
- pydevd_utils.dump_threads(stream)
- def usage(doExit=0):
- sys.stdout.write("Usage:\n")
- sys.stdout.write("pydevd.py --port N [(--client hostname) | --server] --file executable [file_options]\n")
- if doExit:
- sys.exit(0)
- def _init_stdout_redirect():
- pydevd_io.redirect_stream_to_pydb_io_messages(std="stdout")
- def _init_stderr_redirect():
- pydevd_io.redirect_stream_to_pydb_io_messages(std="stderr")
- def _enable_attach(
- address,
- dont_trace_start_patterns=(),
- dont_trace_end_patterns=(),
- patch_multiprocessing=False,
- access_token=None,
- client_access_token=None,
- ):
- """
- Starts accepting connections at the given host/port. The debugger will not be initialized nor
- configured, it'll only start accepting connections (and will have the tracing setup in this
- thread).
- Meant to be used with the DAP (Debug Adapter Protocol) with _wait_for_attach().
- :param address: (host, port)
- :type address: tuple(str, int)
- """
- host = address[0]
- port = int(address[1])
- if SetupHolder.setup is not None:
- if port != SetupHolder.setup["port"]:
- raise AssertionError("Unable to listen in port: %s (already listening in port: %s)" % (port, SetupHolder.setup["port"]))
- settrace(
- host=host,
- port=port,
- suspend=False,
- wait_for_ready_to_run=False,
- block_until_connected=False,
- dont_trace_start_patterns=dont_trace_start_patterns,
- dont_trace_end_patterns=dont_trace_end_patterns,
- patch_multiprocessing=patch_multiprocessing,
- access_token=access_token,
- client_access_token=client_access_token,
- )
- py_db = get_global_debugger()
- py_db.wait_for_server_socket_ready()
- return py_db._server_socket_name
- def _wait_for_attach(cancel=None):
- """
- Meant to be called after _enable_attach() -- the current thread will only unblock after a
- connection is in place and the DAP (Debug Adapter Protocol) sends the ConfigurationDone
- request.
- """
- py_db = get_global_debugger()
- if py_db is None:
- raise AssertionError("Debugger still not created. Please use _enable_attach() before using _wait_for_attach().")
- py_db.block_until_configuration_done(cancel=cancel)
- def _is_attached():
- """
- Can be called any time to check if the connection was established and the DAP (Debug Adapter Protocol) has sent
- the ConfigurationDone request.
- """
- py_db = get_global_debugger()
- return (py_db is not None) and py_db.is_attached()
- # =======================================================================================================================
- # settrace
- # =======================================================================================================================
- def settrace(
- host=None,
- stdout_to_server=False,
- stderr_to_server=False,
- port=5678,
- suspend=True,
- trace_only_current_thread=False,
- overwrite_prev_trace=False, # Deprecated
- patch_multiprocessing=False,
- stop_at_frame=None,
- block_until_connected=True,
- wait_for_ready_to_run=True,
- dont_trace_start_patterns=(),
- dont_trace_end_patterns=(),
- access_token=None,
- client_access_token=None,
- notify_stdin=True,
- protocol=None,
- ppid=0,
- **kwargs,
- ):
- """Sets the tracing function with the pydev debug function and initializes needed facilities.
- :param host: the user may specify another host, if the debug server is not in the same machine (default is the local
- host)
- :param stdout_to_server: when this is true, the stdout is passed to the debug server
- :param stderr_to_server: when this is true, the stderr is passed to the debug server
- so that they are printed in its console and not in this process console.
- :param port: specifies which port to use for communicating with the server (note that the server must be started
- in the same port). @note: currently it's hard-coded at 5678 in the client
- :param suspend: whether a breakpoint should be emulated as soon as this function is called.
- :param trace_only_current_thread: determines if only the current thread will be traced or all current and future
- threads will also have the tracing enabled.
- :param overwrite_prev_trace: deprecated
- :param patch_multiprocessing: if True we'll patch the functions which create new processes so that launched
- processes are debugged.
- :param stop_at_frame: if passed it'll stop at the given frame, otherwise it'll stop in the function which
- called this method.
- :param wait_for_ready_to_run: if True settrace will block until the ready_to_run flag is set to True,
- otherwise, it'll set ready_to_run to True and this function won't block.
- Note that if wait_for_ready_to_run == False, there are no guarantees that the debugger is synchronized
- with what's configured in the client (IDE), the only guarantee is that when leaving this function
- the debugger will be already connected.
- :param dont_trace_start_patterns: if set, then any path that starts with one fo the patterns in the collection
- will not be traced
- :param dont_trace_end_patterns: if set, then any path that ends with one fo the patterns in the collection
- will not be traced
- :param access_token: token to be sent from the client (i.e.: IDE) to the debugger when a connection
- is established (verified by the debugger).
- :param client_access_token: token to be sent from the debugger to the client (i.e.: IDE) when
- a connection is established (verified by the client).
- :param notify_stdin:
- If True sys.stdin will be patched to notify the client when a message is requested
- from the IDE. This is done so that when reading the stdin the client is notified.
- Clients may need this to know when something that is being written should be interpreted
- as an input to the process or as a command to be evaluated.
- Note that parallel-python has issues with this (because it tries to assert that sys.stdin
- is of a given type instead of just checking that it has what it needs).
- :param protocol:
- When using in Eclipse the protocol should not be passed, but when used in VSCode
- or some other IDE/editor that accepts the Debug Adapter Protocol then 'dap' should
- be passed.
- :param ppid:
- Override the parent process id (PPID) for the current debugging session. This PPID is
- reported to the debug client (IDE) and can be used to act like a child process of an
- existing debugged process without being a child process.
- """
- if protocol and protocol.lower() == "dap":
- pydevd_defaults.PydevdCustomization.DEFAULT_PROTOCOL = pydevd_constants.HTTP_JSON_PROTOCOL
- stdout_to_server = stdout_to_server or kwargs.get("stdoutToServer", False) # Backward compatibility
- stderr_to_server = stderr_to_server or kwargs.get("stderrToServer", False) # Backward compatibility
- # Internal use (may be used to set the setup info directly for subprocesess).
- __setup_holder__ = kwargs.get("__setup_holder__")
- with _set_trace_lock:
- _locked_settrace(
- host,
- stdout_to_server,
- stderr_to_server,
- port,
- suspend,
- trace_only_current_thread,
- patch_multiprocessing,
- stop_at_frame,
- block_until_connected,
- wait_for_ready_to_run,
- dont_trace_start_patterns,
- dont_trace_end_patterns,
- access_token,
- client_access_token,
- __setup_holder__=__setup_holder__,
- notify_stdin=notify_stdin,
- ppid=ppid,
- )
- _set_trace_lock = ForkSafeLock()
- def _locked_settrace(
- host,
- stdout_to_server,
- stderr_to_server,
- port,
- suspend,
- trace_only_current_thread,
- patch_multiprocessing,
- stop_at_frame,
- block_until_connected,
- wait_for_ready_to_run,
- dont_trace_start_patterns,
- dont_trace_end_patterns,
- access_token,
- client_access_token,
- __setup_holder__,
- notify_stdin,
- ppid,
- ):
- if patch_multiprocessing:
- try:
- from _pydev_bundle import pydev_monkey
- except:
- pass
- else:
- pydev_monkey.patch_new_process_functions()
- if host is None:
- from _pydev_bundle import pydev_localhost
- host = pydev_localhost.get_localhost()
- global _global_redirect_stdout_to_server
- global _global_redirect_stderr_to_server
- py_db = get_global_debugger()
- if __setup_holder__:
- SetupHolder.setup = __setup_holder__
- if py_db is None:
- py_db = PyDB()
- pydevd_vm_type.setup_type()
- if SetupHolder.setup is None:
- setup = {
- "client": host, # dispatch expects client to be set to the host address when server is False
- "server": False,
- "port": int(port),
- "multiprocess": patch_multiprocessing,
- "skip-notify-stdin": not notify_stdin,
- pydevd_constants.ARGUMENT_PPID: ppid,
- }
- SetupHolder.setup = setup
- if access_token is not None:
- py_db.authentication.access_token = access_token
- SetupHolder.setup["access-token"] = access_token
- if client_access_token is not None:
- py_db.authentication.client_access_token = client_access_token
- SetupHolder.setup["client-access-token"] = client_access_token
- if block_until_connected:
- py_db.connect(host, port) # Note: connect can raise error.
- else:
- # Create a dummy writer and wait for the real connection.
- py_db.writer = WriterThread(NULL, py_db, terminate_on_socket_close=False)
- py_db.create_wait_for_connection_thread()
- if dont_trace_start_patterns or dont_trace_end_patterns:
- PyDevdAPI().set_dont_trace_start_end_patterns(py_db, dont_trace_start_patterns, dont_trace_end_patterns)
- _global_redirect_stdout_to_server = stdout_to_server
- _global_redirect_stderr_to_server = stderr_to_server
- if _global_redirect_stdout_to_server:
- _init_stdout_redirect()
- if _global_redirect_stderr_to_server:
- _init_stderr_redirect()
- if notify_stdin:
- patch_stdin()
- t = threadingCurrentThread()
- additional_info = set_additional_thread_info(t)
- if not wait_for_ready_to_run:
- py_db.ready_to_run = True
- py_db.wait_for_ready_to_run()
- py_db.start_auxiliary_daemon_threads()
- try:
- if INTERACTIVE_MODE_AVAILABLE:
- py_db.init_gui_support()
- except:
- pydev_log.exception("Matplotlib support in debugger failed")
- if trace_only_current_thread:
- py_db.enable_tracing()
- else:
- # Trace future threads.
- py_db.patch_threads()
- py_db.enable_tracing(py_db.trace_dispatch, apply_to_all_threads=True)
- # As this is the first connection, also set tracing for any untraced threads
- py_db.set_tracing_for_untraced_contexts()
- py_db.set_trace_for_frame_and_parents(t.ident, get_frame().f_back)
- with CustomFramesContainer.custom_frames_lock: # @UndefinedVariable
- for _frameId, custom_frame in CustomFramesContainer.custom_frames.items():
- py_db.set_trace_for_frame_and_parents(None, custom_frame.frame)
- else:
- # ok, we're already in debug mode, with all set, so, let's just set the break
- if access_token is not None:
- py_db.authentication.access_token = access_token
- if client_access_token is not None:
- py_db.authentication.client_access_token = client_access_token
- t = threadingCurrentThread()
- py_db.set_trace_for_frame_and_parents(t.ident, get_frame().f_back)
- additional_info = set_additional_thread_info(t)
- if trace_only_current_thread:
- py_db.enable_tracing()
- else:
- # Trace future threads.
- py_db.patch_threads()
- py_db.enable_tracing(py_db.trace_dispatch, apply_to_all_threads=True)
- # Suspend as the last thing after all tracing is in place.
- if suspend:
- additional_info.pydev_original_step_cmd = CMD_SET_BREAK
- if stop_at_frame is not None:
- # If the step was set we have to go to run state and
- # set the proper frame for it to stop.
- additional_info.pydev_state = STATE_RUN
- additional_info.pydev_step_cmd = CMD_STEP_OVER
- additional_info.pydev_step_stop = stop_at_frame
- additional_info.suspend_type = PYTHON_SUSPEND
- additional_info.update_stepping_info()
- if PYDEVD_USE_SYS_MONITORING:
- pydevd_sys_monitoring.update_monitor_events(suspend_requested=True)
- py_db.set_trace_for_frame_and_parents(t.ident, stop_at_frame)
- else:
- # Ask to break as soon as possible.
- py_db.set_suspend(t, CMD_SET_BREAK, suspend_requested=True)
- py_db.set_trace_for_frame_and_parents(t.ident, get_frame().f_back)
- if PYDEVD_USE_SYS_MONITORING:
- pydevd_sys_monitoring.restart_events()
- def stoptrace():
- pydev_log.debug("pydevd.stoptrace()")
- pydevd_tracing.restore_sys_set_trace_func()
- if PYDEVD_USE_SYS_MONITORING:
- pydevd_sys_monitoring.stop_monitoring(all_threads=True)
- else:
- sys.settrace(None)
- try:
- # not available in jython!
- threading.settrace(None) # for all future threads
- except:
- pass
- from _pydev_bundle.pydev_monkey import undo_patch_thread_modules
- undo_patch_thread_modules()
- # Either or both standard streams can be closed at this point,
- # in which case flush() will fail.
- try:
- sys.stdout.flush()
- except:
- pass
- try:
- sys.stderr.flush()
- except:
- pass
- py_db = get_global_debugger()
- if py_db is not None:
- py_db.dispose_and_kill_all_pydevd_threads()
- class Dispatcher(object):
- def __init__(self):
- self.port = None
- def connect(self, host, port):
- self.host = host
- self.port = port
- self.client = start_client(self.host, self.port)
- self.reader = DispatchReader(self)
- self.reader.pydev_do_not_trace = False # we run reader in the same thread so we don't want to loose tracing
- self.reader.run()
- def close(self):
- try:
- self.reader.do_kill_pydev_thread()
- except:
- pass
- class DispatchReader(ReaderThread):
- def __init__(self, dispatcher):
- self.dispatcher = dispatcher
- ReaderThread.__init__(
- self,
- get_global_debugger(),
- self.dispatcher.client,
- PyDevJsonCommandProcessor=PyDevJsonCommandProcessor,
- process_net_command=process_net_command,
- )
- @overrides(ReaderThread._on_run)
- def _on_run(self):
- dummy_thread = threading.current_thread()
- dummy_thread.is_pydev_daemon_thread = False
- return ReaderThread._on_run(self)
- @overrides(PyDBDaemonThread.do_kill_pydev_thread)
- def do_kill_pydev_thread(self):
- if not self._kill_received:
- ReaderThread.do_kill_pydev_thread(self)
- try:
- self.sock.shutdown(SHUT_RDWR)
- except:
- pass
- try:
- self.sock.close()
- except:
- pass
- def process_command(self, cmd_id, seq, text):
- if cmd_id == 99:
- self.dispatcher.port = int(text)
- self._kill_received = True
- DISPATCH_APPROACH_NEW_CONNECTION = 1 # Used by PyDev
- DISPATCH_APPROACH_EXISTING_CONNECTION = 2 # Used by PyCharm
- DISPATCH_APPROACH = DISPATCH_APPROACH_NEW_CONNECTION
- def dispatch():
- setup = SetupHolder.setup
- host = setup["client"]
- port = setup["port"]
- if DISPATCH_APPROACH == DISPATCH_APPROACH_EXISTING_CONNECTION:
- dispatcher = Dispatcher()
- try:
- dispatcher.connect(host, port)
- port = dispatcher.port
- finally:
- dispatcher.close()
- return host, port
- def settrace_forked(setup_tracing=True):
- """
- When creating a fork from a process in the debugger, we need to reset the whole debugger environment!
- """
- from _pydevd_bundle.pydevd_constants import GlobalDebuggerHolder
- py_db = GlobalDebuggerHolder.global_dbg
- if py_db is not None:
- py_db.created_pydb_daemon_threads = {} # Just making sure we won't touch those (paused) threads.
- py_db = None
- GlobalDebuggerHolder.global_dbg = None
- threading.current_thread().additional_info = None
- # Make sure that we keep the same access tokens for subprocesses started through fork.
- setup = SetupHolder.setup
- if setup is None:
- setup = {}
- else:
- # i.e.: Get the ppid at this point as it just changed.
- # If we later do an exec() it should remain the same ppid.
- setup[pydevd_constants.ARGUMENT_PPID] = PyDevdAPI().get_ppid()
- access_token = setup.get("access-token")
- client_access_token = setup.get("client-access-token")
- if setup_tracing:
- from _pydevd_frame_eval.pydevd_frame_eval_main import clear_thread_local_info
- host, port = dispatch()
- import pydevd_tracing
- pydevd_tracing.restore_sys_set_trace_func()
- if setup_tracing:
- if port is not None:
- custom_frames_container_init()
- if clear_thread_local_info is not None:
- clear_thread_local_info()
- if PYDEVD_USE_SYS_MONITORING:
- pydevd_sys_monitoring.reset_thread_local_info()
- settrace(
- host,
- port=port,
- suspend=False,
- trace_only_current_thread=False,
- overwrite_prev_trace=True,
- patch_multiprocessing=True,
- access_token=access_token,
- client_access_token=client_access_token,
- )
- @contextmanager
- def skip_subprocess_arg_patch():
- """
- May be used to skip the monkey-patching that pydevd does to
- skip changing arguments to embed the debugger into child processes.
- i.e.:
- with pydevd.skip_subprocess_arg_patch():
- subprocess.call(...)
- """
- from _pydev_bundle import pydev_monkey
- with pydev_monkey.skip_subprocess_arg_patch():
- yield
- def add_dont_terminate_child_pid(pid):
- """
- May be used to ask pydevd to skip the termination of some process
- when it's asked to terminate (debug adapter protocol only).
- :param int pid:
- The pid to be ignored.
- i.e.:
- process = subprocess.Popen(...)
- pydevd.add_dont_terminate_child_pid(process.pid)
- """
- py_db = get_global_debugger()
- if py_db is not None:
- py_db.dont_terminate_child_pids.add(pid)
- class SetupHolder:
- setup = None
- def apply_debugger_options(setup_options):
- """
- :type setup_options: dict[str, bool]
- """
- default_options = {"save-signatures": False, "qt-support": ""}
- default_options.update(setup_options)
- setup_options = default_options
- debugger = get_global_debugger()
- if setup_options["save-signatures"]:
- if pydevd_vm_type.get_vm_type() == pydevd_vm_type.PydevdVmType.JYTHON:
- sys.stderr.write("Collecting run-time type information is not supported for Jython\n")
- else:
- # Only import it if we're going to use it!
- from _pydevd_bundle.pydevd_signature import SignatureFactory
- debugger.signature_factory = SignatureFactory()
- if setup_options["qt-support"]:
- enable_qt_support(setup_options["qt-support"])
- @call_only_once
- def patch_stdin():
- _internal_patch_stdin(None, sys, getpass_mod)
- def _internal_patch_stdin(py_db=None, sys=None, getpass_mod=None):
- """
- Note: don't use this function directly, use `patch_stdin()` instead.
- (this function is only meant to be used on test-cases to avoid patching the actual globals).
- """
- # Patch stdin so that we notify when readline() is called.
- original_sys_stdin = sys.stdin
- debug_console_stdin = DebugConsoleStdIn(py_db, original_sys_stdin)
- sys.stdin = debug_console_stdin
- _original_getpass = getpass_mod.getpass
- @functools.wraps(_original_getpass)
- def getpass(*args, **kwargs):
- with DebugConsoleStdIn.notify_input_requested(debug_console_stdin):
- try:
- curr_stdin = sys.stdin
- if curr_stdin is debug_console_stdin:
- sys.stdin = original_sys_stdin
- return _original_getpass(*args, **kwargs)
- finally:
- sys.stdin = curr_stdin
- getpass_mod.getpass = getpass
- # Dispatch on_debugger_modules_loaded here, after all primary py_db modules are loaded
- for handler in pydevd_extension_utils.extensions_of_type(DebuggerEventHandler):
- handler.on_debugger_modules_loaded(debugger_version=__version__)
- def log_to(log_file: str, log_level=3) -> None:
- """
- In pydevd it's possible to log by setting the following environment variables:
- PYDEVD_DEBUG=1 (sets the default log level to 3 along with other default options)
- PYDEVD_DEBUG_FILE=</path/to/file.log>
- Note that the file will have the pid of the process added to it (so, logging to
- /path/to/file.log would actually start logging to /path/to/file.<pid>.log -- if subprocesses are
- logged, each new subprocess will have the logging set to its own pid).
- Usually setting the environment variable is preferred as it'd log information while
- pydevd is still doing its imports and not just after this method is called, but on
- cases where this is hard to do this function may be called to set the tracing after
- pydevd itself is already imported.
- """
- pydev_log.log_to(log_file, log_level)
- def _log_initial_info():
- pydev_log.debug("Initial arguments: %s", (sys.argv,))
- pydev_log.debug("Current pid: %s", os.getpid())
- pydev_log.debug("Using cython: %s", USING_CYTHON)
- pydev_log.debug("Using frame eval: %s", USING_FRAME_EVAL)
- pydev_log.debug("Using gevent mode: %s / imported gevent module support: %s", SUPPORT_GEVENT, bool(pydevd_gevent_integration))
- def config(protocol="", debug_mode="", preimport=""):
- pydev_log.debug("Config: protocol: %s, debug_mode: %s, preimport: %s", protocol, debug_mode, preimport)
- PydevdCustomization.DEFAULT_PROTOCOL = protocol
- PydevdCustomization.DEBUG_MODE = debug_mode
- PydevdCustomization.PREIMPORT = preimport
- # =======================================================================================================================
- # main
- # =======================================================================================================================
- def main():
- # parse the command line. --file is our last argument that is required
- _log_initial_info()
- original_argv = sys.argv[:]
- try:
- from _pydevd_bundle.pydevd_command_line_handling import process_command_line
- setup = process_command_line(sys.argv)
- SetupHolder.setup = setup
- except ValueError:
- pydev_log.exception()
- usage(1)
- preimport = setup.get("preimport")
- if preimport:
- pydevd_defaults.PydevdCustomization.PREIMPORT = preimport
- debug_mode = setup.get("debug-mode")
- if debug_mode:
- pydevd_defaults.PydevdCustomization.DEBUG_MODE = debug_mode
- log_trace_level = setup.get("log-level")
- # Note: the logging info could've been changed (this would happen if this is a
- # subprocess and the value in the environment variable does not match the value in the
- # argument because the user used `pydevd.log_to` instead of supplying the environment
- # variable). If this is the case, update the logging info and re-log some information
- # in the new target.
- new_debug_file = setup.get("log-file")
- if new_debug_file and DebugInfoHolder.PYDEVD_DEBUG_FILE != new_debug_file:
- # The debug file can't be set directly, we need to use log_to() so that the a
- # new stream is actually created for the new file.
- log_to(new_debug_file, log_trace_level if log_trace_level is not None else 3)
- _log_initial_info() # The redirection info just changed, log it again.
- elif log_trace_level is not None:
- # The log file was not specified
- DebugInfoHolder.DEBUG_TRACE_LEVEL = log_trace_level
- pydev_log.debug("Original sys.argv: %s", original_argv)
- if setup["print-in-debugger-startup"]:
- try:
- pid = " (pid: %s)" % os.getpid()
- except:
- pid = ""
- sys.stderr.write("pydev debugger: starting%s\n" % pid)
- pydev_log.debug("Executing file %s", setup["file"])
- pydev_log.debug("arguments: %s", (sys.argv,))
- pydevd_vm_type.setup_type(setup.get("vm_type", None))
- port = setup["port"]
- host = setup["client"]
- f = setup["file"]
- fix_app_engine_debug = False
- debugger = get_global_debugger()
- if debugger is None:
- debugger = PyDB()
- try:
- from _pydev_bundle import pydev_monkey
- except:
- pass # Not usable on jython 2.1
- else:
- if setup["multiprocess"]: # PyDev
- pydev_monkey.patch_new_process_functions()
- elif setup["multiproc"]: # PyCharm
- pydev_log.debug("Started in multiproc mode\n")
- global DISPATCH_APPROACH
- DISPATCH_APPROACH = DISPATCH_APPROACH_EXISTING_CONNECTION
- dispatcher = Dispatcher()
- try:
- dispatcher.connect(host, port)
- if dispatcher.port is not None:
- port = dispatcher.port
- pydev_log.debug("Received port %d\n", port)
- pydev_log.info("pydev debugger: process %d is connecting\n" % os.getpid())
- try:
- pydev_monkey.patch_new_process_functions()
- except:
- pydev_log.exception("Error patching process functions.")
- else:
- pydev_log.critical("pydev debugger: couldn't get port for new debug process.")
- finally:
- dispatcher.close()
- else:
- try:
- pydev_monkey.patch_new_process_functions_with_warning()
- except:
- pydev_log.exception("Error patching process functions.")
- # Only do this patching if we're not running with multiprocess turned on.
- if f.find("dev_appserver.py") != -1:
- if os.path.basename(f).startswith("dev_appserver.py"):
- appserver_dir = os.path.dirname(f)
- version_file = os.path.join(appserver_dir, "VERSION")
- if os.path.exists(version_file):
- try:
- stream = open(version_file, "r")
- try:
- for line in stream.read().splitlines():
- line = line.strip()
- if line.startswith("release:"):
- line = line[8:].strip()
- version = line.replace('"', "")
- version = version.split(".")
- if int(version[0]) > 1:
- fix_app_engine_debug = True
- elif int(version[0]) == 1:
- if int(version[1]) >= 7:
- # Only fix from 1.7 onwards
- fix_app_engine_debug = True
- break
- finally:
- stream.close()
- except:
- pydev_log.exception()
- try:
- # In the default run (i.e.: run directly on debug mode), we try to patch stackless as soon as possible
- # on a run where we have a remote debug, we may have to be more careful because patching stackless means
- # that if the user already had a stackless.set_schedule_callback installed, he'd loose it and would need
- # to call it again (because stackless provides no way of getting the last function which was registered
- # in set_schedule_callback).
- #
- # So, ideally, if there's an application using stackless and the application wants to use the remote debugger
- # and benefit from stackless debugging, the application itself must call:
- #
- # import pydevd_stackless
- # pydevd_stackless.patch_stackless()
- #
- # itself to be able to benefit from seeing the tasklets created before the remote debugger is attached.
- from _pydevd_bundle import pydevd_stackless
- pydevd_stackless.patch_stackless()
- except:
- # It's ok not having stackless there...
- try:
- if hasattr(sys, "exc_clear"):
- sys.exc_clear() # the exception information should be cleaned in Python 2
- except:
- pass
- is_module = setup["module"]
- if not setup["skip-notify-stdin"]:
- patch_stdin()
- if setup[pydevd_constants.ARGUMENT_JSON_PROTOCOL]:
- PyDevdAPI().set_protocol(debugger, 0, JSON_PROTOCOL)
- elif setup[pydevd_constants.ARGUMENT_HTTP_JSON_PROTOCOL]:
- PyDevdAPI().set_protocol(debugger, 0, HTTP_JSON_PROTOCOL)
- elif setup[pydevd_constants.ARGUMENT_HTTP_PROTOCOL]:
- PyDevdAPI().set_protocol(debugger, 0, pydevd_constants.HTTP_PROTOCOL)
- elif setup[pydevd_constants.ARGUMENT_QUOTED_LINE_PROTOCOL]:
- PyDevdAPI().set_protocol(debugger, 0, pydevd_constants.QUOTED_LINE_PROTOCOL)
- access_token = setup["access-token"]
- if access_token:
- debugger.authentication.access_token = access_token
- client_access_token = setup["client-access-token"]
- if client_access_token:
- debugger.authentication.client_access_token = client_access_token
- if fix_app_engine_debug:
- sys.stderr.write("pydev debugger: google app engine integration enabled\n")
- curr_dir = os.path.dirname(__file__)
- app_engine_startup_file = os.path.join(curr_dir, "pydev_app_engine_debug_startup.py")
- sys.argv.insert(1, "--python_startup_script=" + app_engine_startup_file)
- import json
- setup["pydevd"] = __file__
- sys.argv.insert(
- 2,
- "--python_startup_args=%s" % json.dumps(setup),
- )
- sys.argv.insert(3, "--automatic_restart=no")
- sys.argv.insert(4, "--max_module_instances=1")
- # Run the dev_appserver
- debugger.run(setup["file"], None, None, is_module, set_trace=False)
- else:
- if setup["save-threading"]:
- debugger.thread_analyser = ThreadingLogger()
- if setup["save-asyncio"]:
- debugger.asyncio_analyser = AsyncioLogger()
- apply_debugger_options(setup)
- try:
- debugger.connect(host, port)
- except:
- sys.stderr.write("Could not connect to %s: %s\n" % (host, port))
- pydev_log.exception()
- sys.exit(1)
- globals = debugger.run(setup["file"], None, None, is_module)
- if setup["cmd-line"]:
- debugger.wait_for_commands(globals)
- try:
- # Remove the entry we added: it should no longer be needed as
- # what we need should've been imported already
- if sys.path[:1] == [this_dir]:
- sys.path.remove(this_dir)
- except Exception:
- pass
- if __name__ == "__main__":
- main()
|