mirror of
https://github.com/microsoft/debugpy.git
synced 2025-12-23 08:48:12 +00:00
Send exit message before process is replaced. WIP #865
This commit is contained in:
parent
1b568ab86a
commit
7f25cb22b8
7 changed files with 152 additions and 4 deletions
|
|
@ -699,6 +699,7 @@ def create_execl(original_name):
|
|||
if _get_apply_arg_patching():
|
||||
args = patch_args(args, is_exec=True)
|
||||
send_process_created_message()
|
||||
send_process_about_to_be_replaced()
|
||||
|
||||
return getattr(os, original_name)(path, *args)
|
||||
|
||||
|
|
@ -715,6 +716,7 @@ def create_execv(original_name):
|
|||
if _get_apply_arg_patching():
|
||||
args = patch_args(args, is_exec=True)
|
||||
send_process_created_message()
|
||||
send_process_about_to_be_replaced()
|
||||
|
||||
return getattr(os, original_name)(path, args)
|
||||
|
||||
|
|
@ -731,6 +733,7 @@ def create_execve(original_name):
|
|||
if _get_apply_arg_patching():
|
||||
args = patch_args(args, is_exec=True)
|
||||
send_process_created_message()
|
||||
send_process_about_to_be_replaced()
|
||||
|
||||
return getattr(os, original_name)(path, args, env)
|
||||
|
||||
|
|
@ -915,6 +918,12 @@ def send_process_created_message():
|
|||
py_db.send_process_created_message()
|
||||
|
||||
|
||||
def send_process_about_to_be_replaced():
|
||||
py_db = get_global_debugger()
|
||||
if py_db is not None:
|
||||
py_db.send_process_about_to_be_replaced()
|
||||
|
||||
|
||||
def patch_new_process_functions():
|
||||
# os.execl(path, arg0, arg1, ...)
|
||||
# os.execle(path, arg0, arg1, ..., env)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ class _BaseNetCommand(object):
|
|||
def send(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def call_after_send(self, callback):
|
||||
pass
|
||||
|
||||
|
||||
class _NullNetCommand(_BaseNetCommand):
|
||||
pass
|
||||
|
|
@ -48,6 +51,8 @@ class NetCommand(_BaseNetCommand):
|
|||
_showing_debug_info = 0
|
||||
_show_debug_info_lock = ForkSafeLock(rlock=True)
|
||||
|
||||
_after_send = None
|
||||
|
||||
def __init__(self, cmd_id, seq, text, is_json=False):
|
||||
"""
|
||||
If sequence is 0, new sequence will be generated (otherwise, this was the response
|
||||
|
|
@ -100,6 +105,9 @@ class NetCommand(_BaseNetCommand):
|
|||
if get_protocol() in (HTTP_PROTOCOL, HTTP_JSON_PROTOCOL):
|
||||
sock.sendall(('Content-Length: %s\r\n\r\n' % len(as_bytes)).encode('ascii'))
|
||||
sock.sendall(as_bytes)
|
||||
if self._after_send:
|
||||
for method in self._after_send:
|
||||
method(sock)
|
||||
except:
|
||||
if IS_JYTHON:
|
||||
# Ignore errors in sock.sendall in Jython (seems to be common for Jython to
|
||||
|
|
@ -108,6 +116,12 @@ class NetCommand(_BaseNetCommand):
|
|||
else:
|
||||
raise
|
||||
|
||||
def call_after_send(self, callback):
|
||||
if not self._after_send:
|
||||
self._after_send = [callback]
|
||||
else:
|
||||
self._after_send.append(callback)
|
||||
|
||||
@classmethod
|
||||
def _show_debug_info(cls, cmd_id, seq, text):
|
||||
with cls._show_debug_info_lock:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
from functools import partial
|
||||
import itertools
|
||||
import os
|
||||
import sys
|
||||
import socket as socket_module
|
||||
|
||||
from _pydev_bundle._pydev_imports_tipper import TYPE_IMPORT, TYPE_CLASS, TYPE_FUNCTION, TYPE_ATTR, \
|
||||
TYPE_BUILTIN, TYPE_PARAM
|
||||
|
|
@ -8,7 +10,8 @@ from _pydev_bundle.pydev_is_thread_alive import is_thread_alive
|
|||
from _pydev_bundle.pydev_override import overrides
|
||||
from _pydevd_bundle._debug_adapter import pydevd_schema
|
||||
from _pydevd_bundle._debug_adapter.pydevd_schema import ModuleEvent, ModuleEventBody, Module, \
|
||||
OutputEventBody, OutputEvent, ContinuedEventBody
|
||||
OutputEventBody, OutputEvent, ContinuedEventBody, ExitedEventBody, \
|
||||
ExitedEvent
|
||||
from _pydevd_bundle.pydevd_comm_constants import CMD_THREAD_CREATE, CMD_RETURN, CMD_MODULE_EVENT, \
|
||||
CMD_WRITE_TO_CONSOLE, CMD_STEP_INTO, CMD_STEP_INTO_MY_CODE, CMD_STEP_OVER, CMD_STEP_OVER_MY_CODE, \
|
||||
CMD_STEP_RETURN, CMD_STEP_CAUGHT_EXCEPTION, CMD_ADD_EXCEPTION_BREAK, CMD_SET_BREAK, \
|
||||
|
|
@ -398,6 +401,18 @@ class NetCommandFactoryJson(NetCommandFactory):
|
|||
def make_process_created_message(self, *args, **kwargs):
|
||||
return NULL_NET_COMMAND # Not a part of the debug adapter protocol
|
||||
|
||||
@overrides(NetCommandFactory.make_process_about_to_be_replaced_message)
|
||||
def make_process_about_to_be_replaced_message(self):
|
||||
event = ExitedEvent(ExitedEventBody(-1, pydevdReason="processReplaced"))
|
||||
|
||||
cmd = NetCommand(CMD_RETURN, 0, event, is_json=True)
|
||||
|
||||
def after_send(socket):
|
||||
socket.setsockopt(socket_module.IPPROTO_TCP, socket_module.TCP_NODELAY, 1)
|
||||
|
||||
cmd.call_after_send(after_send)
|
||||
return cmd
|
||||
|
||||
@overrides(NetCommandFactory.make_thread_suspend_message)
|
||||
def make_thread_suspend_message(self, *args, **kwargs):
|
||||
return NULL_NET_COMMAND # Not a part of the debug adapter protocol
|
||||
|
|
|
|||
|
|
@ -61,6 +61,9 @@ class NetCommandFactory(object):
|
|||
cmdText = '<process/>'
|
||||
return NetCommand(CMD_PROCESS_CREATED, 0, cmdText)
|
||||
|
||||
def make_process_about_to_be_replaced_message(self):
|
||||
return NULL_NET_COMMAND
|
||||
|
||||
def make_show_cython_warning_message(self):
|
||||
try:
|
||||
return NetCommand(CMD_SHOW_CYTHON_WARNING, 0, '')
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ from _pydevd_bundle.pydevd_comm import(InternalConsoleExec,
|
|||
from _pydevd_bundle.pydevd_daemon_thread import PyDBDaemonThread, mark_as_pydevd_daemon_thread
|
||||
from _pydevd_bundle.pydevd_process_net_command_json import PyDevJsonCommandProcessor
|
||||
from _pydevd_bundle.pydevd_process_net_command import process_net_command
|
||||
from _pydevd_bundle.pydevd_net_command import NetCommand
|
||||
from _pydevd_bundle.pydevd_net_command import NetCommand, NULL_NET_COMMAND
|
||||
|
||||
from _pydevd_bundle.pydevd_breakpoints import stop_on_unhandled_exception
|
||||
from _pydevd_bundle.pydevd_collect_bytecode_info import collect_try_except_info, collect_return_info, collect_try_except_info_from_source
|
||||
|
|
@ -1909,6 +1909,32 @@ class PyDB(object):
|
|||
cmd = self.cmd_factory.make_process_created_message()
|
||||
self.writer.add_command(cmd)
|
||||
|
||||
def send_process_about_to_be_replaced(self):
|
||||
"""Sends a message that a new process has been created.
|
||||
"""
|
||||
if self.writer is None or self.cmd_factory is None:
|
||||
return
|
||||
cmd = self.cmd_factory.make_process_about_to_be_replaced_message()
|
||||
if cmd is NULL_NET_COMMAND:
|
||||
return
|
||||
|
||||
sent = [False]
|
||||
|
||||
def after_sent(*args, **kwargs):
|
||||
sent[0] = True
|
||||
|
||||
cmd.call_after_send(after_sent)
|
||||
self.writer.add_command(cmd)
|
||||
|
||||
timeout = 5 # Wait up to 5 seconds
|
||||
initial_time = time.time()
|
||||
while not sent[0]:
|
||||
time.sleep(.05)
|
||||
|
||||
if (time.time() - initial_time) > timeout:
|
||||
pydev_log.critical('pydevd: Sending message related to process being replaced timed-out after %s seconds', timeout)
|
||||
break
|
||||
|
||||
def set_next_statement(self, frame, event, func_name, next_line):
|
||||
stop = False
|
||||
response_msg = ""
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
import sys
|
||||
|
||||
if __name__ == '__main__':
|
||||
if 'in-sub' not in sys.argv:
|
||||
import os
|
||||
# These functions all execute a new program, replacing the current process; they do not return.
|
||||
# os.execl(path, arg0, arg1, ...)
|
||||
# os.execle(path, arg0, arg1, ..., env)
|
||||
# os.execlp(file, arg0, arg1, ...)
|
||||
# os.execlpe(file, arg0, arg1, ..., env)¶
|
||||
# os.execv(path, args)
|
||||
# os.execve(path, args, env)
|
||||
# os.execvp(file, args)
|
||||
# os.execvpe(file, args, env)
|
||||
os.execvp(sys.executable, [sys.executable, __file__, 'in-sub'])
|
||||
else:
|
||||
print('In sub')
|
||||
print('TEST SUCEEDED!')
|
||||
|
|
@ -5937,6 +5937,69 @@ def test_same_lineno_and_filename(case_setup, pyfile):
|
|||
writer.finished_ok = True
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pytest.main(['-k', 'test_case_skipping_filters', '-s'])
|
||||
@pytest.mark.skipif(sys.platform == 'win32', reason='Windows does not have execvp.')
|
||||
def test_replace_process(case_setup_multiprocessing):
|
||||
import threading
|
||||
from tests_python.debugger_unittest import AbstractWriterThread
|
||||
from _pydevd_bundle._debug_adapter.pydevd_schema import ExitedEvent
|
||||
|
||||
with case_setup_multiprocessing.test_file(
|
||||
'_debugger_case_replace_process.py',
|
||||
) as writer:
|
||||
json_facade = JsonFacade(writer)
|
||||
json_facade.write_launch()
|
||||
|
||||
break1_line = writer.get_line_index_with_content("print('In sub')")
|
||||
json_facade.write_set_breakpoints([break1_line])
|
||||
|
||||
server_socket = writer.server_socket
|
||||
secondary_finished_ok = [False]
|
||||
|
||||
class SecondaryProcessWriterThread(AbstractWriterThread):
|
||||
|
||||
TEST_FILE = writer.get_main_filename()
|
||||
_sequence = -1
|
||||
|
||||
class SecondaryProcessThreadCommunication(threading.Thread):
|
||||
|
||||
def run(self):
|
||||
from tests_python.debugger_unittest import ReaderThread
|
||||
server_socket.listen(1)
|
||||
self.server_socket = server_socket
|
||||
new_sock, addr = server_socket.accept()
|
||||
|
||||
reader_thread = ReaderThread(new_sock)
|
||||
reader_thread.name = ' *** Multiprocess Reader Thread'
|
||||
reader_thread.start()
|
||||
|
||||
writer2 = SecondaryProcessWriterThread()
|
||||
writer2.reader_thread = reader_thread
|
||||
writer2.sock = new_sock
|
||||
json_facade2 = JsonFacade(writer2)
|
||||
|
||||
json_facade2.write_set_breakpoints([break1_line, ])
|
||||
json_facade2.write_make_initial_run()
|
||||
|
||||
json_facade2.wait_for_thread_stopped()
|
||||
json_facade2.write_continue()
|
||||
secondary_finished_ok[0] = True
|
||||
|
||||
secondary_process_thread_communication = SecondaryProcessThreadCommunication()
|
||||
secondary_process_thread_communication.start()
|
||||
time.sleep(.1)
|
||||
|
||||
json_facade.write_make_initial_run()
|
||||
exited_event = json_facade.wait_for_json_message(ExitedEvent)
|
||||
assert exited_event.body.kwargs['pydevdReason'] == "processReplaced"
|
||||
|
||||
secondary_process_thread_communication.join(10)
|
||||
if secondary_process_thread_communication.is_alive():
|
||||
raise AssertionError('The SecondaryProcessThreadCommunication did not finish')
|
||||
|
||||
assert secondary_finished_ok[0]
|
||||
writer.finished_ok = True
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pytest.main(['-k', 'test_replace_process', '-s'])
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue