1
1
"""Debugger implementation for the IPython kernel."""
2
+ from __future__ import annotations
3
+
2
4
import os
3
5
import re
4
6
import sys
5
7
import typing as t
8
+ from math import inf
9
+ from typing import Any
6
10
7
11
import zmq
12
+ from anyio import Event , create_memory_object_stream
8
13
from IPython .core .getipython import get_ipython
9
14
from IPython .core .inputtransformer2 import leading_empty_lines
10
- from tornado .locks import Event
11
- from tornado .queues import Queue
12
15
from zmq .utils import jsonapi
13
16
14
17
try :
@@ -116,7 +119,9 @@ def __init__(self, event_callback, log):
116
119
self .tcp_buffer = ""
117
120
self ._reset_tcp_pos ()
118
121
self .event_callback = event_callback
119
- self .message_queue : Queue [t .Any ] = Queue ()
122
+ self .message_send_stream , self .message_receive_stream = create_memory_object_stream [
123
+ dict [str , Any ]
124
+ ](max_buffer_size = inf )
120
125
self .log = log
121
126
122
127
def _reset_tcp_pos (self ):
@@ -135,7 +140,7 @@ def _put_message(self, raw_msg):
135
140
else :
136
141
self .log .debug ("QUEUE - put message:" )
137
142
self .log .debug (msg )
138
- self .message_queue . put_nowait (msg )
143
+ self .message_send_stream . send_nowait (msg )
139
144
140
145
def put_tcp_frame (self , frame ):
141
146
"""Put a tcp frame in the queue."""
@@ -186,25 +191,31 @@ def put_tcp_frame(self, frame):
186
191
187
192
async def get_message (self ):
188
193
"""Get a message from the queue."""
189
- return await self .message_queue . get ()
194
+ return await self .message_receive_stream . receive ()
190
195
191
196
192
197
class DebugpyClient :
193
198
"""A client for debugpy."""
194
199
195
- def __init__ (self , log , debugpy_stream , event_callback ):
200
+ def __init__ (self , log , debugpy_socket , event_callback ):
196
201
"""Initialize the client."""
197
202
self .log = log
198
- self .debugpy_stream = debugpy_stream
203
+ self .debugpy_socket = debugpy_socket
199
204
self .event_callback = event_callback
200
205
self .message_queue = DebugpyMessageQueue (self ._forward_event , self .log )
201
206
self .debugpy_host = "127.0.0.1"
202
207
self .debugpy_port = - 1
203
208
self .routing_id = None
204
209
self .wait_for_attach = True
205
- self .init_event = Event ()
210
+ self ._init_event = None
206
211
self .init_event_seq = - 1
207
212
213
+ @property
214
+ def init_event (self ):
215
+ if self ._init_event is None :
216
+ self ._init_event = Event ()
217
+ return self ._init_event
218
+
208
219
def _get_endpoint (self ):
209
220
host , port = self .get_host_port ()
210
221
return "tcp://" + host + ":" + str (port )
@@ -215,9 +226,9 @@ def _forward_event(self, msg):
215
226
self .init_event_seq = msg ["seq" ]
216
227
self .event_callback (msg )
217
228
218
- def _send_request (self , msg ):
229
+ async def _send_request (self , msg ):
219
230
if self .routing_id is None :
220
- self .routing_id = self .debugpy_stream . socket .getsockopt (ROUTING_ID )
231
+ self .routing_id = self .debugpy_socket .getsockopt (ROUTING_ID )
221
232
content = jsonapi .dumps (
222
233
msg ,
223
234
default = json_default ,
@@ -232,7 +243,7 @@ def _send_request(self, msg):
232
243
self .log .debug ("DEBUGPYCLIENT:" )
233
244
self .log .debug (self .routing_id )
234
245
self .log .debug (buf )
235
- self .debugpy_stream .send_multipart ((self .routing_id , buf ))
246
+ await self .debugpy_socket .send_multipart ((self .routing_id , buf ))
236
247
237
248
async def _wait_for_response (self ):
238
249
# Since events are never pushed to the message_queue
@@ -250,7 +261,7 @@ async def _handle_init_sequence(self):
250
261
"seq" : int (self .init_event_seq ) + 1 ,
251
262
"command" : "configurationDone" ,
252
263
}
253
- self ._send_request (configurationDone )
264
+ await self ._send_request (configurationDone )
254
265
255
266
# 3] Waits for configurationDone response
256
267
await self ._wait_for_response ()
@@ -262,7 +273,7 @@ async def _handle_init_sequence(self):
262
273
def get_host_port (self ):
263
274
"""Get the host debugpy port."""
264
275
if self .debugpy_port == - 1 :
265
- socket = self .debugpy_stream . socket
276
+ socket = self .debugpy_socket
266
277
socket .bind_to_random_port ("tcp://" + self .debugpy_host )
267
278
self .endpoint = socket .getsockopt (zmq .LAST_ENDPOINT ).decode ("utf-8" )
268
279
socket .unbind (self .endpoint )
@@ -272,14 +283,13 @@ def get_host_port(self):
272
283
273
284
def connect_tcp_socket (self ):
274
285
"""Connect to the tcp socket."""
275
- self .debugpy_stream . socket .connect (self ._get_endpoint ())
276
- self .routing_id = self .debugpy_stream . socket .getsockopt (ROUTING_ID )
286
+ self .debugpy_socket .connect (self ._get_endpoint ())
287
+ self .routing_id = self .debugpy_socket .getsockopt (ROUTING_ID )
277
288
278
289
def disconnect_tcp_socket (self ):
279
290
"""Disconnect from the tcp socket."""
280
- self .debugpy_stream . socket .disconnect (self ._get_endpoint ())
291
+ self .debugpy_socket .disconnect (self ._get_endpoint ())
281
292
self .routing_id = None
282
- self .init_event = Event ()
283
293
self .init_event_seq = - 1
284
294
self .wait_for_attach = True
285
295
@@ -289,7 +299,7 @@ def receive_dap_frame(self, frame):
289
299
290
300
async def send_dap_request (self , msg ):
291
301
"""Send a dap request."""
292
- self ._send_request (msg )
302
+ await self ._send_request (msg )
293
303
if self .wait_for_attach and msg ["command" ] == "attach" :
294
304
rep = await self ._handle_init_sequence ()
295
305
self .wait_for_attach = False
@@ -325,17 +335,19 @@ class Debugger:
325
335
]
326
336
327
337
def __init__ (
328
- self , log , debugpy_stream , event_callback , shell_socket , session , just_my_code = True
338
+ self , log , debugpy_socket , event_callback , shell_socket , session , just_my_code = True
329
339
):
330
340
"""Initialize the debugger."""
331
341
self .log = log
332
- self .debugpy_client = DebugpyClient (log , debugpy_stream , self ._handle_event )
342
+ self .debugpy_client = DebugpyClient (log , debugpy_socket , self ._handle_event )
333
343
self .shell_socket = shell_socket
334
344
self .session = session
335
345
self .is_started = False
336
346
self .event_callback = event_callback
337
347
self .just_my_code = just_my_code
338
- self .stopped_queue : Queue [t .Any ] = Queue ()
348
+ self .stopped_send_stream , self .stopped_receive_stream = create_memory_object_stream [
349
+ dict [str , Any ]
350
+ ](max_buffer_size = inf )
339
351
340
352
self .started_debug_handlers = {}
341
353
for msg_type in Debugger .started_debug_msg_types :
@@ -360,7 +372,7 @@ def __init__(
360
372
def _handle_event (self , msg ):
361
373
if msg ["event" ] == "stopped" :
362
374
if msg ["body" ]["allThreadsStopped" ]:
363
- self .stopped_queue . put_nowait (msg )
375
+ self .stopped_send_stream . send_nowait (msg )
364
376
# Do not forward the event now, will be done in the handle_stopped_event
365
377
return
366
378
else :
@@ -400,7 +412,7 @@ async def handle_stopped_event(self):
400
412
"""Handle a stopped event."""
401
413
# Wait for a stopped event message in the stopped queue
402
414
# This message is used for triggering the 'threads' request
403
- event = await self .stopped_queue . get ()
415
+ event = await self .stopped_receive_stream . receive ()
404
416
req = {"seq" : event ["seq" ] + 1 , "type" : "request" , "command" : "threads" }
405
417
rep = await self ._forward_message (req )
406
418
for thread in rep ["body" ]["threads" ]:
@@ -412,7 +424,7 @@ async def handle_stopped_event(self):
412
424
def tcp_client (self ):
413
425
return self .debugpy_client
414
426
415
- def start (self ):
427
+ async def start (self ):
416
428
"""Start the debugger."""
417
429
if not self .debugpy_initialized :
418
430
tmp_dir = get_tmp_directory ()
@@ -430,7 +442,12 @@ def start(self):
430
442
(self .shell_socket .getsockopt (ROUTING_ID )),
431
443
)
432
444
433
- ident , msg = self .session .recv (self .shell_socket , mode = 0 )
445
+ msg = await self .shell_socket .recv_multipart ()
446
+ ident , msg = self .session .feed_identities (msg , copy = True )
447
+ try :
448
+ msg = self .session .deserialize (msg , content = True , copy = True )
449
+ except Exception :
450
+ self .log .error ("Invalid message" , exc_info = True )
434
451
self .debugpy_initialized = msg ["content" ]["status" ] == "ok"
435
452
436
453
# Don't remove leading empty lines when debugging so the breakpoints are correctly positioned
@@ -719,7 +736,7 @@ async def process_request(self, message):
719
736
if self .is_started :
720
737
self .log .info ("The debugger has already started" )
721
738
else :
722
- self .is_started = self .start ()
739
+ self .is_started = await self .start ()
723
740
if self .is_started :
724
741
self .log .info ("The debugger has started" )
725
742
else :
0 commit comments