Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[sys.monitoring] Breakpoints #1512

Merged
merged 3 commits into from
Feb 29, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/debugpy/common/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,5 +160,6 @@ def hide_thread_from_debugger(thread):
DEBUGPY_TRACE_DEBUGPY is used to debug debugpy with debugpy
"""
if hide_debugpy_internals():
thread.is_debugpy_thread = True
thread.pydev_do_not_trace = True
thread.is_pydev_daemon_thread = True
5 changes: 5 additions & 0 deletions src/debugpy/server/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@


def adapter():
"""
Returns the instance of Adapter corresponding to the debug adapter that is currently
connected to this process, or None if there is no adapter connected. Use in lieu of
Adapter.instance to avoid import cycles.
"""
from debugpy.server.adapters import Adapter

return Adapter.instance
61 changes: 35 additions & 26 deletions src/debugpy/server/adapters.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@

from debugpy.adapter import components
from debugpy.common import json, log, messaging, sockets
from debugpy.common.messaging import Request
from debugpy.server import tracing, eval
from debugpy.server.tracing import Breakpoint, StackFrame
from debugpy.common.messaging import MessageDict, Request
from debugpy.server import eval
from debugpy.server.tracing import Breakpoint, StackFrame, Thread, Tracer


class Adapter:
Expand Down Expand Up @@ -50,13 +50,13 @@ class Expectations(components.Capabilities):
server_access_token = None
"""Access token that the adapter must use to authenticate with this server."""


_is_initialized: bool = False
_has_started: bool = False
_client_id: str = None
_capabilities: Capabilities = None
_expectations: Expectations = None
_start_request: messaging.Request = None
_tracer: Tracer = None

def __init__(self, stream: messaging.JsonIOStream):
self._is_initialized = False
Expand All @@ -65,6 +65,7 @@ def __init__(self, stream: messaging.JsonIOStream):
self._capabilities = None
self._expectations = None
self._start_request = None
self._tracer = Tracer.instance

self.channel = messaging.JsonMessageChannel(stream, self)
self.channel.start()
Expand Down Expand Up @@ -139,6 +140,8 @@ def initialize_request(self, request: Request):
]

return {
"exceptionBreakpointFilters": exception_breakpoint_filters,
"supportsClipboardContext": True,
"supportsCompletionsRequest": True,
"supportsConditionalBreakpoints": True,
"supportsConfigurationDoneRequest": True,
Expand All @@ -148,17 +151,15 @@ def initialize_request(self, request: Request):
"supportsExceptionInfoRequest": True,
"supportsExceptionOptions": True,
"supportsFunctionBreakpoints": True,
"supportsGotoTargetsRequest": True,
"supportsHitConditionalBreakpoints": True,
"supportsLogPoints": True,
"supportsModulesRequest": True,
"supportsSetExpression": True,
"supportsSetVariable": True,
"supportsValueFormattingOptions": True,
"supportsTerminateRequest": True,
"supportsGotoTargetsRequest": True,
"supportsClipboardContext": True,
"exceptionBreakpointFilters": exception_breakpoint_filters,
"supportsStepInTargetsRequest": True,
"supportsTerminateRequest": True,
"supportsValueFormattingOptions": True,
}

def _handle_start_request(self, request: Request):
Expand Down Expand Up @@ -189,7 +190,7 @@ def configurationDone_request(self, request: Request):
'or an "attach" request'
)

tracing.start()
self._tracer.start()
self._has_started = True

request.respond({})
Expand Down Expand Up @@ -233,20 +234,28 @@ def setBreakpoints_request(self, request: Request):
bps = list(request("breakpoints", json.array(json.object())))
else:
lines = request("lines", json.array(int))
bps = [{"line": line} for line in lines]
bps = [MessageDict(request, {"line": line}) for line in lines]

Breakpoint.clear([path])
bps_set = [Breakpoint.set(path, bp["line"]) for bp in bps]
bps_set = [
Breakpoint.set(
path, bp["line"],
condition=bp("condition", str, optional=True),
hit_condition=bp("hitCondition", str, optional=True),
log_message=bp("logMessage", str, optional=True),
)
for bp in bps
]
return {"breakpoints": bps_set}

def threads_request(self, request: Request):
return {"threads": tracing.Thread.enumerate()}
return {"threads": Thread.enumerate()}

def stackTrace_request(self, request: Request):
thread_id = request("threadId", int)
start_frame = request("startFrame", 0)

thread = tracing.Thread.get(thread_id)
thread = Thread.get(thread_id)
if thread is None:
raise request.isnt_valid(f'Invalid "threadId": {thread_id}')

Expand All @@ -265,7 +274,7 @@ def pause_request(self, request: Request):
thread_ids = None
else:
thread_ids = [request("threadId", int)]
tracing.pause(thread_ids)
self._tracer.pause(thread_ids)
return {}

def continue_request(self, request: Request):
Expand All @@ -274,25 +283,25 @@ def continue_request(self, request: Request):
else:
thread_ids = [request("threadId", int)]
single_thread = request("singleThread", False)
tracing.resume(thread_ids if single_thread else None)
self._tracer.resume(thread_ids if single_thread else None)
return {}

def stepIn_request(self, request: Request):
# TODO: support "singleThread" and "granularity"
thread_id = request("threadId", int)
tracing.step_in(thread_id)
self._tracer.step_in(thread_id)
return {}

def stepOut_request(self, request: Request):
# TODO: support "singleThread" and "granularity"
thread_id = request("threadId", int)
tracing.step_out(thread_id)
self._tracer.step_out(thread_id)
return {}

def next_request(self, request: Request):
# TODO: support "singleThread" and "granularity"
thread_id = request("threadId", int)
tracing.step_over(thread_id)
self._tracer.step_over(thread_id)
return {}

def scopes_request(self, request: Request):
Expand All @@ -316,18 +325,18 @@ def evaluate_request(self, request: Request):
return {"result": var.repr, "variablesReference": var.id}

def disconnect_request(self, request: Request):
tracing.Breakpoint.clear()
tracing.abandon_step()
tracing.resume()
Breakpoint.clear()
self._tracer.abandon_step()
self._tracer.resume()
return {}

def terminate_request(self, request: Request):
tracing.Breakpoint.clear()
tracing.abandon_step()
tracing.resume()
Breakpoint.clear()
self._tracer.abandon_step()
self._tracer.resume()
return {}

def disconnect(self):
tracing.resume()
self._tracer.resume()
self.connected_event.clear()
return {}
19 changes: 9 additions & 10 deletions src/debugpy/server/eval.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,28 @@
# Licensed under the MIT License. See LICENSE in the project root
# for license information.

import debugpy
import threading

from collections.abc import Iterable
from debugpy.server.inspect import inspect
from types import FrameType
from typing import ClassVar, Dict, Literal, Self

from debugpy.server import tracing
from debugpy.server.inspect import inspect

ScopeKind = Literal["global", "nonlocal", "local"]
type ScopeKind = Literal["global", "nonlocal", "local"]
type StackFrame = "debugpy.server.tracing.StackFrame"


_lock = threading.RLock()


class VariableContainer:
frame: "tracing.StackFrame"
frame: StackFrame
id: int

_last_id: ClassVar[int] = 0
_all: ClassVar[Dict[int, "VariableContainer"]] = {}

def __init__(self, frame: "tracing.StackFrame"):
def __init__(self, frame: StackFrame):
self.frame = frame
with _lock:
VariableContainer._last_id += 1
Expand All @@ -46,7 +45,7 @@ def variables(self) -> Iterable["Variable"]:
raise NotImplementedError

@classmethod
def invalidate(self, *frames: Iterable["tracing.StackFrame"]) -> None:
def invalidate(self, *frames: Iterable[StackFrame]) -> None:
with _lock:
ids = [
id
Expand All @@ -61,7 +60,7 @@ class Scope(VariableContainer):
frame: FrameType
kind: ScopeKind

def __init__(self, frame: "tracing.StackFrame", kind: ScopeKind):
def __init__(self, frame: StackFrame, kind: ScopeKind):
super().__init__(frame)
self.kind = kind

Expand Down Expand Up @@ -92,7 +91,7 @@ class Variable(VariableContainer):
value: object
# TODO: evaluateName, memoryReference, presentationHint

def __init__(self, frame: "tracing.StackFrame", name: str, value: object):
def __init__(self, frame: StackFrame, name: str, value: object):
super().__init__(frame)
self.name = name
self.value = value
Expand Down
Loading
Loading