blob: 1bde97466af58ee0ed865e775852ff49a6af4756 [file]
# Copyright 2026 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from dataclasses import dataclass, field
from zxdb_dap.models import AsyncTaskNode
@dataclass
class Thread:
"""Represents a thread in a target process."""
id: int
name: str = field(default="", compare=False)
is_stopped: bool = field(default=False, compare=False)
process: "Process | None" = field(default=None, repr=False, compare=False)
def resume(self) -> None:
"""Marks this thread as not stopped, and ensures that any cached state on this thread and
its process is cleared."""
self._resume_internal(clear_process_cache=True)
def _resume_internal(self, clear_process_cache: bool) -> None:
"""Marks the thread as resumed (is_stopped = False)."""
self.is_stopped = False
if clear_process_cache and self.process is not None:
self.process.clear_cache()
@dataclass
class Process:
"""Represents a debugged target process."""
id: int
name: str = field(default="", compare=False)
threads: dict[int, Thread] = field(
default_factory=dict, repr=False, compare=False
)
# This is optional to differentiate between cases where there is a stopped process (all threads
# are either suspended or blocked on some exception) and there is no async executor present in
# any of the threads, and where any thread is running and the async backtrace cannot be
# collected due to that fact.
#
# In the former situation, zxdb will still send us events to populate this with an empty task
# list, indicating that there are no asynchronous tasks for this process. In the latter, it
# means the process is not in the correct state to have produced any events yet and therefore
# we cannot know yet whether or not there is an async executor present to produce tasks.
async_backtrace: list[AsyncTaskNode] | None = field(
default=None, repr=False, compare=False
)
def resume(self) -> None:
"""Clears our local cache and marks all threads in the process as resumed."""
self.clear_cache()
for t in self.threads.values():
t._resume_internal(clear_process_cache=False)
def clear_cache(self) -> None:
"""Cleans up cached process state that we've received from the debug adapter while all
threads in this process were stopped. This is cleared any time a thread resumes from a
stopped state."""
self.async_backtrace = None
@property
def all_threads_stopped(self) -> bool:
return bool(self.threads) and all(
t.is_stopped for t in self.threads.values()
)