Skip to content
This repository was archived by the owner on Aug 2, 2023. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ptvsd/ipcjson.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ def _send(self, **payload):
try:
self.__socket.send(headers)
self.__socket.send(content)
_trace('Sent content', content)
except BrokenPipeError:
pass
except OSError as exc:
Expand Down Expand Up @@ -191,6 +192,7 @@ def _wait_for_message(self):
# read content, utf-8 encoded
content = self._buffered_read_as_utf8(length)
try:
_trace('Received content', content)
msg = json.loads(content)
self._receive_message(msg)
except ValueError:
Expand Down
130 changes: 99 additions & 31 deletions ptvsd/wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,26 @@ def __init__(self, name, description, stack, source):
self.source = source


class PydevdSocket(object):
class Observable(object):
def __init__(self):
self._observers = []

def register_observer(self, observer):
self._observers.append(observer)

def un_register_observer(self, observer):
self._observers.remove(observer)

def notify_observers(self, *args, **kwargs):
for observer in self._observers:
observer.notify(*args, **kwargs)

@property
def observer_count(self):
return len(self._observers)


class PydevdSocket(Observable):
"""A dummy socket-like object for communicating with pydevd.

It parses pydevd messages and redirects them to the provided handler
Expand All @@ -245,9 +264,10 @@ class PydevdSocket(object):

_vscprocessor = None

def __init__(self, event_handler):
def __init__(self):
super(PydevdSocket, self).__init__()
#self.log = open('pydevd.log', 'w')
self.event_handler = event_handler
self.event_handler = self.notify_observers
self.lock = threading.Lock()
self.seq = 1000000000
self.pipe_r, self.pipe_w = os.pipe()
Expand Down Expand Up @@ -621,6 +641,7 @@ def __init__(self, socket, pydevd, logfile=None,
super(VSCodeMessageProcessor, self).__init__(socket=socket,
own_socket=False,
logfile=logfile)
pydevd.register_observer(self)
self.socket = socket
self.pydevd = pydevd
self.killonclose = killonclose
Expand Down Expand Up @@ -659,16 +680,24 @@ def __init__(self, socket, pydevd, logfile=None,

# closing the adapter

def close(self):
def close(self, exit=True):
"""Stop the message processor and release its resources."""
if self._closed:
return
self._closed = True

# Stop the PyDevd message handler first.
self._stop_pydevd_message_loop()
# Treat PyDevd as effectively exited.
self._handle_pydevd_stopped()
self.pydevd.un_register_observer(self)
if exit:
# Stop the PyDevd message handler first.
self._stop_pydevd_message_loop()
# Treat PyDevd as effectively exited.
self._handle_pydevd_stopped()
else:
# Notify the editor that the debugger has stopped.
self.send_event('terminated')
# The editor will send a "disconnect" request at this point.
self._wait_for_disconnect()

# Close the editor-side socket.
self._stop_vsc_message_loop()

Expand All @@ -689,6 +718,9 @@ def _stop_vsc_message_loop(self):
except Exception:
pass

def notify(self, *args, **kwargs):
self._on_pydevd_event(*args)

def _handle_pydevd_stopped(self):
wait_on_normal_exit = self.debug_options.get(
'WAIT_ON_NORMAL_EXIT', False)
Expand Down Expand Up @@ -723,13 +755,13 @@ def _wait_for_disconnect(self, timeout=None):
self.send_response(self.disconnect_request)
self.disconnect_request = None

def _handle_disconnect(self, request):
def _handle_disconnect(self, request, exit=True):
self.disconnect_request = request
self.disconnect_request_event.set()
killProcess = not self._closed
self.close()
self.close(exit=exit)
# TODO: Move killing the process to close()?
if killProcess and self.killonclose:
if exit and killProcess and self.killonclose:
os.kill(os.getpid(), signal.SIGTERM)

# async helpers
Expand Down Expand Up @@ -792,7 +824,7 @@ def decorate(f):

pydevd_events = EventHandlers()

def on_pydevd_event(self, cmd_id, seq, args):
def _on_pydevd_event(self, cmd_id, seq, args):
# TODO: docstring
try:
f = self.pydevd_events[cmd_id]
Expand Down Expand Up @@ -937,6 +969,15 @@ def on_attach(self, request, args):
options = self.build_debug_options(args.get('debugOptions', []))
self.debug_options = self._parse_debug_options(
args.get('options', options))

if self.pydevd.observer_count > 1:
self.send_response(
request,
success=False,
message='A debugger is already attached to this process.',
)
return

self.send_response(request)

@async_handler
Expand All @@ -953,7 +994,7 @@ def on_disconnect(self, request, args):
if self.start_reason == 'launch':
self._handle_disconnect(request)
else:
self.send_response(request)
self._handle_disconnect(request, exit=False)

def send_process_event(self, start_method):
# TODO: docstring
Expand Down Expand Up @@ -1412,7 +1453,7 @@ def on_setBreakpoints(self, request, args):
for src_bp in src_bps:
line = src_bp['line']
vsc_bpid = self.bp_map.add(
lambda vsc_bpid: (path, vsc_bpid))
lambda vsc_bpid: (path, vsc_bpid))
self.path_casing.track_file_path_case(path)
msg = msgfmt.format(vsc_bpid, bp_type, path, line,
src_bp.get('condition', None))
Expand Down Expand Up @@ -1561,9 +1602,9 @@ def on_pydevd_thread_suspend(self, seq, args):
pyd_tid = xml.thread['id']
reason = int(xml.thread['stop_reason'])
STEP_REASONS = {
pydevd_comm.CMD_STEP_INTO,
pydevd_comm.CMD_STEP_OVER,
pydevd_comm.CMD_STEP_RETURN,
pydevd_comm.CMD_STEP_INTO,
pydevd_comm.CMD_STEP_OVER,
pydevd_comm.CMD_STEP_RETURN,
}
EXCEPTION_REASONS = {
pydevd_comm.CMD_STEP_CAUGHT_EXCEPTION,
Expand Down Expand Up @@ -1715,22 +1756,28 @@ def _new_sock():
return sock


def _start(client, server, killonclose=True, addhandlers=True):
name = 'ptvsd.Client' if server is None else 'ptvsd.Server'

pydevd = PydevdSocket(lambda *args: proc.on_pydevd_event(*args))
proc = VSCodeMessageProcessor(client, pydevd,
killonclose=killonclose)
def _add_pydevd_event_handler(client,
pydevd,
name,
killonclose=True,
addhandlers=True):
proc = VSCodeMessageProcessor(client, pydevd, killonclose=killonclose)

server_thread = threading.Thread(target=proc.process_messages,
name=name)
server_thread = threading.Thread(target=proc.process_messages, name=name)
server_thread.daemon = True
server_thread.start()

if addhandlers:
_add_atexit_handler(proc, server_thread)
_set_signal_handlers(proc)
_add_signal_handler(proc)


def _start(client, server, killonclose=True, addhandlers=True):
name = 'ptvsd.Client' if server is None else 'ptvsd.Server'
if addhandlers:
_set_signal_handler()
pydevd = PydevdSocket()
_add_pydevd_event_handler(client, pydevd, name, killonclose, addhandlers)
return pydevd


Expand All @@ -1742,14 +1789,25 @@ def handler():
atexit.register(handler)


def _set_signal_handlers(proc):
signal_handler = Observable()


def _set_signal_handler():
if platform.system() == 'Windows':
return None
signal.signal(signal.SIGHUP, signal_sighup_handler)


def signal_sighup_handler(signum, frame):
signal_handler.notify_observers()
sys.exit(0)

def handler(signum, frame):
proc.close()
sys.exit(0)
signal.signal(signal.SIGHUP, handler)

def _add_signal_handler(proc):
if platform.system() == 'Windows':
return None

signal_handler.register_observer({"notify": lambda: proc.close()})


########################
Expand All @@ -1766,6 +1824,16 @@ def start_server(port, addhandlers=True):
server = _create_server(port)
client, _ = server.accept()
pydevd = _start(client, server)

def _wait_for_more_connections():
while True:
client, _ = server.accept()
_add_pydevd_event_handler(client, pydevd, name='ptvsd.Server')

connection_thread = threading.Thread(target=_wait_for_more_connections,
name='ptvsd.Client.Connection')
connection_thread.daemon = True
connection_thread.start()
return pydevd


Expand Down
11 changes: 2 additions & 9 deletions tests/highlevel/test_lifecycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def test_attach(self):
# end
req_disconnect = self.send_request('disconnect')
finally:
with self._fix.wait_for_events(['exited', 'terminated']):
with self._fix.wait_for_events(['terminated']):
self.fix.close_ptvsd()
daemon.close()

Expand Down Expand Up @@ -90,15 +90,8 @@ def test_attach(self):
self.new_event('initialized'),
self.new_response(req_attach),
self.new_response(req_config),
#self.new_event('process', **dict(
# name=sys.argv[0],
# systemProcessId=os.getpid(),
# isLocalProcess=True,
# startMethod='attach',
#)),
self.new_response(req_disconnect),
self.new_event('exited', exitCode=0),
self.new_event('terminated'),
self.new_response(req_disconnect),
])
self.assert_received(self.debugger, [
self.debugger_msgs.new_request(CMD_VERSION,
Expand Down