Skip to content

Commit 943490a

Browse files
authored
[Debugger] Adding debupgy as the ray debugger (ray-project#42311)
- enabled debugpy as the ray debugger for breakpoint and post_mortem debugging - added flag RAY_DEBUG=1 to enable debugpy. If RAY_DEBUG is not set and RAY_PDB is set, then rpdb will be used. - used state api to save worker debugging port.
1 parent 587eb0d commit 943490a

File tree

3 files changed

+143
-1
lines changed

3 files changed

+143
-1
lines changed

python/ray/util/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from ray._private.services import get_node_ip_address
77
from ray.util import iter
88
from ray.util import rpdb as pdb
9+
from ray.util import debugpy as ray_debugpy
910
from ray.util.actor_pool import ActorPool
1011
from ray.util import accelerators
1112
from ray.util.annotations import PublicAPI
@@ -62,6 +63,7 @@ def list_named_actors(all_namespaces: bool = False) -> List[str]:
6263
"get_current_placement_group",
6364
"get_node_ip_address",
6465
"remove_placement_group",
66+
"ray_debugpy",
6567
"inspect_serializability",
6668
"collective",
6769
"connect",

python/ray/util/debugpy.py

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import logging
2+
import os
3+
import sys
4+
import threading
5+
import importlib
6+
7+
import ray
8+
from ray.util.annotations import DeveloperAPI
9+
10+
log = logging.getLogger(__name__)
11+
12+
POST_MORTEM_ERROR_UUID = "post_mortem_error_uuid"
13+
14+
15+
def _try_import_debugpy():
16+
try:
17+
debugpy = importlib.import_module("debugpy")
18+
if not hasattr(debugpy, "__version__") or debugpy.__version__ < "1.8.0":
19+
raise ImportError()
20+
return debugpy
21+
except (ModuleNotFoundError, ImportError):
22+
log.error(
23+
"Module 'debugpy>=1.8.0' cannot be loaded. "
24+
"Ray Debugpy Debugger will not work without 'debugpy>=1.8.0' installed. "
25+
"Install this module using 'pip install debugpy==1.8.0' "
26+
)
27+
return None
28+
29+
30+
# A lock to ensure that only one thread can open the debugger port.
31+
debugger_port_lock = threading.Lock()
32+
33+
34+
def _override_breakpoint_hooks():
35+
"""
36+
This method overrides the breakpoint() function to set_trace()
37+
so that other threads can reuse the same setup logic.
38+
This is based on: https://github.com/microsoft/debugpy/blob/ef9a67fe150179ee4df9997f9273723c26687fab/src/debugpy/_vendored/pydevd/pydev_sitecustomize/sitecustomize.py#L87 # noqa: E501
39+
"""
40+
sys.__breakpointhook__ = set_trace
41+
sys.breakpointhook = set_trace
42+
import builtins as __builtin__
43+
44+
__builtin__.breakpoint = set_trace
45+
46+
47+
def _ensure_debugger_port_open_thread_safe():
48+
"""
49+
This is a thread safe method that ensure that the debugger port
50+
is open, and if not, open it.
51+
"""
52+
53+
# The lock is acquired before checking the debugger port so only
54+
# one thread can open the debugger port.
55+
with debugger_port_lock:
56+
debugpy = _try_import_debugpy()
57+
if not debugpy:
58+
return
59+
60+
debugger_port = ray._private.worker.global_worker.debugger_port
61+
if not debugger_port:
62+
(host, port) = debugpy.listen(
63+
(ray._private.worker.global_worker.node_ip_address, 0)
64+
)
65+
ray._private.worker.global_worker.set_debugger_port(port)
66+
log.info(f"Ray debugger is listening on {host}:{port}")
67+
else:
68+
log.info(f"Ray debugger is already open on {debugger_port}")
69+
70+
71+
@DeveloperAPI
72+
def set_trace(breakpoint_uuid=None):
73+
"""Interrupt the flow of the program and drop into the Ray debugger.
74+
Can be used within a Ray task or actor.
75+
"""
76+
debugpy = _try_import_debugpy()
77+
if not debugpy:
78+
return
79+
80+
_ensure_debugger_port_open_thread_safe()
81+
82+
# debugpy overrides the breakpoint() function, so we need to set it back
83+
# so other threads can reuse it.
84+
_override_breakpoint_hooks()
85+
86+
with ray._private.worker.global_worker.worker_paused_by_debugger():
87+
log.info("Waiting for debugger to attach...")
88+
debugpy.wait_for_client()
89+
90+
log.info("Debugger client is connected")
91+
if breakpoint_uuid == POST_MORTEM_ERROR_UUID:
92+
_debugpy_excepthook()
93+
else:
94+
_debugpy_breakpoint()
95+
96+
97+
def _debugpy_breakpoint():
98+
"""
99+
Drop the user into the debugger on a breakpoint.
100+
"""
101+
import pydevd
102+
103+
pydevd.settrace(stop_at_frame=sys._getframe().f_back)
104+
105+
106+
def _debugpy_excepthook():
107+
"""
108+
Drop the user into the debugger on an unhandled exception.
109+
"""
110+
import threading
111+
112+
import pydevd
113+
114+
py_db = pydevd.get_global_debugger()
115+
thread = threading.current_thread()
116+
additional_info = py_db.set_additional_thread_info(thread)
117+
additional_info.is_tracing += 1
118+
try:
119+
error = sys.exc_info()
120+
py_db.stop_on_unhandled_exception(py_db, thread, additional_info, error)
121+
sys.excepthook(error[0], error[1], error[2])
122+
finally:
123+
additional_info.is_tracing -= 1
124+
125+
126+
def _is_ray_debugger_enabled():
127+
return "RAY_DEBUG" in os.environ
128+
129+
130+
def _post_mortem():
131+
return set_trace(POST_MORTEM_ERROR_UUID)

python/ray/util/rpdb.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,9 @@ def set_trace(breakpoint_uuid=None):
280280
281281
Can be used within a Ray task or actor.
282282
"""
283+
if ray.util.ray_debugpy._is_ray_debugger_enabled():
284+
return ray.util.ray_debugpy.set_trace(breakpoint_uuid)
285+
283286
# If there is an active debugger already, we do not want to
284287
# start another one, so "set_trace" is just a no-op in that case.
285288
if ray._private.worker.global_worker.debugger_breakpoint == b"":
@@ -301,6 +304,9 @@ def _driver_set_trace():
301304
This disables Ray driver logs temporarily so that the PDB console is not
302305
spammed: https://github.com/ray-project/ray/issues/18172
303306
"""
307+
if ray.util.ray_debugpy._is_ray_debugger_enabled():
308+
return ray.util.ray_debugpy.set_trace()
309+
304310
print("*** Temporarily disabling Ray worker logs ***")
305311
ray._private.worker._worker_logs_enabled = False
306312

@@ -314,10 +320,13 @@ def enable_logging():
314320

315321

316322
def _is_ray_debugger_enabled():
317-
return "RAY_PDB" in os.environ
323+
return "RAY_PDB" in os.environ or ray.util.ray_debugpy._is_ray_debugger_enabled()
318324

319325

320326
def _post_mortem():
327+
if ray.util.ray_debugpy._is_ray_debugger_enabled():
328+
return ray.util.ray_debugpy._post_mortem()
329+
321330
rdb = _connect_ray_pdb(
322331
host=None,
323332
port=None,

0 commit comments

Comments
 (0)