Skip to content

Commit a0be399

Browse files
committed
Plugged debugger
1 parent 9bce1fa commit a0be399

File tree

4 files changed

+115
-23
lines changed

4 files changed

+115
-23
lines changed

ipykernel/debugger.py

Lines changed: 51 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
import os
33

44
from zmq.utils import jsonapi
5-
from traitlets import Instance
5+
#from traitlets import Instance
6+
#from traitlets.config.configurable import SingletonConfigurable
67

7-
from asyncio import Queue
8+
from asyncio import (Event, Queue)
89

910
class DebugpyMessageQueue:
1011

@@ -26,7 +27,6 @@ def _reset_tcp_pos(self):
2627
self.message_pos = -1
2728

2829
def _put_message(self, raw_msg):
29-
# TODO: forward to iopub if this is an event message
3030
msg = jsonapi.loads(raw_msg)
3131
if mes['type'] == 'event':
3232
self.event_callback(msg)
@@ -72,23 +72,23 @@ async def get_message(self):
7272

7373
class DebugpyClient:
7474

75-
def __init__(self, debugpy_socket, debugpy_stream):
76-
self.debugpy_socket = debugpy_socket
75+
def __init__(self, debugpy_stream, event_callback):
7776
self.debugpy_stream = debugpy_stream
77+
self.event_callback = event_callback
7878
self.message_queue = DebugpyMessageQueue(self._forward_event)
7979
self.wait_for_attach = True
80-
self.init_event = asyncio.Event()
80+
self.init_event = Event()
8181

8282
def _forward_event(self, msg):
8383
if msg['event'] == 'initialized':
8484
self.init_event.set()
85-
#TODO: send event to iopub
85+
self.event_callback(msg)
8686

8787
def _send_request(self, msg):
8888
content = jsonapi.dumps(msg)
8989
content_length = len(content)
9090
buf = DebugpyMessageQueue.HEADER + content_length + DebugpyMessageQueue.SEPARATOR + content_msg
91-
self.debugpy_socket.send(buf) # TODO: pass routing_id
91+
self.debugpy_stream.send(buf) # TODO: pass routing_id
9292

9393
async def _wait_for_reponse(self):
9494
# Since events are never pushed to the message_queue
@@ -115,6 +115,9 @@ async def _handle_init_sequence(self):
115115
attach_rep = await self._wait_for_response()
116116
return attach_rep
117117

118+
def receive_dap_frame(self, frame):
119+
self.message_queue.put_tcp_frame(frame)
120+
118121
async def send_dap_request(self, msg):
119122
self._send_request(msg)
120123
if self.wait_for_attach and msg['command'] == 'attach':
@@ -140,19 +143,20 @@ class Debugger:
140143
'debugInfo', 'inspectVariables'
141144
]
142145

143-
log = Instance(logging.Logger, allow_none=True)
146+
#log = Instance(logging.Logger, allow_none=True)
144147

145-
def __init__(self):
148+
def __init__(self, debugpy_stream, event_callback, shell_socket, session):
149+
self.debugpy_client = DebugpyClient(debugpy_stream, event_callback)
150+
self.shell_socket = shell_socket
151+
self.session = session
146152
self.is_started = False
147-
148-
self.header = ''
149153

150154
self.started_debug_handlers = {}
151-
for msg_type in started_debug_msg_types:
155+
for msg_type in Debugger.started_debug_msg_types:
152156
self.started_debug_handlers[msg_type] = getattr(self, msg_type)
153157

154158
self.static_debug_handlers = {}
155-
for msg_type in static_debug_msg_types:
159+
for msg_type in Debugger.static_debug_msg_types:
156160
self.static_debug_handlers[msg_type] = getattr(self, msg_type)
157161

158162
self.breakpoint_list = {}
@@ -161,10 +165,27 @@ def __init__(self):
161165
async def _forward_message(self, msg):
162166
return await self.debugpy_client.send_dap_request(msg)
163167

168+
@property
169+
def tcp_client(self):
170+
return self.debugpy_client
171+
164172
def start(self):
173+
endpoint = self.debugpy_client.debugpy_stream.socket.getsockopt(zmq.LAST_ENDPOINT)
174+
index = endpoit.rfind(':')
175+
port = endpoint[index+1:]
176+
code = 'import debugpy;'
177+
code += 'debugpy.listen(("127.0.0.1",' + port + '))'
178+
content = {
179+
'code': code,
180+
'slient': True
181+
}
182+
self.session.send(self.shell_socket, 'execute_request', content,
183+
None, (self.shell_socket.getsockopt(zmq.ROUTING_ID)))
184+
165185
return False
166186

167187
def stop(self):
188+
# TODO
168189
pass
169190

170191
def dumpCell(self, message):
@@ -198,7 +219,7 @@ def source(self, message):
198219

199220
async def stackTrace(self, message):
200221
reply = await self._forward_message(message)
201-
reply['body']['stackFrames'] =
222+
reply['body']['stackFrames'] = \
202223
[frame for frame in reply['body']['stackFrames'] if frame['source']['path'] != '<string>']
203224
return reply
204225

@@ -215,7 +236,7 @@ async def attach(self, message):
215236
message['arguments']['logToFile'] = True
216237
return await self._forward_message(message)
217238

218-
def configurationDone(self, message):
239+
async def configurationDone(self, message):
219240
reply = {
220241
'seq': message['seq'],
221242
'type': 'response',
@@ -225,7 +246,13 @@ def configurationDone(self, message):
225246
}
226247
return reply;
227248

228-
def debugInfo(self, message):
249+
async def debugInfo(self, message):
250+
breakpoint_list = []
251+
for key, value in self.breakpoint_list.items():
252+
breakpoint_list.append({
253+
'source': key,
254+
'breakpoints': value
255+
})
229256
reply = {
230257
'type': 'response',
231258
'request_seq': message['seq'],
@@ -235,18 +262,21 @@ def debugInfo(self, message):
235262
'isStarted': self.is_started,
236263
'hashMethod': 'Murmur2',
237264
'hashSeed': 0,
238-
'tmpFilePrefix': 'coincoin',
265+
'tmpFilePrefix': '/tmp/ipykernel_debugger',
239266
'tmpFileSuffix': '.py',
240-
'breakpoints': self.breakpoint_list,
267+
'breakpoints': breakpoint_list,
241268
'stoppedThreads': self.stopped_threads
242269
}
243270
}
271+
#self.log.info("returning reply %s", reply)
272+
print("DEBUGGER: ", reply)
244273
return reply
245274

246275
def inspectVariables(self, message):
276+
# TODO
247277
return {}
248278

249-
async def process_request(self, header, message):
279+
async def process_request(self, message):
250280
reply = {}
251281

252282
if message['command'] == 'initialize':
@@ -269,11 +299,10 @@ async def process_request(self, header, message):
269299
if handler is not None:
270300
reply = await handler(message)
271301
elif self.is_started:
272-
self.header = header
273302
handler = self.started_debug_handlers.get(message['command'], None)
274303
if handler is not None:
275304
reply = await handler(message)
276-
else
305+
else:
277306
reply = await self._forward_message(message)
278307

279308
if message['command'] == 'disconnect':

ipykernel/kernelapp.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ class IPKernelApp(BaseIPythonApplication, InteractiveShellApp,
122122
context = Any()
123123
shell_socket = Any()
124124
control_socket = Any()
125+
debugpy_socket = Any()
126+
debug_shell_socket = Any()
125127
stdin_socket = Any()
126128
iopub_socket = Any()
127129
iopub_thread = Any()
@@ -294,6 +296,16 @@ def init_control(self, context):
294296
self.control_port = self._bind_socket(self.control_socket, self.control_port)
295297
self.log.debug("control ROUTER Channel on port: %i" % self.control_port)
296298

299+
self.debugpy_socket = context.socket(zmq.STREAM)
300+
self.debugpy_socket.linger = 1000
301+
self.debugpy_port = 0
302+
self.debugpy_port = self._bind_socket(self.debugpy_socket, self.debugpy_port)
303+
self.log.debug("debugpy STREAM Channel on port: %i" % self.debugpy_port)
304+
305+
self.debug_shell_socket = context.socket(zmq.DEALER)
306+
self.debug_shell_socket.linger = 1000
307+
self.debug_shell_socket.connect(self.shell_socket.getsockopt(zmq.LAST_ENDPOINT))
308+
297309
if hasattr(zmq, 'ROUTER_HANDOVER'):
298310
# set router-handover to workaround zeromq reconnect problems
299311
# in certain rare circumstances
@@ -335,6 +347,9 @@ def close(self):
335347
self.log.debug("Closing iopub channel")
336348
self.iopub_thread.stop()
337349
self.iopub_thread.close()
350+
351+
self.debug_shell_socket.close()
352+
338353
for channel in ('shell', 'control', 'stdin'):
339354
self.log.debug("Closing %s channel", channel)
340355
socket = getattr(self, channel + "_socket", None)
@@ -449,11 +464,14 @@ def init_kernel(self):
449464
"""Create the Kernel object itself"""
450465
shell_stream = ZMQStream(self.shell_socket)
451466
control_stream = ZMQStream(self.control_socket, self.control_thread.io_loop)
467+
debugpy_stream = ZMQStream(self.debugpy_socket, self.control_thread.io_loop)
452468
self.control_thread.start()
453469
kernel_factory = self.kernel_class.instance
454470

455471
kernel = kernel_factory(parent=self, session=self.session,
456472
control_stream=control_stream,
473+
debugpy_stream=debugpy_stream,
474+
debug_shell_socket=self.debug_shell_socket,
457475
shell_stream=shell_stream,
458476
iopub_thread=self.iopub_thread,
459477
iopub_socket=self.iopub_socket,

ipykernel/kernelbase.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import time
1313
import uuid
1414
import warnings
15+
import asyncio
1516

1617
try:
1718
# jupyter_client >= 5, use tz-aware now
@@ -39,6 +40,7 @@
3940

4041
from ._version import kernel_protocol_version
4142

43+
from .debugger import Debugger
4244

4345
class Kernel(SingletonConfigurable):
4446

@@ -69,6 +71,10 @@ def shell_streams(self):
6971
return [shell_stream]
7072

7173
control_stream = Instance(ZMQStream, allow_none=True)
74+
debugpy_stream = Instance(ZMQStream, allow_none=True)
75+
76+
debug_shell_socket = Any()
77+
7278
iopub_socket = Any()
7379
iopub_thread = Any()
7480
stdin_socket = Any()
@@ -160,7 +166,7 @@ def _default_ident(self):
160166
'apply_request',
161167
]
162168
# add deprecated ipyparallel control messages
163-
control_msg_types = msg_types + ['clear_request', 'abort_request']
169+
control_msg_types = msg_types + ['clear_request', 'abort_request', 'debug_request']
164170

165171
def __init__(self, **kwargs):
166172
super(Kernel, self).__init__(**kwargs)
@@ -173,6 +179,16 @@ def __init__(self, **kwargs):
173179
for msg_type in self.control_msg_types:
174180
self.control_handlers[msg_type] = getattr(self, msg_type)
175181

182+
self.debugger = Debugger(self.debugpy_stream,
183+
self._publish_debug_event,
184+
self.debug_shell_socket,
185+
self.session)
186+
187+
@gen.coroutine
188+
def dispatch_debugpy(self, msg):
189+
self.log.debug("Debugpy received: %s", msg)
190+
selg.debugger.tcp_client.receive_dap_frame(msg)
191+
176192
@gen.coroutine
177193
def dispatch_control(self, msg):
178194
"""dispatch control requests"""
@@ -366,7 +382,12 @@ def dispatch_queue(self):
366382
Ensures that only one message is processing at a time,
367383
even when the handler is async
368384
"""
385+
369386
while True:
387+
# ensure control stream is flushed before processing shell messages
388+
if self.control_stream:
389+
self.control_stream.flush()
390+
# receive the next message and handle it
370391
try:
371392
yield self.process_one()
372393
except Exception:
@@ -401,6 +422,7 @@ def start(self):
401422
self.io_loop.add_callback(self.dispatch_queue)
402423

403424
self.control_stream.on_recv(self.dispatch_control, copy=False)
425+
self.debugpy_stream.on_recv(self.dispatch_debugpy, copy=False)
404426

405427
self.shell_stream.on_recv(
406428
partial(
@@ -442,6 +464,13 @@ def _publish_status(self, status, channel, parent=None):
442464
parent=parent or self._parent_header[channel],
443465
ident=self._topic('status'),
444466
)
467+
def _publish_debug_event(self, event):
468+
self.session.send(self.iopub_socket,
469+
'debug_event',
470+
event,
471+
parent=self._parent_header['control'],
472+
ident=self._topic('debug_event')
473+
)
445474

446475
def set_parent(self, ident, parent, channel='shell'):
447476
"""Set the current parent_header
@@ -519,6 +548,7 @@ def execute_request(self, stream, ident, parent):
519548
)
520549
)
521550

551+
self.log.debug("EXECUTE_REPLY: %s", reply_content)
522552
# Flush output before sending the reply.
523553
sys.stdout.flush()
524554
sys.stderr.flush()
@@ -693,6 +723,20 @@ def do_is_complete(self, code):
693723
return {'status' : 'unknown',
694724
}
695725

726+
@gen.coroutine
727+
def debug_request(self, stream, ident, parent):
728+
content = parent['content']
729+
730+
reply_content = yield gen.maybe_future(self.do_debug_request(content))
731+
reply_content = json_clean(reply_content)
732+
reply_msg = self.session.send(stream, 'debug_reply', reply_content,
733+
parent, ident)
734+
self.log.debug("%s", reply_msg)
735+
736+
@gen.coroutine
737+
def do_debug_request(self, msg):
738+
return (yield self.debugger.process_request(msg))
739+
696740
#---------------------------------------------------------------------------
697741
# Engine methods (DEPRECATED)
698742
#---------------------------------------------------------------------------

ipykernel/kernelspec.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ def get_kernel_dict(extra_arguments=None):
5555
'argv': make_ipkernel_cmd(extra_arguments=extra_arguments),
5656
'display_name': 'Python %i' % sys.version_info[0],
5757
'language': 'python',
58+
'metadata': { 'debugger': True}
5859
}
5960

6061

0 commit comments

Comments
 (0)