Skip to content

Commit 3ab223e

Browse files
committed
optimize disconnect from python/robotcode debugger
1 parent 53e12dd commit 3ab223e

File tree

8 files changed

+852
-779
lines changed

8 files changed

+852
-779
lines changed

CHANGELOG.md

Lines changed: 760 additions & 755 deletions
Large diffs are not rendered by default.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -902,7 +902,8 @@
902902
"presentation": {
903903
"hidden": true
904904
},
905-
"pythonConfiguration": "RobotCode: Debug"
905+
"attachPython": false,
906+
"pythonConfiguration": "RobotCode: Python"
906907
},
907908
{
908909
"name": "RobotCode: Python",

robotcode/debugger/__main__.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ async def _debug_adapter_server_(host: str, port: int) -> None:
8181
async def start_debugpy_async(
8282
server: "DebugAdapterServer",
8383
debugpy_port: int = 5678,
84+
addresses: Union[Sequence[str], str, None] = None,
8485
wait_for_debugpy_client: bool = False,
8586
wait_for_client_timeout: float = DEFAULT_TIMEOUT,
8687
) -> None:
@@ -93,12 +94,14 @@ async def start_debugpy_async(
9394
if port != debugpy_port:
9495
_logger.warning(f"start debugpy session on port {port}")
9596

96-
if enable_debugpy(port) and await run_coroutine_from_thread_async(
97+
if enable_debugpy(port, addresses) and await run_coroutine_from_thread_async(
9798
server.protocol.wait_for_client, wait_for_client_timeout, loop=server.loop
9899
):
99100
await asyncio.wrap_future(
100101
asyncio.run_coroutine_threadsafe(
101-
server.protocol.send_event_async(Event(event="debugpyStarted", body={"port": port})),
102+
server.protocol.send_event_async(
103+
Event(event="debugpyStarted", body={"port": port, "addresses": addresses})
104+
),
102105
loop=server.loop,
103106
)
104107
)
@@ -110,7 +113,7 @@ async def start_debugpy_async(
110113
async def run_robot(
111114
port: int,
112115
args: List[str],
113-
bind: Union[Sequence[str], str, None] = None,
116+
addresses: Union[Sequence[str], str, None] = None,
114117
no_debug: bool = False,
115118
wait_for_client: bool = False,
116119
wait_for_client_timeout: float = DEFAULT_TIMEOUT,
@@ -129,10 +132,14 @@ async def run_robot(
129132
run_coroutine_from_thread_async,
130133
run_coroutine_in_thread,
131134
)
135+
from ..utils.debugpy import is_debugpy_installed
132136
from .dap_types import Event
133137
from .debugger import Debugger
134138

135-
server_future = run_coroutine_in_thread(_debug_adapter_server_, bind, port)
139+
if debugpy and not is_debugpy_installed():
140+
print("debugpy not installed.")
141+
142+
server_future = run_coroutine_in_thread(_debug_adapter_server_, addresses, port)
136143

137144
server = await wait_for_server()
138145

@@ -147,7 +154,7 @@ async def run_robot(
147154
except asyncio.TimeoutError as e:
148155
raise ConnectionError("No incomming connection from a debugger client.") from e
149156

150-
await run_coroutine_from_thread_async(server.protocol.initialized, loop=server.loop)
157+
await run_coroutine_from_thread_async(server.protocol.wait_for_initialized, loop=server.loop)
151158

152159
if wait_for_client:
153160
try:
@@ -160,7 +167,7 @@ async def run_robot(
160167
raise ConnectionError("Timeout to get configuration from client.") from e
161168

162169
if debugpy:
163-
await start_debugpy_async(server, debugpy_port, wait_for_debugpy_client, wait_for_client_timeout)
170+
await start_debugpy_async(server, debugpy_port, addresses, wait_for_debugpy_client, wait_for_client_timeout)
164171

165172
args = [
166173
"--listener",

robotcode/debugger/debugger.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,11 @@ def stop(self) -> None:
303303

304304
self.condition.notify_all()
305305

306+
@_logger.call
307+
def continue_all(self) -> None:
308+
if self.main_thread is not None and self.main_thread.ident is not None:
309+
self.continue_thread(self.main_thread.ident)
310+
306311
@_logger.call
307312
def continue_thread(self, thread_id: int) -> None:
308313
if self.main_thread is None or thread_id != self.main_thread.ident:

robotcode/debugger/launcher/server.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
DisconnectRequest,
2020
Event,
2121
ExceptionBreakpointsFilter,
22+
InitializeRequest,
2223
InitializeRequestArguments,
2324
LaunchRequestArguments,
2425
OutputCategory,
@@ -59,6 +60,7 @@ def __init__(self) -> None:
5960
super().__init__()
6061
self._client: Optional[DAPClient] = None
6162
self._process: Optional[asyncio.subprocess.Process] = None
63+
self._initialize_arguments: Optional[InitializeRequestArguments] = None
6264

6365
@property
6466
def client(self) -> DAPClient:
@@ -77,6 +79,8 @@ def connected(self) -> bool:
7779

7880
@rpc_method(name="initialize", param_type=InitializeRequestArguments)
7981
async def _initialize(self, arguments: InitializeRequestArguments, *args: Any, **kwargs: Any) -> Capabilities:
82+
self._initialize_arguments = arguments
83+
8084
self._initialized = True
8185

8286
return Capabilities(
@@ -264,7 +268,10 @@ async def _launch(
264268
try:
265269
await self.client.connect(connect_timeout)
266270
except asyncio.TimeoutError:
267-
raise asyncio.TimeoutError("Can't connect to debug launcher.")
271+
raise asyncio.TimeoutError("Can't connect to debug child.")
272+
273+
if self._initialize_arguments is not None:
274+
await self.client.protocol.send_request_async(InitializeRequest(arguments=self._initialize_arguments))
268275

269276
@rpc_method(name="configurationDone", param_type=ConfigurationDoneArguments)
270277
async def _configuration_done(
@@ -279,16 +286,16 @@ async def _disconnect(self, arguments: Optional[DisconnectArguments] = None, *ar
279286
if not self.client.protocol.terminated:
280287
await self.client.protocol.send_request_async(DisconnectRequest(arguments=arguments))
281288
else:
289+
self.send_event(Event("disconnectRequested"))
282290
await self.send_event_async(TerminatedEvent())
283291

284292
@_logger.call
285293
@rpc_method(name="terminate", param_type=TerminateArguments)
286294
async def _terminate(self, arguments: Optional[TerminateArguments] = None, *args: Any, **kwargs: Any) -> None:
287295
if self.client.connected:
288-
self.send_event(Event("terminateRequested"))
289-
290296
return await self.client.protocol.send_request_async(TerminateRequest(arguments=arguments))
291297
else:
298+
self.send_event(Event("terminateRequested"))
292299
await self.send_event_async(TerminatedEvent())
293300

294301
async def handle_unknown_command(self, message: Request) -> Any:

robotcode/debugger/server.py

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,14 @@ class DebugAdapterServerProtocol(DebugAdapterProtocol):
5656
def __init__(self) -> None:
5757
super().__init__()
5858

59-
self._initialized = False
6059
self._connected_event = async_tools.Event()
6160
self._disconnected_event = async_tools.Event()
6261
self._connected = False
6362
self._sigint_signaled = False
6463

64+
self._initialized = False
65+
self._initialized_event = async_tools.Event()
66+
6567
self._exited_lock = async_tools.Lock()
6668
self._exited = False
6769

@@ -115,6 +117,12 @@ async def wait_for_client(self, timeout: float = 5) -> bool:
115117

116118
return self._connected
117119

120+
@_logger.call
121+
async def wait_for_initialized(self, timeout: float = 60) -> bool:
122+
await asyncio.wait_for(self._initialized_event.wait(), timeout)
123+
124+
return self._initialized
125+
118126
@_logger.call
119127
async def wait_for_disconnected(self, timeout: float = 60) -> bool:
120128
await asyncio.wait_for(self._disconnected_event.wait(), timeout)
@@ -125,6 +133,9 @@ async def wait_for_disconnected(self, timeout: float = 60) -> bool:
125133
async def _initialize(self, arguments: InitializeRequestArguments, *args: Any, **kwargs: Any) -> Capabilities:
126134
self._initialized = True
127135

136+
if self.loop is not None:
137+
self.loop.call_soon(self.initialized)
138+
128139
return Capabilities(
129140
supports_configuration_done_request=True,
130141
supports_conditional_breakpoints=True,
@@ -187,8 +198,9 @@ async def _attach(
187198
Debugger.instance().attached = True
188199

189200
@_logger.call
190-
async def initialized(self) -> None:
191-
await self.send_event_async(InitializedEvent())
201+
def initialized(self) -> None:
202+
self.send_event(InitializedEvent())
203+
self._initialized_event.set()
192204

193205
@_logger.call
194206
async def exit(self, exit_code: int) -> None:
@@ -213,17 +225,25 @@ async def _terminate(self, arguments: Optional[TerminateArguments] = None, *args
213225
signal.raise_signal(signal.SIGINT)
214226
self._sigint_signaled = True
215227
else:
228+
self.send_event(Event("terminateRequested"))
229+
216230
self._logger.info("Send SIGTERM to process")
217231
signal.raise_signal(signal.SIGTERM)
218232

219233
@rpc_method(name="disconnect", param_type=DisconnectArguments)
220234
async def _disconnect(self, arguments: Optional[DisconnectArguments] = None, *args: Any, **kwargs: Any) -> None:
235+
221236
if (
222-
not (await self.exited)
223-
or not (await self.terminated)
224-
and (arguments is None or arguments.terminate_debuggee is None or arguments.terminate_debuggee)
237+
(not (await self.exited) or not (await self.terminated))
238+
and arguments is not None
239+
and arguments.terminate_debuggee
225240
):
226241
os._exit(-1)
242+
else:
243+
await self.send_event_async(Event("disconnectRequested"))
244+
await asyncio.sleep(3)
245+
Debugger.instance().attached = False
246+
Debugger.instance().continue_all()
227247

228248
@rpc_method(name="setBreakpoints", param_type=SetBreakpointsArguments)
229249
async def _set_breakpoints(

robotcode/utils/debugpy.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from typing import Sequence, Tuple, Union
2+
13
from .logging import LoggingDescriptor
24
from .net import find_free_port
35

@@ -24,24 +26,34 @@ def wait_for_debugpy_connected() -> bool:
2426
return False
2527

2628

27-
def enable_debugpy(port: int) -> bool:
29+
def enable_debugpy(port: int, addresses: Union[Sequence[str], str, None] = None) -> bool:
2830
if is_debugpy_installed():
2931
import debugpy
3032

31-
debugpy.listen(port)
33+
if addresses is None:
34+
addresses = ["127.0.0.1"]
35+
36+
if not isinstance(addresses, Sequence):
37+
addresses = [addresses]
38+
39+
for address in addresses:
40+
debugpy.listen((address, port))
3241

3342
return True
3443
return False
3544

3645

37-
def start_debugpy(port: int, wait_for_client: bool) -> bool:
46+
def start_debugpy(end_point: Union[Tuple[str, int], int], wait_for_client: bool) -> bool:
3847
if is_debugpy_installed():
3948
import debugpy
4049

41-
real_port = find_free_port(port)
42-
if real_port != port:
50+
if isinstance(end_point, int):
51+
end_point = ("127.0.0.1", end_point)
52+
53+
real_port = find_free_port(end_point[1])
54+
if real_port != end_point[1]:
4355
_logger.warning(f"start debugpy session on port {real_port}")
44-
debugpy.listen(real_port)
56+
debugpy.listen((end_point[0], real_port))
4557

4658
if wait_for_client:
4759
_logger.info("wait for debugpy client")

vscode-client/debugmanager.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ export class DebugManager {
264264
await DebugManager.OnDebugpyStarted(event.session, event.event, event.body);
265265
break;
266266
}
267+
case "disconnectRequested":
267268
case "terminateRequested": {
268269
for (const s of this._attachedSessions) {
269270
if (s.parentSession == event.session) {
@@ -286,9 +287,19 @@ export class DebugManager {
286287
}
287288
}),
288289

289-
vscode.debug.onDidTerminateDebugSession((session) => {
290+
vscode.debug.onDidTerminateDebugSession(async (session) => {
290291
if (this._attachedSessions.has(session)) {
291292
this._attachedSessions.delete(session);
293+
if (session.parentSession?.type === "robotcode") {
294+
await vscode.debug.stopDebugging(session.parentSession);
295+
}
296+
}
297+
if (session.configuration.type === "robotcode") {
298+
for (const s of this._attachedSessions) {
299+
if (s.parentSession == session) {
300+
await vscode.debug.stopDebugging(s);
301+
}
302+
}
292303
}
293304
}),
294305

@@ -389,7 +400,7 @@ export class DebugManager {
389400
static async OnDebugpyStarted(
390401
session: vscode.DebugSession,
391402
_event: string,
392-
options?: { port: number }
403+
options?: { port: number; addresses: undefined | string[] | null }
393404
): Promise<void> {
394405
if (
395406
session.type === "robotcode" &&
@@ -416,10 +427,15 @@ export class DebugManager {
416427
request: "attach",
417428
connect: {
418429
port: options.port,
430+
host: options.addresses ? options.addresses[0] : undefined,
419431
},
420432
},
421433
};
422434

435+
if (session.configuration?.request === "attach" && !debugConfiguration.pathMappings) {
436+
debugConfiguration.pathMappings = session.configuration.pathMappings;
437+
}
438+
423439
await vscode.debug.startDebugging(session.workspaceFolder, debugConfiguration, {
424440
parentSession: session,
425441
compact: true,

0 commit comments

Comments
 (0)