Skip to content

Commit d7fe611

Browse files
committed
fix(debugger): rework async code in debugger and some other little quitrks in debugger, like hiding the debugger thread when debuggin python code
closes #242
1 parent 9a00d0c commit d7fe611

File tree

8 files changed

+164
-145
lines changed

8 files changed

+164
-145
lines changed

packages/core/src/robotcode/core/concurrent.py

+21-1
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,9 @@ def _remove_future_from_running_tasks(future: Task[Any]) -> None:
217217
_P = ParamSpec("_P")
218218

219219

220-
def run_as_task(callable: Callable[_P, _TResult], *args: _P.args, **kwargs: _P.kwargs) -> Task[_TResult]:
220+
def _create_task_in_thread(
221+
callable: Callable[_P, _TResult], *args: _P.args, **kwargs: _P.kwargs
222+
) -> Tuple[Task[_TResult], threading.Thread]:
221223
future: Task[_TResult] = Task()
222224
with _running_tasks_lock:
223225
thread = threading.Thread(
@@ -227,8 +229,26 @@ def run_as_task(callable: Callable[_P, _TResult], *args: _P.args, **kwargs: _P.k
227229
)
228230
_running_tasks[future] = thread
229231
future.add_done_callback(_remove_future_from_running_tasks)
232+
230233
# TODO: don't set daemon=True because it can be deprecated in future pyhton versions
231234
thread.daemon = True
235+
return future, thread
236+
237+
238+
def run_as_task(callable: Callable[_P, _TResult], *args: _P.args, **kwargs: _P.kwargs) -> Task[_TResult]:
239+
future, thread = _create_task_in_thread(callable, *args, **kwargs)
240+
241+
thread.start()
242+
243+
return future
244+
245+
246+
def run_as_debugpy_hidden_task(callable: Callable[_P, _TResult], *args: _P.args, **kwargs: _P.kwargs) -> Task[_TResult]:
247+
future, thread = _create_task_in_thread(callable, *args, **kwargs)
248+
249+
thread.pydev_do_not_trace = True # type: ignore[attr-defined]
250+
thread.is_pydev_daemon_thread = True # type: ignore[attr-defined]
251+
232252
thread.start()
233253

234254
return future

packages/core/src/robotcode/core/utils/debugpy.py

+16-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import threading
12
from typing import Optional, Sequence, Tuple, Union
23

4+
from robotcode.core.concurrent import run_as_debugpy_hidden_task
5+
36
from .logging import LoggingDescriptor
47
from .net import find_free_port
58

@@ -15,14 +18,25 @@ def is_debugpy_installed() -> bool:
1518
return True
1619

1720

18-
def wait_for_debugpy_connected() -> bool:
21+
def wait_for_debugpy_connected(timeout: float = 30) -> bool:
1922
if is_debugpy_installed():
2023
import debugpy # noqa: T100
2124

25+
connected = threading.Event()
2226
_logger.info("wait for debugpy client")
27+
28+
def _wait_for_client() -> bool:
29+
if not connected.wait(timeout=timeout):
30+
debugpy.wait_for_client.cancel()
31+
return False
32+
33+
return True
34+
35+
wait_task = run_as_debugpy_hidden_task(_wait_for_client)
2336
debugpy.wait_for_client() # noqa: T100
37+
connected.set()
38+
return wait_task.result()
2439

25-
return True
2640
return False
2741

2842

packages/debugger/src/robotcode/debugger/cli.py

+25-27
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import asyncio
21
from typing import Optional, Sequence, Tuple
32

43
import click
@@ -22,7 +21,8 @@
2221
add_help_option=True,
2322
)
2423
@click.option(
25-
"--debug / --no-debug",
24+
"--debug/--no-debug",
25+
is_flag=True,
2626
default=True,
2727
help="Enable/disable debug mode",
2828
show_default=True,
@@ -34,7 +34,7 @@
3434
show_default=True,
3535
)
3636
@click.option(
37-
"--wait-for-client / --no-wait-for-client",
37+
"--wait-for-client/--no-wait-for-client",
3838
is_flag=True,
3939
default=True,
4040
help="Waits until a debug client is connected.",
@@ -55,14 +55,14 @@
5555
show_default=True,
5656
)
5757
@click.option(
58-
"--debugpy / --no-debugpy",
58+
"--debugpy/--no-debugpy",
5959
is_flag=True,
6060
default=False,
6161
help="Enable/disable python debugging.",
6262
show_default=True,
6363
)
6464
@click.option(
65-
"--debugpy-wait-for-client",
65+
"--debugpy-wait-for-client/--no-debugpy-wait-for-client",
6666
is_flag=True,
6767
default=True,
6868
help="Waits for a debugpy client to connect.",
@@ -170,28 +170,26 @@ def debug(
170170

171171
try:
172172
app.exit(
173-
asyncio.run(
174-
run_debugger(
175-
ctx=ctx,
176-
app=app,
177-
args=list(robot_options_and_args),
178-
mode=mode,
179-
addresses=bind,
180-
port=port if port is not None else DEBUGGER_DEFAULT_PORT,
181-
pipe_name=pipe_name,
182-
debug=debug,
183-
stop_on_entry=stop_on_entry,
184-
wait_for_client=wait_for_client,
185-
wait_for_client_timeout=wait_for_client_timeout,
186-
configuration_done_timeout=configuration_done_timeout,
187-
debugpy=debugpy,
188-
debugpy_wait_for_client=debugpy_wait_for_client,
189-
debugpy_port=debugpy_port,
190-
output_messages=output_messages,
191-
output_log=output_log,
192-
output_timestamps=output_timestamps,
193-
group_output=group_output,
194-
)
173+
run_debugger(
174+
ctx=ctx,
175+
app=app,
176+
args=list(robot_options_and_args),
177+
mode=mode,
178+
addresses=bind,
179+
port=port if port is not None else DEBUGGER_DEFAULT_PORT,
180+
pipe_name=pipe_name,
181+
debug=debug,
182+
stop_on_entry=stop_on_entry,
183+
wait_for_client=wait_for_client,
184+
wait_for_client_timeout=wait_for_client_timeout,
185+
configuration_done_timeout=configuration_done_timeout,
186+
debugpy=debugpy,
187+
debugpy_wait_for_client=debugpy_wait_for_client,
188+
debugpy_port=debugpy_port,
189+
output_messages=output_messages,
190+
output_log=output_log,
191+
output_timestamps=output_timestamps,
192+
group_output=group_output,
195193
)
196194
)
197195

packages/debugger/src/robotcode/debugger/protocol.py

+3-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
from __future__ import annotations
2-
31
import asyncio
42
import inspect
53
import json
@@ -142,7 +140,7 @@ def _handle_body(self, body: bytes, charset: str) -> None:
142140
)
143141

144142
def _handle_messages(self, iterator: Iterator[ProtocolMessage]) -> None:
145-
def done(f: asyncio.Future[Any]) -> None:
143+
def done(f: "asyncio.Future[Any]") -> None:
146144
ex = f.exception()
147145
if ex is not None and not isinstance(ex, asyncio.CancelledError):
148146
self._logger.exception(ex, exc_info=ex)
@@ -249,15 +247,14 @@ def handle_request(self, message: Request) -> None:
249247

250248
self._received_request[message.seq] = result
251249

252-
def done(t: asyncio.Task[Any]) -> None:
250+
def done(t: "asyncio.Task[Any]") -> None:
253251
try:
254252
self.send_response(message.seq, message.command, t.result())
255253
except asyncio.CancelledError:
256254
self._logger.debug(lambda: f"request message {message!r} canceled")
257255
except (SystemExit, KeyboardInterrupt):
258256
raise
259257
except DebugAdapterRPCErrorException as ex:
260-
self._logger.exception(ex)
261258
self.send_error(
262259
message=ex.message,
263260
request_seq=message.seq,
@@ -321,7 +318,7 @@ def send_request(self, request: Request, return_type: Optional[Type[TResult]] =
321318
@_logger.call
322319
def send_request_async(
323320
self, request: Request, return_type: Optional[Type[TResult]] = None
324-
) -> asyncio.Future[TResult]:
321+
) -> "asyncio.Future[TResult]":
325322
return asyncio.wrap_future(self.send_request(request, return_type))
326323

327324
@_logger.call

0 commit comments

Comments
 (0)