================
@@ -0,0 +1,529 @@
+# FIXME: remove when LLDB_MINIMUM_PYTHON_VERSION > 3.8
+from __future__ import annotations
+
+import contextlib
+import dataclasses
+import functools
+import io
+import itertools
+import json
+import logging
+import os
+import subprocess
+import threading
+from concurrent import futures
+from concurrent.futures import Future
+from dataclasses import fields
+from pathlib import Path
+from typing import Any, Callable, Generic, Optional, Type, TypeVar
+
+from .dap_types import (
+ AnyResponse,
+ ArgsProtocol,
+ Capabilities,
+ CapabilitiesEvent,
+ ContinueArgs,
+ DAPError,
+ DisconnectArgs,
+ ErrorResponse,
+ Event,
+ ExitedEvent,
+ GotoArgs,
+ InitializeArgs,
+ InitializedEvent,
+ Message,
+ MessageType,
+ NextArgs,
+ OutputCategory,
+ OutputEvent,
+ RawMessage,
+ Request,
+ Response,
+ RestartArgs,
+ ReverseResponse,
+ RunInTerminalRequest,
+ RunInTerminalResponse,
+ StepInArgs,
+ StepOutArgs,
+ StoppedEvent,
+ TerminateArgs,
+ dict_to_message,
+)
+from .utils import (
+ DebugAdapter,
+ EventHistory,
+ MessageHandler,
+ SubProcessSpawner,
+ redirect_stream,
+)
+
+R = TypeVar("R")
+
+# Any Request that resumes execution (or terminates the session),
+# invalidates any frameId or variablesReference captured during the current
stop.
+# Sending one of these Requests advances the session's stop_generation.
+# To prevent using a frameId or variablesReference that is no longer valid once
+# the session continues.
+_RESUMING_COMMANDS = (
+ ContinueArgs,
+ NextArgs,
+ StepInArgs,
+ StepOutArgs,
+ GotoArgs,
+ RestartArgs,
+ TerminateArgs,
+)
+
+
+class PendingResponse(Generic[AnyResponse]):
+ """A Holds the future to the expected request for the sequence id."""
+
+ def __init__(
+ self,
+ seq: int,
+ response_class: Type[AnyResponse],
+ raw_future: Future[RawMessage],
+ timeout: float,
+ command: str,
+ on_resolve: Callable[[Response], None] = lambda _: None,
+ ):
+ assert issubclass(
+ response_class, Response
+ ), f"'{response_class.__name__}' must be a subclass of Response."
+ self.seq = seq
+ self.response_class: Type[AnyResponse] = response_class
+ self._future = raw_future
+ self._timeout = timeout
+ self._command = command
+ self._on_resolve = on_resolve
+
+ def result(self, msg: Optional[str] = None) -> AnyResponse:
+ response = self.result_or_error()
+
+ if isinstance(response, self.response_class):
+ return response
+ detail = f"expected '{self.response_class.__name__}' got {response}."
+ raise DAPError(f"{msg}:\n\t{detail}" if msg else detail)
+
+ def error(self, msg: Optional[str] = None) -> ErrorResponse:
+ response = self.result_or_error()
+
+ if isinstance(response, ErrorResponse):
+ return response
+ detail = f"expected 'ErrorResponse' got {response}."
+ raise DAPError(f"{msg}\n\t{detail}" if msg else detail)
+
+ def result_or_error(self) -> AnyResponse | ErrorResponse:
+ try:
+ raw = self._future.result(timeout=self._timeout)
+ except (TimeoutError, futures.TimeoutError) as e:
+ msg = f"\n\tRequest '{self._command}' (seq={self.seq}) timed out
after {self._timeout}s"
+ e.args = (f"{e.args[0]}{msg}", *e.args)
+ raise
+ except ConnectionError as e:
+ raise DAPError(
+ f"Session ended before getting response for "
+ f"'{self._command}' (seq={self.seq})"
+ ) from e
+
+ cls = self.response_class if raw["success"] else ErrorResponse
+ response = cls.from_json(raw)
+
+ self._on_resolve(response)
+ return response
+
+
+def _synchronized(method: Callable[..., R]) -> Callable[..., R]:
+ """Class method decorator to acquire and release the lock automatically."""
+
+ @functools.wraps(method)
+ def wrapper(self, *args: Any, **kwargs: Any) -> R:
+ with self._lock:
+ return method(self, *args, **kwargs)
+
+ return wrapper
+
+
+class _DAPSessionState:
+ def __init__(self):
+ self._lock = threading.RLock()
+ self._initialized = False
+ self._capabilities = Capabilities()
+ self.output_streams = {
+ OutputCategory.STDOUT: io.StringIO(),
+ OutputCategory.STDERR: io.StringIO(),
+ OutputCategory.CONSOLE: io.StringIO(),
+ OutputCategory.IMPORTANT: io.StringIO(),
+ OutputCategory.TELEMETRY: io.StringIO(),
+ }
+ self._stopped_thread_id: Optional[int] = None
+ self._last_response: Optional[Response] = None
+ self._stop_generation: int = 0
+
+ @property
+ @_synchronized
+ def is_initialized(self):
+ return self._initialized
+
+ @_synchronized
+ def set_initialized(self, val: bool):
----------------
ashgti wrote:
Should this be a property setter? e.g. `@is_initialized.setter`?
https://github.com/llvm/llvm-project/pull/203978
_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits