mirror of
https://github.com/microsoft/debugpy.git
synced 2025-12-23 08:48:12 +00:00
* Remove wrapper and in-proc support code * Fixing up multiproc patching * Address comments * Import cleanup * More cleanup * Remove loopback fast path * Disable IDE disconnect unpause-ing * Add missing file to do not trace
This commit is contained in:
parent
0ea50467aa
commit
e093c86fba
39 changed files with 290 additions and 4405 deletions
|
|
@ -37,7 +37,7 @@ from ptvsd import _version
|
|||
__version__ = _version.get_versions()["version"]
|
||||
del _version
|
||||
|
||||
from ptvsd.server.attach_server import (
|
||||
from ptvsd.server.attach import (
|
||||
attach,
|
||||
break_into_debugger,
|
||||
debug_this_thread,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See LICENSE in the project root
|
||||
# for license information.
|
||||
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
import contextlib
|
||||
from importlib import import_module
|
||||
import os
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See LICENSE in the project root
|
||||
# for license information.
|
||||
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
from . import VENDORED_ROOT
|
||||
from ._util import cwd, iter_all_files
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See LICENSE in the project root
|
||||
# for license information.
|
||||
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
import contextlib
|
||||
import os
|
||||
import os.path
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See LICENSE in the project root
|
||||
# for license information.
|
||||
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
from importlib import import_module
|
||||
import warnings
|
||||
|
||||
|
|
@ -50,3 +56,15 @@ def ptvsd_breakpointhook():
|
|||
|
||||
|
||||
pydevd.install_breakpointhook(ptvsd_breakpointhook)
|
||||
|
||||
# Ensure that pydevd uses JSON protocol
|
||||
from _pydevd_bundle import pydevd_constants
|
||||
from _pydevd_bundle import pydevd_defaults
|
||||
pydevd_defaults.PydevdCustomization.DEFAULT_PROTOCOL = pydevd_constants.HTTP_JSON_PROTOCOL
|
||||
|
||||
# Ensure our patch args is used. This is invoked when a child process is spawned
|
||||
# with multiproc debugging enabled.
|
||||
from _pydev_bundle import pydev_monkey
|
||||
from ptvsd.server import multiproc
|
||||
pydev_monkey.patch_args = multiproc.patch_and_quote_args
|
||||
pydev_monkey.patch_new_process_functions()
|
||||
|
|
|
|||
|
|
@ -26,8 +26,10 @@ class PyDevdAPI(object):
|
|||
def run(self, py_db):
|
||||
py_db.ready_to_run = True
|
||||
|
||||
def notify_initialize(self, py_db):
|
||||
py_db.on_initialize()
|
||||
|
||||
def notify_configuration_done(self, py_db):
|
||||
py_db.dap_debugger_attached.set()
|
||||
py_db.on_configuration_done()
|
||||
|
||||
def notify_disconnect(self, py_db):
|
||||
|
|
@ -124,7 +126,7 @@ class PyDevdAPI(object):
|
|||
self.remove_all_breakpoints(py_db, filename='*')
|
||||
self.remove_all_exception_breakpoints(py_db)
|
||||
self.notify_disconnect(py_db)
|
||||
py_db.dap_debugger_attached.clear()
|
||||
|
||||
if resume_threads:
|
||||
self.request_resume_thread(thread_id='*')
|
||||
|
||||
|
|
@ -568,7 +570,10 @@ class PyDevdAPI(object):
|
|||
reset_caches = True
|
||||
|
||||
def custom_dont_trace_external_files(abs_path):
|
||||
return abs_path.startswith(start_patterns) or abs_path.endswith(end_patterns)
|
||||
for p in start_patterns:
|
||||
if p in abs_path:
|
||||
return True
|
||||
return abs_path.endswith(end_patterns)
|
||||
|
||||
custom_dont_trace_external_files.start_patterns = start_patterns
|
||||
custom_dont_trace_external_files.end_patterns = end_patterns
|
||||
|
|
|
|||
|
|
@ -71,9 +71,9 @@ from _pydev_bundle.pydev_imports import _queue
|
|||
from _pydev_imps._pydev_saved_modules import time
|
||||
from _pydev_imps._pydev_saved_modules import thread
|
||||
from _pydev_imps._pydev_saved_modules import threading
|
||||
from socket import AF_INET, SOCK_STREAM, SHUT_RD, SHUT_WR, SOL_SOCKET, SO_REUSEADDR, SHUT_RDWR
|
||||
from _pydevd_bundle.pydevd_constants import (DebugInfoHolder, get_thread_id, IS_JYTHON, IS_PY2,
|
||||
IS_PY36_OR_GREATER, STATE_RUN, dict_keys, ASYNC_EVAL_TIMEOUT_SEC, GlobalDebuggerHolder,
|
||||
from socket import AF_INET, SOCK_STREAM, SHUT_RD, SHUT_WR, SOL_SOCKET, SO_REUSEADDR, SHUT_RDWR, IPPROTO_TCP
|
||||
from _pydevd_bundle.pydevd_constants import (DebugInfoHolder, get_thread_id, IS_WINDOWS, IS_JYTHON,
|
||||
IS_PY2, IS_PY36_OR_GREATER, STATE_RUN, dict_keys, ASYNC_EVAL_TIMEOUT_SEC, GlobalDebuggerHolder,
|
||||
get_global_debugger, GetGlobalDebugger, set_global_debugger) # Keep for backward compatibility @UnusedImport
|
||||
from _pydev_bundle.pydev_override import overrides
|
||||
import weakref
|
||||
|
|
@ -227,7 +227,10 @@ class ReaderThread(PyDBDaemonThread):
|
|||
self._buffer = self._buffer[size:]
|
||||
return ret
|
||||
|
||||
r = self.sock.recv(max(size - buffer_len, 1024))
|
||||
try:
|
||||
r = self.sock.recv(max(size - buffer_len, 1024))
|
||||
except OSError:
|
||||
return b''
|
||||
if not r:
|
||||
return b''
|
||||
self._buffer += r
|
||||
|
|
@ -241,7 +244,10 @@ class ReaderThread(PyDBDaemonThread):
|
|||
self._buffer = self._buffer[i:]
|
||||
return ret
|
||||
else:
|
||||
r = self.sock.recv(1024)
|
||||
try:
|
||||
r = self.sock.recv(1024)
|
||||
except OSError:
|
||||
return b''
|
||||
if not r:
|
||||
return b''
|
||||
self._buffer += r
|
||||
|
|
@ -343,7 +349,7 @@ class WriterThread(PyDBDaemonThread):
|
|||
def add_command(self, cmd):
|
||||
''' cmd is NetCommand '''
|
||||
if not self.killReceived: # we don't take new data after everybody die
|
||||
self.cmdQueue.put(cmd)
|
||||
self.cmdQueue.put(cmd, False)
|
||||
|
||||
@overrides(PyDBDaemonThread._on_run)
|
||||
def _on_run(self):
|
||||
|
|
@ -353,7 +359,7 @@ class WriterThread(PyDBDaemonThread):
|
|||
while True:
|
||||
try:
|
||||
try:
|
||||
cmd = self.cmdQueue.get(1, 0.1)
|
||||
cmd = self.cmdQueue.get(True, 0.1)
|
||||
except _queue.Empty:
|
||||
if self.killReceived:
|
||||
try:
|
||||
|
|
@ -367,6 +373,9 @@ class WriterThread(PyDBDaemonThread):
|
|||
|
||||
return # break if queue is empty and killReceived
|
||||
else:
|
||||
if time is None:
|
||||
break # interpreter shutdown
|
||||
time.sleep(self.timeout)
|
||||
continue
|
||||
except:
|
||||
# pydev_log.info('Finishing debug communication...(1)')
|
||||
|
|
@ -404,18 +413,21 @@ class WriterThread(PyDBDaemonThread):
|
|||
|
||||
|
||||
def create_server_socket(host, port):
|
||||
s = socket(AF_INET, SOCK_STREAM)
|
||||
s.settimeout(None)
|
||||
|
||||
try:
|
||||
from socket import SO_REUSEPORT
|
||||
s.setsockopt(SOL_SOCKET, SO_REUSEPORT, 1)
|
||||
except ImportError:
|
||||
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
|
||||
server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
|
||||
if IS_WINDOWS:
|
||||
from socket import SO_EXCLUSIVEADDRUSE
|
||||
server.setsockopt(SOL_SOCKET, SO_EXCLUSIVEADDRUSE, 1)
|
||||
else:
|
||||
server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
|
||||
|
||||
s.bind((host, port))
|
||||
pydev_log.info("Bound to port :%s", port)
|
||||
return s
|
||||
server.bind((host, port))
|
||||
server.settimeout(None)
|
||||
except Exception:
|
||||
server.close()
|
||||
raise
|
||||
|
||||
return server
|
||||
|
||||
|
||||
def start_server(port):
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
'''
|
||||
This module holds the customization settings for the debugger.
|
||||
'''
|
||||
from _pydevd_bundle.pydevd_constants import QUOTED_LINE_PROTOCOL
|
||||
|
||||
|
||||
class PydevdCustomization(object):
|
||||
DEFAULT_PROTOCOL = QUOTED_LINE_PROTOCOL
|
||||
|
|
@ -75,6 +75,7 @@ DONT_TRACE = {
|
|||
'pydevd_constants.py': PYDEV_FILE,
|
||||
'pydevd_custom_frames.py': PYDEV_FILE,
|
||||
'pydevd_cython_wrapper.py': PYDEV_FILE,
|
||||
'pydevd_defaults.py': PYDEV_FILE,
|
||||
'pydevd_dont_trace.py': PYDEV_FILE,
|
||||
'pydevd_dont_trace_files.py': PYDEV_FILE,
|
||||
'pydevd_exec.py': PYDEV_FILE,
|
||||
|
|
|
|||
|
|
@ -195,7 +195,6 @@ class _PyDevJsonCommandProcessor(object):
|
|||
|
||||
py_db._main_lock.acquire()
|
||||
try:
|
||||
|
||||
cmd = on_request(py_db, request)
|
||||
if cmd is not None:
|
||||
py_db.writer.add_command(cmd)
|
||||
|
|
@ -225,6 +224,7 @@ class _PyDevJsonCommandProcessor(object):
|
|||
{'filter': 'uncaught', 'label': 'Uncaught Exceptions', 'default': True},
|
||||
],
|
||||
}
|
||||
self.api.notify_initialize(py_db)
|
||||
response = pydevd_base_schema.build_response(request, kwargs={'body': body})
|
||||
return NetCommand(CMD_RETURN, 0, response, is_json=True)
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ from _pydevd_bundle.pydevd_constants import (IS_JYTH_LESS25, get_thread_id, get_
|
|||
clear_cached_thread_id, INTERACTIVE_MODE_AVAILABLE, SHOW_DEBUG_INFO_ENV, IS_PY34_OR_GREATER, IS_PY2, NULL,
|
||||
NO_FTRACE, IS_IRONPYTHON)
|
||||
from _pydevd_bundle.pydevd_custom_frames import CustomFramesContainer, custom_frames_container_init
|
||||
from _pydevd_bundle.pydevd_defaults import PydevdCustomization
|
||||
from _pydevd_bundle.pydevd_dont_trace_files import DONT_TRACE, PYDEV_FILE
|
||||
from _pydevd_bundle.pydevd_extension_api import DebuggerEventHandler
|
||||
from _pydevd_bundle.pydevd_frame_utils import add_exception_to_frame, remove_exception_from_frame
|
||||
|
|
@ -397,6 +398,9 @@ class PyDB(object):
|
|||
|
||||
self.breakpoints = {}
|
||||
|
||||
# Set communication protocol
|
||||
PyDevdAPI().set_protocol(self, 0, PydevdCustomization.DEFAULT_PROTOCOL)
|
||||
|
||||
# mtime to be raised when breakpoints change
|
||||
self.mtime = 0
|
||||
|
||||
|
|
@ -475,6 +479,9 @@ class PyDB(object):
|
|||
|
||||
self._local_thread_trace_func = threading.local()
|
||||
|
||||
self._server_socket_ready_event = threading.Event()
|
||||
self._server_socket_name = None
|
||||
|
||||
# Bind many locals to the debugger because upon teardown those names may become None
|
||||
# in the namespace (and thus can't be relied upon unless the reference was previously
|
||||
# saved).
|
||||
|
|
@ -515,8 +522,11 @@ class PyDB(object):
|
|||
self._exclude_by_filter_cache = {}
|
||||
self._apply_filter_cache = {}
|
||||
|
||||
# State for use with DAP based connections
|
||||
self.dap_debugger_attached = threading.Event()
|
||||
def on_initialize(self):
|
||||
'''
|
||||
Note: only called when using the DAP (Debug Adapter Protocol).
|
||||
'''
|
||||
self._on_configuration_done_event.clear()
|
||||
|
||||
def on_configuration_done(self):
|
||||
'''
|
||||
|
|
@ -524,6 +534,9 @@ class PyDB(object):
|
|||
'''
|
||||
self._on_configuration_done_event.set()
|
||||
|
||||
def is_attached(self):
|
||||
return self._on_configuration_done_event.is_set()
|
||||
|
||||
def on_disconnect(self):
|
||||
'''
|
||||
Note: only called when using the DAP (Debug Adapter Protocol).
|
||||
|
|
@ -924,9 +937,16 @@ class PyDB(object):
|
|||
if self._waiting_for_connection_thread is not None:
|
||||
raise AssertionError('There is already another thread waiting for a connection.')
|
||||
|
||||
self._server_socket_ready_event.clear()
|
||||
self._waiting_for_connection_thread = self._WaitForConnectionThread(self)
|
||||
self._waiting_for_connection_thread.start()
|
||||
|
||||
def set_server_socket_ready(self):
|
||||
self._server_socket_ready_event.set()
|
||||
|
||||
def wait_for_server_socket_ready(self):
|
||||
self._server_socket_ready_event.wait()
|
||||
|
||||
class _WaitForConnectionThread(PyDBDaemonThread):
|
||||
|
||||
def __init__(self, py_db):
|
||||
|
|
@ -939,6 +959,8 @@ class PyDB(object):
|
|||
port = SetupHolder.setup['port']
|
||||
|
||||
self._server_socket = create_server_socket(host=host, port=port)
|
||||
self.py_db._server_socket_name = self._server_socket.getsockname()
|
||||
self.py_db.set_server_socket_ready()
|
||||
|
||||
while not self.killReceived:
|
||||
try:
|
||||
|
|
@ -1726,7 +1748,6 @@ class PyDB(object):
|
|||
sys.path.insert(0, os.path.split(rPath(file))[0])
|
||||
|
||||
if set_trace:
|
||||
|
||||
while not self.ready_to_run:
|
||||
time.sleep(0.1) # busy wait until we receive run command
|
||||
|
||||
|
|
@ -1942,12 +1963,15 @@ def _enable_attach(address):
|
|||
if port != SetupHolder.setup['port']:
|
||||
raise AssertionError('Unable to listen in port: %s (already listening in port: %s)' % (port, SetupHolder.setup['port']))
|
||||
settrace(host=host, port=port, suspend=False, wait_for_ready_to_run=False, block_until_connected=False)
|
||||
py_db = get_global_debugger()
|
||||
py_db.wait_for_server_socket_ready()
|
||||
return py_db._server_socket_name
|
||||
|
||||
|
||||
def _wait_for_attach():
|
||||
'''
|
||||
Meant to be called after _enable_attach() -- the current thread will only unblock after a
|
||||
connection is in place and the the DAP (Debug Adapter Protocol) sends the ConfigurationDone
|
||||
connection is in place and the DAP (Debug Adapter Protocol) sends the ConfigurationDone
|
||||
request.
|
||||
'''
|
||||
py_db = get_global_debugger()
|
||||
|
|
@ -1957,6 +1981,14 @@ def _wait_for_attach():
|
|||
py_db.block_until_configuration_done()
|
||||
|
||||
|
||||
def _is_attached():
|
||||
'''
|
||||
Can be called any time to check if the connection was established and the DAP (Debug Adapter Protocol) has sent
|
||||
the ConfigurationDone request.
|
||||
'''
|
||||
py_db = get_global_debugger()
|
||||
return (py_db is not None) and py_db.is_attached()
|
||||
|
||||
#=======================================================================================================================
|
||||
# settrace
|
||||
#=======================================================================================================================
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
# Licensed under the MIT License. See LICENSE in the project root
|
||||
# for license information.
|
||||
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
# This file helps to compute a version number in source trees obtained from
|
||||
# git-archive tarball (such as those provided by githubs download-from-tag
|
||||
# feature). Distribution tarballs (built by setup.py sdist) and build
|
||||
|
|
|
|||
|
|
@ -5,9 +5,11 @@
|
|||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
import functools
|
||||
import platform
|
||||
|
||||
import ptvsd
|
||||
from ptvsd.common import json, log, messaging, singleton
|
||||
from ptvsd.common.compat import unicode
|
||||
from ptvsd.adapter import channels, debuggee, contract, options, state
|
||||
|
||||
|
||||
|
|
@ -170,7 +172,7 @@ class IDEMessages(Messages):
|
|||
# TODO: nodebug
|
||||
debuggee.spawn_and_connect(request)
|
||||
|
||||
return self._configure()
|
||||
return self._configure(request)
|
||||
|
||||
@_replay_to_server
|
||||
@_only_allowed_while("initializing")
|
||||
|
|
@ -183,12 +185,34 @@ class IDEMessages(Messages):
|
|||
options.port = int(request.arguments.get("port", options.port))
|
||||
_channels.connect_to_server(address=(options.host, options.port))
|
||||
|
||||
return self._configure()
|
||||
return self._configure(request)
|
||||
|
||||
def _set_debugger_properties(self, request):
|
||||
debug_options = set(request("debugOptions", json.array(unicode)))
|
||||
client_os_type = None
|
||||
if 'WindowsClient' in debug_options or 'WINDOWS' in debug_options:
|
||||
client_os_type = 'WINDOWS'
|
||||
elif 'UnixClient' in debug_options or 'UNIX' in debug_options:
|
||||
client_os_type = 'UNIX'
|
||||
else:
|
||||
client_os_type = 'WINDOWS' if platform.system() == 'Windows' else 'UNIX'
|
||||
|
||||
try:
|
||||
self._server.request("setDebuggerProperty", arguments={
|
||||
"dontTraceStartPatterns": ["\\ptvsd\\", "/ptvsd/"],
|
||||
"dontTraceEndPatterns": ["ptvsd_launcher.py"],
|
||||
"skipSuspendOnBreakpointException": ("BaseException",),
|
||||
"skipPrintBreakpointException": ("NameError",),
|
||||
"multiThreadsSingleNotification": True,
|
||||
"ideOS": client_os_type,
|
||||
})
|
||||
except messaging.MessageHandlingError as exc:
|
||||
exc.propagate(request)
|
||||
|
||||
# Handles the configuration request sequence for "launch" or "attach", from when
|
||||
# the "initialized" event is sent, to when "configurationDone" is received; see
|
||||
# https://github.com/microsoft/vscode/issues/4902#issuecomment-368583522
|
||||
def _configure(self):
|
||||
def _configure(self, request):
|
||||
log.debug("Replaying previously received messages to server.")
|
||||
|
||||
assert len(self._initial_messages)
|
||||
|
|
@ -211,6 +235,8 @@ class IDEMessages(Messages):
|
|||
log.debug("Finished replaying messages to server.")
|
||||
self.initial_messages = None
|
||||
|
||||
self._set_debugger_properties(request)
|
||||
|
||||
# Let the IDE know that it can begin configuring the adapter.
|
||||
state.change("configuring")
|
||||
self._ide.send_event("initialized")
|
||||
|
|
|
|||
|
|
@ -1,343 +0,0 @@
|
|||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See LICENSE in the project root
|
||||
# for license information.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import contextlib
|
||||
import threading
|
||||
import time
|
||||
import sys
|
||||
|
||||
import ptvsd.common.log
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def ignore_errors(log=None):
|
||||
"""A context manager that masks any raised exceptions."""
|
||||
try:
|
||||
yield
|
||||
except Exception:
|
||||
if log is not None:
|
||||
ptvsd.common.log.exception('Ignoring error', level='info')
|
||||
|
||||
|
||||
def call_all(callables, *args, **kwargs):
|
||||
"""Return the result of calling every given object."""
|
||||
results = []
|
||||
for call in callables:
|
||||
try:
|
||||
call(*args, **kwargs)
|
||||
except Exception as exc:
|
||||
results.append((call, exc))
|
||||
else:
|
||||
results.append((call, None))
|
||||
return results
|
||||
|
||||
|
||||
########################
|
||||
# threading stuff
|
||||
|
||||
try:
|
||||
ThreadError = threading.ThreadError
|
||||
except AttributeError:
|
||||
ThreadError = RuntimeError
|
||||
|
||||
|
||||
try:
|
||||
base = __builtins__.TimeoutError
|
||||
except AttributeError:
|
||||
base = OSError
|
||||
class TimeoutError(base): # noqa
|
||||
"""Timeout expired."""
|
||||
timeout = None
|
||||
reason = None
|
||||
|
||||
@classmethod
|
||||
def from_timeout(cls, timeout, reason=None):
|
||||
"""Return a TimeoutError with the given timeout."""
|
||||
msg = 'timed out (after {} seconds)'.format(timeout)
|
||||
if reason is not None:
|
||||
msg += ' ' + reason
|
||||
self = cls(msg)
|
||||
self.timeout = timeout
|
||||
self.reason = reason
|
||||
return self
|
||||
del base # noqa
|
||||
|
||||
|
||||
def wait(check, timeout=None, reason=None):
|
||||
"""Wait for the given func to return True.
|
||||
|
||||
If a timeout is given and reached then raise TimeoutError.
|
||||
"""
|
||||
if timeout is None or timeout <= 0:
|
||||
while not check():
|
||||
time.sleep(0.01)
|
||||
else:
|
||||
if not _wait(check, timeout):
|
||||
raise TimeoutError.from_timeout(timeout, reason)
|
||||
|
||||
|
||||
def is_locked(lock):
|
||||
"""Return True if the lock is locked."""
|
||||
if lock is None:
|
||||
return False
|
||||
if not lock.acquire(False):
|
||||
return True
|
||||
lock_release(lock)
|
||||
return False
|
||||
|
||||
|
||||
def lock_release(lock):
|
||||
"""Ensure that the lock is released."""
|
||||
if lock is None:
|
||||
return
|
||||
try:
|
||||
lock.release()
|
||||
except ThreadError: # already unlocked
|
||||
pass
|
||||
|
||||
|
||||
def lock_wait(lock, timeout=None, reason='waiting for lock'):
|
||||
"""Wait until the lock is not locked."""
|
||||
if not _lock_acquire(lock, timeout):
|
||||
raise TimeoutError.from_timeout(timeout, reason)
|
||||
lock_release(lock)
|
||||
|
||||
|
||||
if sys.version_info >= (3,):
|
||||
def _lock_acquire(lock, timeout):
|
||||
if timeout is None:
|
||||
timeout = -1
|
||||
return lock.acquire(timeout=timeout)
|
||||
else:
|
||||
def _lock_acquire(lock, timeout):
|
||||
if timeout is None or timeout <= 0:
|
||||
return lock.acquire()
|
||||
|
||||
def check():
|
||||
return lock.acquire(False)
|
||||
return _wait(check, timeout)
|
||||
|
||||
|
||||
def _wait(check, timeout):
|
||||
if check():
|
||||
return True
|
||||
for _ in range(int(timeout * 100)):
|
||||
time.sleep(0.01)
|
||||
if check():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def new_hidden_thread(name, target, prefix='ptvsd.common.', daemon=True, **kwargs):
|
||||
"""Return a thread that will be ignored by pydevd."""
|
||||
if prefix is not None and not name.startswith(prefix):
|
||||
name = prefix + name
|
||||
t = threading.Thread(
|
||||
name=name,
|
||||
target=target,
|
||||
**kwargs
|
||||
)
|
||||
t.pydev_do_not_trace = True
|
||||
t.is_pydev_daemon_thread = True
|
||||
if daemon:
|
||||
t.daemon = True
|
||||
return t
|
||||
|
||||
|
||||
########################
|
||||
# closing stuff
|
||||
|
||||
class ClosedError(RuntimeError):
|
||||
"""Indicates that the object is closed."""
|
||||
|
||||
|
||||
def close_all(closeables):
|
||||
"""Return the result of closing every given object."""
|
||||
results = []
|
||||
for obj in closeables:
|
||||
try:
|
||||
obj.close()
|
||||
except Exception as exc:
|
||||
results.append((obj, exc))
|
||||
else:
|
||||
results.append((obj, None))
|
||||
return results
|
||||
|
||||
|
||||
class Closeable(object):
|
||||
"""A base class for types that may be closed."""
|
||||
|
||||
NAME = None
|
||||
FAIL_ON_ALREADY_CLOSED = True
|
||||
|
||||
def __init__(self):
|
||||
super(Closeable, self).__init__()
|
||||
self._closed = False
|
||||
self._closedlock = threading.Lock()
|
||||
self._handlers = []
|
||||
|
||||
def __del__(self):
|
||||
try:
|
||||
self.close()
|
||||
except ClosedError:
|
||||
pass
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
self.close()
|
||||
|
||||
@property
|
||||
def closed(self):
|
||||
return self._closed
|
||||
|
||||
def add_resource_to_close(self, resource, before=False):
|
||||
"""Add a resource to be closed when closing."""
|
||||
close = resource.close
|
||||
if before:
|
||||
def handle_closing(before):
|
||||
if not before:
|
||||
return
|
||||
close()
|
||||
else:
|
||||
def handle_closing(before):
|
||||
if before:
|
||||
return
|
||||
close()
|
||||
self.add_close_handler(handle_closing)
|
||||
|
||||
def add_close_handler(self, handle_closing, nodupe=True):
|
||||
"""Add a func to be called when closing.
|
||||
|
||||
The func takes one arg: True if it was called before the main
|
||||
close func and False if after.
|
||||
"""
|
||||
with self._closedlock:
|
||||
if self._closed:
|
||||
if self.FAIL_ON_ALREADY_CLOSED:
|
||||
raise ClosedError('already closed')
|
||||
return
|
||||
if nodupe and handle_closing in self._handlers:
|
||||
raise ValueError('close func already added')
|
||||
|
||||
self._handlers.append(handle_closing)
|
||||
|
||||
def check_closed(self):
|
||||
"""Raise ClosedError if closed."""
|
||||
if self._closed:
|
||||
if self.NAME:
|
||||
raise ClosedError('{} closed'.format(self.NAME))
|
||||
else:
|
||||
raise ClosedError('closed')
|
||||
|
||||
@contextlib.contextmanager
|
||||
def while_not_closed(self):
|
||||
"""A context manager under which the object will not be closed."""
|
||||
with self._closedlock:
|
||||
self.check_closed()
|
||||
yield
|
||||
|
||||
def close(self):
|
||||
"""Release any owned resources and clean up."""
|
||||
with self._closedlock:
|
||||
if self._closed:
|
||||
if self.FAIL_ON_ALREADY_CLOSED:
|
||||
raise ClosedError('already closed')
|
||||
return
|
||||
self._closed = True
|
||||
handlers = list(self._handlers)
|
||||
|
||||
results = call_all(handlers, True)
|
||||
self._log_results(results)
|
||||
self._close()
|
||||
results = call_all(handlers, False)
|
||||
self._log_results(results)
|
||||
|
||||
# implemented by subclasses
|
||||
|
||||
def _close(self):
|
||||
pass
|
||||
|
||||
# internal methods
|
||||
|
||||
def _log_results(self, results, log=None):
|
||||
if log is None:
|
||||
return
|
||||
for obj, exc in results:
|
||||
if exc is None:
|
||||
continue
|
||||
log('failed to close {!r} ({!r})'.format(obj, exc))
|
||||
|
||||
|
||||
########################
|
||||
# running stuff
|
||||
|
||||
class NotRunningError(RuntimeError):
|
||||
"""Something isn't currently running."""
|
||||
|
||||
|
||||
class AlreadyStartedError(RuntimeError):
|
||||
"""Something was already started."""
|
||||
|
||||
|
||||
class AlreadyRunningError(AlreadyStartedError):
|
||||
"""Something is already running."""
|
||||
|
||||
|
||||
class Startable(object):
|
||||
"""A base class for types that may be started."""
|
||||
|
||||
RESTARTABLE = False
|
||||
FAIL_ON_ALREADY_STOPPED = True
|
||||
|
||||
def __init__(self):
|
||||
super(Startable, self).__init__()
|
||||
self._is_running = None
|
||||
self._startlock = threading.Lock()
|
||||
self._numstarts = 0
|
||||
|
||||
def is_running(self, checkclosed=True):
|
||||
"""Return True if currently running."""
|
||||
if checkclosed and hasattr(self, 'check_closed'):
|
||||
self.check_closed()
|
||||
is_running = self._is_running
|
||||
if is_running is None:
|
||||
return False
|
||||
return is_running()
|
||||
|
||||
def start(self, *args, **kwargs):
|
||||
"""Begin internal execution."""
|
||||
with self._startlock:
|
||||
if self.is_running():
|
||||
raise AlreadyRunningError()
|
||||
if not self.RESTARTABLE and self._numstarts > 0:
|
||||
raise AlreadyStartedError()
|
||||
|
||||
self._is_running = self._start(*args, **kwargs)
|
||||
self._numstarts += 1
|
||||
|
||||
def stop(self, *args, **kwargs):
|
||||
"""Stop execution and wait until done."""
|
||||
with self._startlock:
|
||||
# TODO: Call self.check_closed() here?
|
||||
if not self.is_running(checkclosed=False):
|
||||
if not self.FAIL_ON_ALREADY_STOPPED:
|
||||
return
|
||||
raise NotRunningError()
|
||||
self._is_running = None
|
||||
|
||||
self._stop(*args, **kwargs)
|
||||
|
||||
# implemented by subclasses
|
||||
|
||||
def _start(self, *args, **kwargs):
|
||||
"""Return an "is_running()" func after starting."""
|
||||
raise NotImplementedError
|
||||
|
||||
def _stop(self):
|
||||
raise NotImplementedError
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
# Licensed under the MIT License. See LICENSE in the project root
|
||||
# for license information.
|
||||
|
||||
from __future__ import print_function, absolute_import, unicode_literals
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
import contextlib
|
||||
import functools
|
||||
|
|
|
|||
|
|
@ -19,8 +19,7 @@ import itertools
|
|||
import sys
|
||||
import threading
|
||||
|
||||
from ptvsd.common import compat, fmt, json, log
|
||||
from ptvsd.common._util import new_hidden_thread
|
||||
from ptvsd.common import compat, fmt, json, log, util
|
||||
|
||||
|
||||
class JsonIOStream(object):
|
||||
|
|
@ -774,7 +773,7 @@ class JsonMessageChannel(object):
|
|||
self._stop = threading.Event()
|
||||
self._seq_iter = itertools.count(1)
|
||||
self._requests = {}
|
||||
self._worker = new_hidden_thread(repr(self), self._process_incoming_messages)
|
||||
self._worker = util.new_hidden_thread(repr(self), self._process_incoming_messages)
|
||||
self._worker.daemon = True
|
||||
|
||||
def __repr__(self):
|
||||
|
|
@ -1199,7 +1198,7 @@ class JsonMessageChannel(object):
|
|||
error_message = str(error_message)
|
||||
exc_type = MessageHandlingError
|
||||
if error_message.startswith(InvalidMessageError.PREFIX):
|
||||
error_message = error_message[len(InvalidMessageError.PREFIX) :]
|
||||
error_message = error_message[len(InvalidMessageError.PREFIX):]
|
||||
exc_type = InvalidMessageError
|
||||
body = exc_type(error_message, request)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
# Licensed under the MIT License. See LICENSE in the project root
|
||||
# for license information.
|
||||
|
||||
from __future__ import print_function, with_statement, absolute_import
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
import os
|
||||
|
||||
|
|
|
|||
|
|
@ -2,80 +2,18 @@
|
|||
# Licensed under the MIT License. See LICENSE in the project root
|
||||
# for license information.
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
from collections import namedtuple
|
||||
import contextlib
|
||||
import errno
|
||||
import platform
|
||||
import socket
|
||||
try:
|
||||
from urllib.parse import urlparse
|
||||
except ImportError:
|
||||
from urlparse import urlparse
|
||||
|
||||
|
||||
try:
|
||||
ConnectionError # noqa
|
||||
BrokenPipeError # noqa
|
||||
ConnectionResetError # noqa
|
||||
except NameError:
|
||||
class BrokenPipeError(Exception):
|
||||
# EPIPE and ESHUTDOWN
|
||||
pass
|
||||
|
||||
class ConnectionResetError(Exception):
|
||||
# ECONNRESET
|
||||
pass
|
||||
|
||||
|
||||
NOT_CONNECTED = (
|
||||
errno.ENOTCONN,
|
||||
errno.EBADF,
|
||||
)
|
||||
|
||||
CLOSED = (
|
||||
errno.EPIPE,
|
||||
errno.ESHUTDOWN,
|
||||
errno.ECONNRESET,
|
||||
# Windows
|
||||
10038, # "An operation was attempted on something that is not a socket"
|
||||
10058,
|
||||
)
|
||||
|
||||
EOF = NOT_CONNECTED + CLOSED
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def convert_eof():
|
||||
"""A context manager to convert some socket errors into EOFError."""
|
||||
try:
|
||||
yield
|
||||
except ConnectionResetError:
|
||||
raise EOFError
|
||||
except BrokenPipeError:
|
||||
raise EOFError
|
||||
except OSError as exc:
|
||||
if exc.errno in EOF:
|
||||
raise EOFError
|
||||
raise
|
||||
|
||||
|
||||
class TimeoutError(socket.timeout):
|
||||
"""A socket timeout happened."""
|
||||
|
||||
|
||||
def is_socket(sock):
|
||||
"""Return True if the object can be used as a socket."""
|
||||
return isinstance(sock, socket.socket)
|
||||
|
||||
|
||||
def create_server(host, port, timeout=None):
|
||||
"""Return a local server socket listening on the given port."""
|
||||
if host is None:
|
||||
host = 'localhost'
|
||||
server = _new_sock()
|
||||
try:
|
||||
server = _new_sock()
|
||||
server.bind((host, port))
|
||||
if timeout is not None:
|
||||
server.settimeout(timeout)
|
||||
|
|
@ -92,116 +30,17 @@ def create_client():
|
|||
|
||||
|
||||
def _new_sock():
|
||||
sock = socket.socket(socket.AF_INET,
|
||||
socket.SOCK_STREAM,
|
||||
socket.IPPROTO_TCP)
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)
|
||||
if platform.system() == 'Windows':
|
||||
try:
|
||||
sock.ioctl(socket.SIO_LOOPBACK_FAST_PATH, True)
|
||||
except AttributeError:
|
||||
pass # Not supported in python 2.* or <3.6
|
||||
except OSError as ose:
|
||||
if ose.winerror == 10045: # Not supported by OS
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_EXCLUSIVEADDRUSE, 1)
|
||||
else:
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
return sock
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def ignored_errno(*ignored):
|
||||
"""A context manager that ignores the given errnos."""
|
||||
try:
|
||||
yield
|
||||
except OSError as exc:
|
||||
if exc.errno not in ignored:
|
||||
raise
|
||||
|
||||
|
||||
class KeepAlive(namedtuple('KeepAlive', 'interval idle maxfails')):
|
||||
"""TCP keep-alive settings."""
|
||||
|
||||
INTERVAL = 3 # seconds
|
||||
IDLE = 1 # seconds after idle
|
||||
MAX_FAILS = 5
|
||||
|
||||
@classmethod
|
||||
def from_raw(cls, raw):
|
||||
"""Return the corresponding KeepAlive."""
|
||||
if raw is None:
|
||||
return None
|
||||
elif isinstance(raw, cls):
|
||||
return raw
|
||||
elif isinstance(raw, (str, int, float)):
|
||||
return cls(raw)
|
||||
else:
|
||||
try:
|
||||
raw = dict(raw)
|
||||
except TypeError:
|
||||
return cls(*raw)
|
||||
else:
|
||||
return cls(**raw)
|
||||
|
||||
def __new__(cls, interval=None, idle=None, maxfails=None):
|
||||
self = super(KeepAlive, cls).__new__(
|
||||
cls,
|
||||
float(interval) if interval or interval == 0 else cls.INTERVAL,
|
||||
float(idle) if idle or idle == 0 else cls.IDLE,
|
||||
float(maxfails) if maxfails or maxfails == 0 else cls.MAX_FAILS,
|
||||
)
|
||||
return self
|
||||
|
||||
def apply(self, sock):
|
||||
"""Set the keepalive values on the socket."""
|
||||
sock.setsockopt(socket.SOL_SOCKET,
|
||||
socket.SO_KEEPALIVE,
|
||||
1)
|
||||
interval = self.interval
|
||||
idle = self.idle
|
||||
maxfails = self.maxfails
|
||||
try:
|
||||
if interval > 0:
|
||||
sock.setsockopt(socket.IPPROTO_TCP,
|
||||
socket.TCP_KEEPINTVL,
|
||||
interval)
|
||||
if idle > 0:
|
||||
sock.setsockopt(socket.IPPROTO_TCP,
|
||||
socket.TCP_KEEPIDLE,
|
||||
idle)
|
||||
if maxfails >= 0:
|
||||
sock.setsockopt(socket.IPPROTO_TCP,
|
||||
socket.TCP_KEEPCNT,
|
||||
maxfails)
|
||||
except AttributeError:
|
||||
# mostly linux-only
|
||||
pass
|
||||
|
||||
|
||||
def connect(sock, addr, keepalive=None):
|
||||
"""Return the client socket for the next connection."""
|
||||
if addr is None:
|
||||
if keepalive is None or keepalive is True:
|
||||
keepalive = KeepAlive()
|
||||
elif keepalive:
|
||||
keepalive = KeepAlive.from_raw(keepalive)
|
||||
client, _ = sock.accept()
|
||||
if keepalive:
|
||||
keepalive.apply(client)
|
||||
return client
|
||||
else:
|
||||
if keepalive:
|
||||
raise NotImplementedError
|
||||
sock.connect(addr)
|
||||
return sock
|
||||
|
||||
|
||||
def shut_down(sock, how=socket.SHUT_RDWR, ignored=NOT_CONNECTED):
|
||||
def shut_down(sock, how=socket.SHUT_RDWR):
|
||||
"""Shut down the given socket."""
|
||||
with ignored_errno(*ignored or ()):
|
||||
sock.shutdown(how)
|
||||
sock.shutdown(how)
|
||||
|
||||
|
||||
def close_socket(sock):
|
||||
|
|
@ -211,90 +50,3 @@ def close_socket(sock):
|
|||
except Exception:
|
||||
pass
|
||||
sock.close()
|
||||
|
||||
|
||||
class Address(namedtuple('Address', 'host port')):
|
||||
"""An IP address to use for sockets."""
|
||||
|
||||
@classmethod
|
||||
def from_raw(cls, raw, defaultport=None):
|
||||
"""Return an address corresponding to the given data."""
|
||||
if isinstance(raw, cls):
|
||||
return raw
|
||||
elif isinstance(raw, int):
|
||||
return cls(None, raw)
|
||||
elif isinstance(raw, str):
|
||||
if raw == '':
|
||||
return cls('', defaultport)
|
||||
parsed = urlparse(raw)
|
||||
if not parsed.netloc:
|
||||
if parsed.scheme:
|
||||
raise ValueError('invalid address {!r}'.format(raw))
|
||||
return cls.from_raw('x://' + raw, defaultport=defaultport)
|
||||
return cls(
|
||||
parsed.hostname or '',
|
||||
parsed.port if parsed.port else defaultport,
|
||||
)
|
||||
elif not raw:
|
||||
return cls(None, defaultport)
|
||||
else:
|
||||
try:
|
||||
kwargs = dict(**raw)
|
||||
except TypeError:
|
||||
return cls(*raw)
|
||||
else:
|
||||
kwargs.setdefault('host', None)
|
||||
kwargs.setdefault('port', defaultport)
|
||||
return cls(**kwargs)
|
||||
|
||||
@classmethod
|
||||
def as_server(cls, host, port):
|
||||
"""Return an address to use as a server address."""
|
||||
return cls(host, port, isserver=True)
|
||||
|
||||
@classmethod
|
||||
def as_client(cls, host, port):
|
||||
"""Return an address to use as a server address."""
|
||||
return cls(host, port, isserver=False)
|
||||
|
||||
def __new__(cls, host, port, **kwargs):
|
||||
if host == '*':
|
||||
host = ''
|
||||
isserver = kwargs.pop('isserver', None)
|
||||
if isserver is None:
|
||||
isserver = (host is None or host == '')
|
||||
else:
|
||||
isserver = bool(isserver)
|
||||
if host is None:
|
||||
host = 'localhost'
|
||||
self = super(Address, cls).__new__(
|
||||
cls,
|
||||
str(host),
|
||||
int(port) if port is not None else None,
|
||||
**kwargs
|
||||
)
|
||||
self._isserver = isserver
|
||||
return self
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if self.port is None:
|
||||
raise TypeError('missing port')
|
||||
if self.port < 0 or self.port > 65535:
|
||||
raise ValueError('port must be non-negative int < 65535')
|
||||
|
||||
def __repr__(self):
|
||||
orig = super(Address, self).__repr__()
|
||||
return '{}, isserver={})'.format(orig[:-1], self._isserver)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not super(Address, self).__eq__(other):
|
||||
return False
|
||||
try:
|
||||
other = self.from_raw(other)
|
||||
except Exception:
|
||||
return False
|
||||
return self._isserver == other._isserver
|
||||
|
||||
@property
|
||||
def isserver(self):
|
||||
return self._isserver
|
||||
|
|
|
|||
23
src/ptvsd/common/util.py
Normal file
23
src/ptvsd/common/util.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See LICENSE in the project root
|
||||
# for license information.
|
||||
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
import threading
|
||||
|
||||
|
||||
def new_hidden_thread(name, target, prefix='ptvsd.common.', daemon=True, **kwargs):
|
||||
"""Return a thread that will be ignored by pydevd."""
|
||||
if prefix is not None and not name.startswith(prefix):
|
||||
name = prefix + name
|
||||
t = threading.Thread(
|
||||
name=name,
|
||||
target=target,
|
||||
**kwargs
|
||||
)
|
||||
t.pydev_do_not_trace = True
|
||||
t.is_pydev_daemon_thread = True
|
||||
if daemon:
|
||||
t.daemon = False
|
||||
return t
|
||||
|
|
@ -2,8 +2,9 @@
|
|||
# Licensed under the MIT License. See LICENSE in the project root
|
||||
# for license information.
|
||||
|
||||
import ptvsd.server.log
|
||||
import ptvsd.server.options
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
from ptvsd.server import log, options
|
||||
from ptvsd.server.__main__ import run_file, run_module, run_code
|
||||
|
||||
|
||||
|
|
@ -16,8 +17,8 @@ DONT_DEBUG = []
|
|||
# A legacy entrypoint for Visual Studio, to allow older versions to work with new ptvsd.server.
|
||||
# All new code should use the entrypoints in ptvsd.server.__main__ directly.
|
||||
def debug(filename, port_num, debug_id, debug_options, run_as):
|
||||
ptvsd.server.log.to_file()
|
||||
ptvsd.server.log.info(
|
||||
log.to_file()
|
||||
log.info(
|
||||
"debug{0!r}", (filename, port_num, debug_id, debug_options, run_as)
|
||||
)
|
||||
|
||||
|
|
@ -26,10 +27,10 @@ def debug(filename, port_num, debug_id, debug_options, run_as):
|
|||
except KeyError:
|
||||
raise ValueError("run_as must be one of: {0!r}".format(tuple(RUNNERS.keys())))
|
||||
|
||||
ptvsd.server.options.target_kind = "file" if run_as == "script" else run_as
|
||||
ptvsd.server.options.target = filename
|
||||
ptvsd.server.options.port = port_num
|
||||
ptvsd.server.options.client = True
|
||||
options.target_kind = "file" if run_as == "script" else run_as
|
||||
options.target = filename
|
||||
options.port = port_num
|
||||
options.client = True
|
||||
|
||||
# debug_id is ignored because it has no meaning in DAP.
|
||||
# debug_options are ignored, because they will be passed later via DAP "launch" request.
|
||||
|
|
|
|||
|
|
@ -2,11 +2,8 @@
|
|||
# Licensed under the MIT License. See LICENSE in the project root
|
||||
# for license information.
|
||||
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
# "force_pydevd" must be imported first to ensure (via side effects)
|
||||
# that the ptvsd-vendored copy of pydevd gets used.
|
||||
import ptvsd._vendored.force_pydevd
|
||||
|
||||
|
||||
# Treat all modules in ptvsd.common as if they were also in this package for now.
|
||||
import ptvsd.common
|
||||
__path__ += ptvsd.common.__path__
|
||||
import ptvsd._vendored.force_pydevd # noqa
|
||||
|
|
|
|||
|
|
@ -2,9 +2,8 @@
|
|||
# Licensed under the MIT License. See LICENSE in the project root
|
||||
# for license information.
|
||||
|
||||
from __future__ import absolute_import, print_function, with_statement
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
import numbers
|
||||
import os.path
|
||||
import runpy
|
||||
import site
|
||||
|
|
@ -18,9 +17,8 @@ assert "pydevd" in sys.modules
|
|||
|
||||
import pydevd
|
||||
from ptvsd.common import log
|
||||
import ptvsd.server._remote
|
||||
import ptvsd.server.options
|
||||
import ptvsd.server.runner
|
||||
import ptvsd.server.attach
|
||||
from ptvsd.server.multiproc import listen_for_subprocesses
|
||||
|
||||
|
||||
|
|
@ -191,9 +189,6 @@ def parse(args):
|
|||
return it
|
||||
|
||||
|
||||
daemon = None
|
||||
|
||||
|
||||
def setup_connection():
|
||||
ptvsd.common.log.debug('sys.prefix: {0}', (sys.prefix,))
|
||||
|
||||
|
|
@ -265,15 +260,10 @@ def setup_connection():
|
|||
|
||||
addr = (opts.host, opts.port)
|
||||
|
||||
global daemon
|
||||
if opts.no_debug:
|
||||
daemon = ptvsd.server.runner.Daemon()
|
||||
if not daemon.wait_for_launch(addr):
|
||||
return
|
||||
elif opts.client:
|
||||
daemon = ptvsd.server._remote.attach(addr)
|
||||
if opts.client:
|
||||
ptvsd.server.attach.attach(addr)
|
||||
else:
|
||||
daemon = ptvsd.server._remote.enable_attach(addr)
|
||||
ptvsd.server.attach.enable_attach(addr)
|
||||
|
||||
if opts.wait:
|
||||
ptvsd.wait_for_attach()
|
||||
|
|
@ -420,14 +410,7 @@ def main(argv=sys.argv):
|
|||
}[ptvsd.server.options.target_kind]
|
||||
run()
|
||||
except SystemExit as ex:
|
||||
ptvsd.common.log.exception('Debuggee exited via SystemExit', level='debug')
|
||||
if daemon is not None:
|
||||
if ex.code is None:
|
||||
daemon.exitcode = 0
|
||||
elif isinstance(ex.code, numbers.Integral):
|
||||
daemon.exitcode = int(ex.code)
|
||||
else:
|
||||
daemon.exitcode = 1
|
||||
ptvsd.common.log.exception('Debuggee exited via SystemExit: {0!r}', ex.code, level='debug')
|
||||
raise
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,148 +0,0 @@
|
|||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See LICENSE in the project root
|
||||
# for license information.
|
||||
|
||||
import numbers
|
||||
import sys
|
||||
import time
|
||||
|
||||
import pydevd
|
||||
from _pydevd_bundle.pydevd_comm import get_global_debugger
|
||||
|
||||
from ptvsd.server.pydevd_hooks import install
|
||||
from ptvsd.server.runner import run as no_debug_runner
|
||||
from ptvsd.server.socket import Address
|
||||
from ptvsd.server._util import new_hidden_thread
|
||||
|
||||
|
||||
PYDEVD_DEFAULTS = {
|
||||
'--qt-support=auto',
|
||||
}
|
||||
|
||||
|
||||
def _set_pydevd_defaults(pydevd_args):
|
||||
args_to_append = []
|
||||
for arg in PYDEVD_DEFAULTS:
|
||||
if arg not in pydevd_args:
|
||||
args_to_append.append(arg)
|
||||
return pydevd_args + args_to_append
|
||||
|
||||
|
||||
########################
|
||||
# high-level functions
|
||||
|
||||
def debug_main(address, name, kind, *extra, **kwargs):
|
||||
if not kwargs.pop('wait', False) and address.isserver:
|
||||
def unblock_debugger():
|
||||
debugger = get_global_debugger()
|
||||
while debugger is None:
|
||||
time.sleep(0.1)
|
||||
debugger = get_global_debugger()
|
||||
debugger.ready_to_run = True
|
||||
new_hidden_thread('ptvsd.server.unblock_debugger', unblock_debugger).start()
|
||||
if kind == 'module':
|
||||
run_module(address, name, *extra, **kwargs)
|
||||
else:
|
||||
run_file(address, name, *extra, **kwargs)
|
||||
|
||||
|
||||
def run_main(address, name, kind, *extra, **kwargs):
|
||||
addr = Address.from_raw(address)
|
||||
sys.argv[:] = _run_main_argv(name, extra)
|
||||
runner = kwargs.pop('_runner', no_debug_runner)
|
||||
runner(addr, name, kind == 'module', *extra, **kwargs)
|
||||
|
||||
|
||||
########################
|
||||
# low-level functions
|
||||
|
||||
def run_module(address, modname, *extra, **kwargs):
|
||||
"""Run pydevd for the given module."""
|
||||
addr = Address.from_raw(address)
|
||||
if not addr.isserver:
|
||||
kwargs['singlesession'] = True
|
||||
run = kwargs.pop('_run', _run)
|
||||
prog = kwargs.pop('_prog', sys.argv[0])
|
||||
filename = modname + ':'
|
||||
argv = _run_argv(addr, filename, extra, _prog=prog)
|
||||
argv.insert(argv.index('--file'), '--module')
|
||||
run(argv, addr, **kwargs)
|
||||
|
||||
|
||||
def run_file(address, filename, *extra, **kwargs):
|
||||
"""Run pydevd for the given Python file."""
|
||||
addr = Address.from_raw(address)
|
||||
if not addr.isserver:
|
||||
kwargs['singlesession'] = True
|
||||
run = kwargs.pop('_run', _run)
|
||||
prog = kwargs.pop('_prog', sys.argv[0])
|
||||
argv = _run_argv(addr, filename, extra, _prog=prog)
|
||||
run(argv, addr, **kwargs)
|
||||
|
||||
|
||||
def _run_argv(address, filename, extra, _prog=sys.argv[0]):
|
||||
"""Convert the given values to an argv that pydevd.main() supports."""
|
||||
if '--' in extra:
|
||||
pydevd = list(extra[:extra.index('--')])
|
||||
extra = list(extra[len(pydevd) + 1:])
|
||||
else:
|
||||
pydevd = []
|
||||
extra = list(extra)
|
||||
|
||||
pydevd = _set_pydevd_defaults(pydevd)
|
||||
host, port = address
|
||||
argv = [
|
||||
_prog,
|
||||
'--port', str(port),
|
||||
]
|
||||
if not address.isserver:
|
||||
argv.extend([
|
||||
'--client', host or 'localhost',
|
||||
])
|
||||
return argv + pydevd + [
|
||||
'--file', filename,
|
||||
] + extra
|
||||
|
||||
|
||||
def _run_main_argv(filename, extra):
|
||||
if '--' in extra:
|
||||
pydevd = list(extra[:extra.index('--')])
|
||||
extra = list(extra[len(pydevd) + 1:])
|
||||
else:
|
||||
extra = list(extra)
|
||||
return [filename] + extra
|
||||
|
||||
|
||||
def _run(argv, addr, _pydevd=pydevd, _install=install, **kwargs):
|
||||
"""Start pydevd with the given commandline args."""
|
||||
|
||||
# Pydevd assumes that the "__main__" module is the "pydevd" module
|
||||
# and does some tricky stuff under that assumption. For example,
|
||||
# when the debugger starts up it calls save_main_module()
|
||||
# (in pydevd_bundle/pydevd_utils.py). That function explicitly sets
|
||||
# sys.modules["pydevd"] to sys.modules["__main__"] and then sets
|
||||
# the __main__ module to a new one. This makes some sense since
|
||||
# it gives the debugged script a fresh __main__ module.
|
||||
#
|
||||
# This complicates things for us since we are running a different
|
||||
# file (i.e. this one) as the __main__ module. Consequently,
|
||||
# sys.modules["pydevd"] gets set to ptvsd/__main__.py. Subsequent
|
||||
# imports of the "pydevd" module then return the wrong module. We
|
||||
# work around this by avoiding lazy imports of the "pydevd" module.
|
||||
# We also replace the __main__ module with the "pydevd" module here.
|
||||
if sys.modules['__main__'].__file__ != _pydevd.__file__:
|
||||
sys.modules['__main___orig'] = sys.modules['__main__']
|
||||
sys.modules['__main__'] = _pydevd
|
||||
|
||||
daemon = _install(_pydevd, addr, **kwargs)
|
||||
sys.argv[:] = argv
|
||||
try:
|
||||
_pydevd.main()
|
||||
except SystemExit as ex:
|
||||
if ex.code is None:
|
||||
daemon.exitcode = 0
|
||||
elif isinstance(ex.code, numbers.Integral):
|
||||
daemon.exitcode = int(ex.code)
|
||||
else:
|
||||
daemon.exitcode = 1
|
||||
raise
|
||||
|
|
@ -1,111 +0,0 @@
|
|||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See LICENSE in the project root
|
||||
# for license information.
|
||||
|
||||
import pydevd
|
||||
import time
|
||||
|
||||
from _pydevd_bundle.pydevd_comm import get_global_debugger
|
||||
|
||||
import ptvsd
|
||||
import ptvsd.server.log
|
||||
import ptvsd.server.options
|
||||
from ptvsd.server._util import new_hidden_thread
|
||||
from ptvsd.server.pydevd_hooks import install
|
||||
from ptvsd.server.daemon import session_not_bound, DaemonClosedError
|
||||
|
||||
|
||||
global_next_session = lambda: None
|
||||
|
||||
|
||||
def enable_attach(address, on_attach=lambda: None, **kwargs):
|
||||
|
||||
host, port = address
|
||||
|
||||
def wait_for_connection(daemon, host, port, next_session=None):
|
||||
ptvsd.server.log.debug('Waiting for pydevd ...')
|
||||
debugger = get_global_debugger()
|
||||
while debugger is None:
|
||||
time.sleep(0.1)
|
||||
debugger = get_global_debugger()
|
||||
|
||||
ptvsd.server.log.debug('Unblocking pydevd.')
|
||||
debugger.ready_to_run = True
|
||||
|
||||
while True:
|
||||
try:
|
||||
session_not_bound.wait()
|
||||
try:
|
||||
global_next_session()
|
||||
on_attach()
|
||||
except DaemonClosedError:
|
||||
return
|
||||
except TypeError:
|
||||
# May happen during interpreter shutdown
|
||||
# (if some global -- such as global_next_session becomes None).
|
||||
return
|
||||
|
||||
def start_daemon():
|
||||
daemon._sock = daemon._start()
|
||||
_, next_session = daemon.start_server(addr=(host, port))
|
||||
global global_next_session
|
||||
global_next_session = next_session
|
||||
return daemon._sock
|
||||
|
||||
daemon = install(pydevd,
|
||||
address,
|
||||
start_server=None,
|
||||
start_client=(lambda daemon, h, port: start_daemon()),
|
||||
singlesession=False,
|
||||
**kwargs)
|
||||
|
||||
ptvsd.server.log.debug('Starting connection listener thread')
|
||||
connection_thread = new_hidden_thread('ptvsd.server.listen_for_connection',
|
||||
wait_for_connection,
|
||||
args=(daemon, host, port))
|
||||
connection_thread.start()
|
||||
|
||||
if ptvsd.server.options.no_debug:
|
||||
_setup_nodebug()
|
||||
else:
|
||||
ptvsd.server.log.debug('pydevd.settrace()')
|
||||
pydevd.settrace(host=host,
|
||||
port=port,
|
||||
suspend=False,
|
||||
patch_multiprocessing=ptvsd.server.options.multiprocess)
|
||||
|
||||
return daemon
|
||||
|
||||
|
||||
def attach(address, **kwargs):
|
||||
host, port = address
|
||||
daemon = install(pydevd, address, singlesession=False, **kwargs)
|
||||
|
||||
if ptvsd.server.options.no_debug:
|
||||
_setup_nodebug()
|
||||
else:
|
||||
ptvsd.server.log.debug('pydevd.settrace()')
|
||||
pydevd.settrace(host=host,
|
||||
port=port,
|
||||
suspend=False,
|
||||
patch_multiprocessing=ptvsd.server.options.multiprocess)
|
||||
|
||||
return daemon
|
||||
|
||||
|
||||
def _setup_nodebug():
|
||||
ptvsd.server.log.debug('Running pydevd in nodebug mode.')
|
||||
debugger = pydevd.PyDB()
|
||||
debugger.init_matplotlib_support = lambda *arg: None
|
||||
# We are invoking run() solely for side effects here - setting up the
|
||||
# debugger and connecting to our socket - so the code run is a no-op.
|
||||
debugger.run(
|
||||
file='ptvsd.server._remote:_nop',
|
||||
globals=None,
|
||||
locals=None,
|
||||
is_module=True,
|
||||
set_trace=False)
|
||||
|
||||
|
||||
def _nop():
|
||||
pass
|
||||
|
|
@ -2,29 +2,16 @@
|
|||
# Licensed under the MIT License. See LICENSE in the project root
|
||||
# for license information.
|
||||
|
||||
import sys
|
||||
import warnings
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
import ptvsd.server.log
|
||||
from ptvsd.server._remote import (
|
||||
attach as ptvsd_attach,
|
||||
enable_attach as ptvsd_enable_attach,
|
||||
)
|
||||
import sys
|
||||
import pydevd
|
||||
|
||||
from ptvsd.common import log, options as common_opts
|
||||
from ptvsd.server import multiproc, options as server_opts
|
||||
from _pydevd_bundle.pydevd_constants import get_global_debugger
|
||||
from pydevd_file_utils import get_abs_path_real_path_and_base_from_frame
|
||||
|
||||
WAIT_TIMEOUT = 1.0
|
||||
|
||||
DEFAULT_HOST = '0.0.0.0'
|
||||
DEFAULT_PORT = 5678
|
||||
|
||||
_pending_threads = set()
|
||||
|
||||
_redirect_output_deprecation_msg = (
|
||||
"'redirect_output' setting via enable_attach will be deprecated in the future versions of the debugger. "
|
||||
"This can be set using redirectOutput in Launch config in VS Code, using Tee output option in Visual Studio, "
|
||||
"or debugOptions configuration for any client.")
|
||||
|
||||
def wait_for_attach(timeout=None):
|
||||
"""If a remote debugger is attached, returns immediately. Otherwise,
|
||||
|
|
@ -36,17 +23,19 @@ def wait_for_attach(timeout=None):
|
|||
timeout : float, optional
|
||||
The timeout for the operation in seconds (or fractions thereof).
|
||||
"""
|
||||
ptvsd.server.log.info('wait_for_attach{0!r}', (timeout,))
|
||||
log.info('wait_for_attach()')
|
||||
dbg = get_global_debugger()
|
||||
if bool(dbg):
|
||||
dbg.dap_debugger_attached.wait(timeout)
|
||||
else:
|
||||
if not bool(dbg):
|
||||
msg = 'wait_for_attach() called before enable_attach().'
|
||||
ptvsd.server.log.info(msg)
|
||||
log.info(msg)
|
||||
raise AssertionError(msg)
|
||||
|
||||
pydevd._wait_for_attach()
|
||||
|
||||
def enable_attach(address=(DEFAULT_HOST, DEFAULT_PORT), redirect_output=None, log_dir=None):
|
||||
|
||||
def enable_attach(
|
||||
address=(server_opts.host, server_opts.port),
|
||||
log_dir=None):
|
||||
"""Enables a client to attach to this process remotely to debug Python code.
|
||||
|
||||
Parameters
|
||||
|
|
@ -58,9 +47,6 @@ def enable_attach(address=(DEFAULT_HOST, DEFAULT_PORT), redirect_output=None, lo
|
|||
``(hostname, port)``. On client side, the server is identified by the
|
||||
Qualifier string in the usual ``'hostname:port'`` format, e.g.:
|
||||
``'myhost.cloudapp.net:5678'``. Default is ``('0.0.0.0', 5678)``.
|
||||
redirect_output : bool, optional
|
||||
(Deprecated) Specifies whether any output (on both `stdout` and `stderr`) produced
|
||||
by this program should be sent to the debugger. Default is ``True``.
|
||||
log_dir : str, optional
|
||||
Name of the directory that debugger will create its log files in.
|
||||
If not specified, logging is disabled.
|
||||
|
|
@ -79,30 +65,27 @@ def enable_attach(address=(DEFAULT_HOST, DEFAULT_PORT), redirect_output=None, lo
|
|||
"""
|
||||
|
||||
if log_dir:
|
||||
ptvsd.common.options.log_dir = log_dir
|
||||
ptvsd.server.log.to_file()
|
||||
ptvsd.server.log.info('enable_attach{0!r}', (address,))
|
||||
|
||||
if redirect_output is not None:
|
||||
ptvsd.server.log.info('redirect_output deprecation warning.')
|
||||
warnings.warn(_redirect_output_deprecation_msg, DeprecationWarning, stacklevel=2)
|
||||
common_opts.log_dir = log_dir
|
||||
log.to_file()
|
||||
log.info('enable_attach{0!r}', (address,))
|
||||
|
||||
if is_attached():
|
||||
ptvsd.server.log.info('enable_attach() ignored - already attached.')
|
||||
return
|
||||
|
||||
dbg = get_global_debugger()
|
||||
if bool(dbg):
|
||||
dbg.dap_debugger_attached.clear()
|
||||
log.info('enable_attach() ignored - already attached.')
|
||||
return None, None
|
||||
|
||||
# Ensure port is int
|
||||
port = address[1]
|
||||
address = (address[0], port if type(port) is int else int(port))
|
||||
host, port = address
|
||||
address = (host, int(port))
|
||||
|
||||
ptvsd_enable_attach(address)
|
||||
server_opts.host, server_opts.port = pydevd._enable_attach(address)
|
||||
|
||||
if server_opts.subprocess_notify:
|
||||
multiproc.notify_root(server_opts.port)
|
||||
|
||||
return (server_opts.host, server_opts.port)
|
||||
|
||||
|
||||
def attach(address, redirect_output=None, log_dir=None):
|
||||
def attach(address, log_dir=None):
|
||||
"""Attaches this process to the debugger listening on a given address.
|
||||
|
||||
Parameters
|
||||
|
|
@ -112,42 +95,36 @@ def attach(address, redirect_output=None, log_dir=None):
|
|||
for TCP connections. It is in the same format as used for
|
||||
regular sockets of the `socket.AF_INET` family, i.e. a tuple of
|
||||
``(hostname, port)``.
|
||||
redirect_output : bool, optional
|
||||
(Deprecated) Specifies whether any output (on both `stdout` and `stderr`) produced
|
||||
by this program should be sent to the debugger. Default is ``True``.
|
||||
log_dir : str, optional
|
||||
Name of the directory that debugger will create its log files in.
|
||||
If not specified, logging is disabled.
|
||||
"""
|
||||
|
||||
if log_dir:
|
||||
ptvsd.common.options.log_dir = log_dir
|
||||
ptvsd.server.log.to_file()
|
||||
ptvsd.server.log.info('attach{0!r}', (address, redirect_output))
|
||||
|
||||
if redirect_output is not None:
|
||||
ptvsd.server.log.info('redirect_output deprecation warning.')
|
||||
warnings.warn(_redirect_output_deprecation_msg, DeprecationWarning)
|
||||
common_opts.log_dir = log_dir
|
||||
log.to_file()
|
||||
log.info('attach{0!r}', (address,))
|
||||
|
||||
if is_attached():
|
||||
ptvsd.server.log.info('attach() ignored - already attached.')
|
||||
return
|
||||
|
||||
dbg = get_global_debugger()
|
||||
if bool(dbg):
|
||||
dbg.dap_debugger_attached.clear()
|
||||
log.info('attach() ignored - already attached.')
|
||||
return None, None
|
||||
|
||||
# Ensure port is int
|
||||
port = address[1]
|
||||
address = (address[0], port if type(port) is int else int(port))
|
||||
host, port = address
|
||||
address = (host, int(port))
|
||||
server_opts.host, server_opts.port = address
|
||||
|
||||
ptvsd_attach(address)
|
||||
log.debug('pydevd.settrace()')
|
||||
pydevd.settrace(
|
||||
host=host,
|
||||
port=port,
|
||||
suspend=False,
|
||||
patch_multiprocessing=server_opts.multiprocess)
|
||||
|
||||
|
||||
def is_attached():
|
||||
"""Returns ``True`` if debugger is attached, ``False`` otherwise."""
|
||||
dbg = get_global_debugger()
|
||||
return bool(dbg) and dbg.dap_debugger_attached.is_set()
|
||||
return pydevd._is_attached()
|
||||
|
||||
|
||||
def break_into_debugger():
|
||||
|
|
@ -155,10 +132,10 @@ def break_into_debugger():
|
|||
and breaks into the debugger with current thread as active.
|
||||
"""
|
||||
|
||||
ptvsd.server.log.info('break_into_debugger()')
|
||||
log.info('break_into_debugger()')
|
||||
|
||||
if not is_attached():
|
||||
ptvsd.server.log.info('break_into_debugger() ignored - debugger not attached')
|
||||
log.info('break_into_debugger() ignored - debugger not attached')
|
||||
return
|
||||
|
||||
# Get the first frame in the stack that's not an internal frame.
|
||||
|
|
@ -168,17 +145,6 @@ def break_into_debugger():
|
|||
get_abs_path_real_path_and_base_from_frame(stop_at_frame)) == global_debugger.PYDEV_FILE:
|
||||
stop_at_frame = stop_at_frame.f_back
|
||||
|
||||
|
||||
# pydevd.settrace() only enables debugging of the current
|
||||
# thread and all future threads. PyDevd is not enabled for
|
||||
# existing threads (other than the current one). Consequently,
|
||||
# pydevd.settrace() must be called ASAP in the current thread.
|
||||
# See issue #509.
|
||||
#
|
||||
# This is tricky, however, because settrace() will block until
|
||||
# it receives a CMD_RUN message. You can't just call it in a
|
||||
# thread to avoid blocking; doing so would prevent the current
|
||||
# thread from being debugged.
|
||||
pydevd.settrace(
|
||||
suspend=True,
|
||||
trace_only_current_thread=True,
|
||||
|
|
@ -1,560 +0,0 @@
|
|||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See LICENSE in the project root
|
||||
# for license information.
|
||||
|
||||
import contextlib
|
||||
import sys
|
||||
import threading
|
||||
|
||||
import ptvsd.server.log
|
||||
from ptvsd.server import wrapper, options, multiproc
|
||||
from ptvsd.server.socket import (
|
||||
close_socket, create_server, create_client, connect, Address)
|
||||
from .exit_handlers import (
|
||||
ExitHandlers, UnsupportedSignalError,
|
||||
kill_current_proc)
|
||||
from .session import PyDevdDebugSession
|
||||
from ._util import (
|
||||
ClosedError, NotRunningError, ignore_errors, lock_wait)
|
||||
|
||||
|
||||
session_not_bound = threading.Event()
|
||||
session_not_bound.set()
|
||||
|
||||
|
||||
def _wait_for_user():
|
||||
if sys.__stdout__ is not None:
|
||||
try:
|
||||
import msvcrt
|
||||
except ImportError:
|
||||
sys.__stdout__.write('Press Enter to continue . . . ')
|
||||
sys.__stdout__.flush()
|
||||
sys.__stdin__.read(1)
|
||||
else:
|
||||
sys.__stdout__.write('Press any key to continue . . . ')
|
||||
sys.__stdout__.flush()
|
||||
msvcrt.getch()
|
||||
|
||||
|
||||
class DaemonError(RuntimeError):
|
||||
"""Indicates that a Daemon had a problem."""
|
||||
MSG = 'error'
|
||||
|
||||
def __init__(self, msg=None):
|
||||
if msg is None:
|
||||
msg = self.MSG
|
||||
super(DaemonError, self).__init__(msg)
|
||||
|
||||
|
||||
class DaemonClosedError(DaemonError):
|
||||
"""Indicates that a Daemon was unexpectedly closed."""
|
||||
MSG = 'closed'
|
||||
|
||||
|
||||
class DaemonStoppedError(DaemonError):
|
||||
"""Indicates that a Daemon was unexpectedly stopped."""
|
||||
MSG = 'stopped'
|
||||
|
||||
|
||||
# TODO: Inherit from Closeable.
|
||||
# TODO: Inherit from Startable?
|
||||
|
||||
class DaemonBase(object):
|
||||
"""The base class for DAP daemons."""
|
||||
|
||||
SESSION = None
|
||||
|
||||
exitcode = None
|
||||
|
||||
def __init__(self, wait_for_user=_wait_for_user,
|
||||
addhandlers=True, killonclose=True,
|
||||
singlesession=False):
|
||||
|
||||
self._lock = threading.Lock()
|
||||
self._started = False
|
||||
self._stopped = False
|
||||
self._closed = False
|
||||
|
||||
# socket-related
|
||||
|
||||
self._sock = None # set when started
|
||||
self._server = None
|
||||
|
||||
# session-related
|
||||
|
||||
self._singlesession = singlesession
|
||||
|
||||
self._session = None
|
||||
self._numsessions = 0
|
||||
self._sessionlock = None
|
||||
|
||||
# proc-related
|
||||
|
||||
self._wait_for_user = wait_for_user
|
||||
self._killonclose = killonclose
|
||||
|
||||
self._exiting_via_atexit_handler = False
|
||||
|
||||
self._exithandlers = ExitHandlers()
|
||||
if addhandlers:
|
||||
self._install_exit_handlers()
|
||||
|
||||
@property
|
||||
def session(self):
|
||||
"""The current session."""
|
||||
return self._session
|
||||
|
||||
@contextlib.contextmanager
|
||||
def started(self):
|
||||
"""A context manager that starts the daemon and stops it for errors."""
|
||||
self.start()
|
||||
try:
|
||||
yield self
|
||||
except Exception:
|
||||
self._stop_quietly()
|
||||
raise
|
||||
|
||||
@contextlib.contextmanager
|
||||
def running(self):
|
||||
"""A context manager that starts the daemon.
|
||||
|
||||
If there's a failure then the daemon is stopped. It is also
|
||||
stopped at the end of the with block.
|
||||
"""
|
||||
self.start()
|
||||
try:
|
||||
yield self
|
||||
finally:
|
||||
self._stop_quietly()
|
||||
|
||||
def is_running(self):
|
||||
"""Return True if the daemon is running."""
|
||||
with self._lock:
|
||||
if self._closed:
|
||||
return False
|
||||
if self._sock is None:
|
||||
return False
|
||||
return self._started and not self._stopped
|
||||
|
||||
def start(self):
|
||||
"""Return the "socket" to use for pydevd after setting it up."""
|
||||
with self._lock:
|
||||
if self._closed:
|
||||
raise DaemonClosedError()
|
||||
if self._started:
|
||||
raise RuntimeError('already started')
|
||||
self._started = True
|
||||
|
||||
sock = self._start()
|
||||
self._sock = sock
|
||||
return sock
|
||||
|
||||
def start_server(self, addr, hidebadsessions=True):
|
||||
"""Return ("socket", next_session) with a new server socket."""
|
||||
|
||||
ptvsd.server.log.debug('Starting server daemon on {0!r}.', addr)
|
||||
|
||||
addr = Address.from_raw(addr)
|
||||
with self.started():
|
||||
assert self._sessionlock is None
|
||||
assert self.session is None
|
||||
self._server = create_server(addr.host, addr.port)
|
||||
host, port = self._server.getsockname()
|
||||
ptvsd.server.log.debug('Server socket created on {0!r}', addr)
|
||||
self._sessionlock = threading.Lock()
|
||||
sock = self._sock
|
||||
|
||||
def check_ready(**kwargs):
|
||||
self._check_ready_for_session(**kwargs)
|
||||
if self._server is None:
|
||||
raise DaemonStoppedError()
|
||||
|
||||
def next_session(timeout=None, **kwargs):
|
||||
server = self._server
|
||||
sessionlock = self._sessionlock
|
||||
check_ready(checksession=False)
|
||||
|
||||
ptvsd.server.log.debug('Getting next session...')
|
||||
sessionlock.acquire() # Released in _finish_session().
|
||||
ptvsd.server.log.debug('Session lock acquired.')
|
||||
# It may have closed or stopped while we waited.
|
||||
check_ready()
|
||||
|
||||
timeout = kwargs.pop('timeout', None)
|
||||
try:
|
||||
ptvsd.server.log.debug('Getting session socket...')
|
||||
client = connect(server, None, **kwargs)
|
||||
self._bind_session(client)
|
||||
ptvsd.server.log.debug('Starting session...')
|
||||
self._start_session_safely('ptvsd.server.Server', timeout=timeout)
|
||||
ptvsd.server.log.debug('Session started.')
|
||||
return self._session
|
||||
except Exception:
|
||||
ptvsd.server.log.exception(level=('debug' if hidebadsessions else 'error'))
|
||||
with ignore_errors():
|
||||
self._finish_session()
|
||||
if hidebadsessions:
|
||||
ptvsd.server.log.debug('Hiding bad session')
|
||||
return None
|
||||
self._stop_quietly()
|
||||
raise
|
||||
|
||||
if options.subprocess_notify:
|
||||
multiproc.notify_root(port)
|
||||
|
||||
return sock, next_session
|
||||
|
||||
def start_client(self, addr):
|
||||
"""Return ("socket", start_session) with a new client socket."""
|
||||
|
||||
ptvsd.server.log.debug('Starting client daemon on {0!r}.', addr)
|
||||
|
||||
addr = Address.from_raw(addr)
|
||||
self._singlesession = True
|
||||
with self.started():
|
||||
assert self.session is None
|
||||
client = create_client()
|
||||
connect(client, addr)
|
||||
sock = self._sock
|
||||
|
||||
def start_session(**kwargs):
|
||||
self._check_ready_for_session()
|
||||
if self._server is not None:
|
||||
raise RuntimeError('running as server')
|
||||
if self._numsessions:
|
||||
raise RuntimeError('session stopped')
|
||||
|
||||
try:
|
||||
self._bind_session(client)
|
||||
self._start_session_safely('ptvsd.server.Client', **kwargs)
|
||||
return self._session
|
||||
except Exception:
|
||||
self._stop_quietly()
|
||||
raise
|
||||
|
||||
return sock, start_session
|
||||
|
||||
def start_session(self, session, threadname, **kwargs):
|
||||
"""Start the debug session and remember it.
|
||||
|
||||
If "session" is a client socket then a session is created
|
||||
from it.
|
||||
"""
|
||||
|
||||
ptvsd.server.log.debug('Starting session.')
|
||||
|
||||
self._check_ready_for_session()
|
||||
if self._server is not None:
|
||||
raise RuntimeError('running as server')
|
||||
|
||||
self._bind_session(session)
|
||||
self._start_session_safely(threadname, **kwargs)
|
||||
return self.session
|
||||
|
||||
def close(self):
|
||||
"""Stop all loops and release all resources."""
|
||||
|
||||
ptvsd.server.log.debug('Stopping daemon.')
|
||||
|
||||
with self._lock:
|
||||
if self._closed:
|
||||
raise DaemonClosedError('already closed')
|
||||
self._closed = True
|
||||
|
||||
self._close()
|
||||
|
||||
# internal methods
|
||||
|
||||
def _check_ready_for_session(self, checksession=True):
|
||||
with self._lock:
|
||||
if self._closed:
|
||||
raise DaemonClosedError()
|
||||
if not self._started:
|
||||
raise DaemonStoppedError('never started')
|
||||
if self._stopped or self._sock is None:
|
||||
raise DaemonStoppedError()
|
||||
if checksession and self.session is not None:
|
||||
raise RuntimeError('session already started')
|
||||
|
||||
def _close(self):
|
||||
self._stop()
|
||||
|
||||
self._sock = None
|
||||
|
||||
def _stop(self):
|
||||
with self._lock:
|
||||
if self._stopped:
|
||||
return
|
||||
self._stopped = True
|
||||
|
||||
server = self._server
|
||||
self._server = None
|
||||
|
||||
with ignore_errors():
|
||||
self._finish_session()
|
||||
|
||||
self._sessionlock = None # TODO: Call self._clear_sessionlock?
|
||||
|
||||
# TODO: Close the server socket *before* finish the session?
|
||||
if server is not None:
|
||||
with ignore_errors():
|
||||
close_socket(server)
|
||||
|
||||
# TODO: Close self._sock *before* finishing the session?
|
||||
if self._sock is not None:
|
||||
with ignore_errors():
|
||||
close_socket(self._sock)
|
||||
|
||||
def _stop_quietly(self):
|
||||
with ignore_errors():
|
||||
self._stop()
|
||||
|
||||
def _handle_session_disconnecting(self, session):
|
||||
ptvsd.server.log.debug('Handling disconnecting session')
|
||||
if self._singlesession:
|
||||
if self._killonclose:
|
||||
with self._lock:
|
||||
if not self._exiting_via_atexit_handler:
|
||||
# Ensure the proc is exiting before closing
|
||||
# socket. Note that we kill the proc instead
|
||||
# of calling sys.exit(0).
|
||||
# Note that this will trigger either the atexit
|
||||
# handler or the signal handler.
|
||||
kill_current_proc()
|
||||
else:
|
||||
try:
|
||||
self.close()
|
||||
except DaemonClosedError:
|
||||
pass
|
||||
|
||||
def _handle_session_closing(self, session):
|
||||
ptvsd.server.log.debug('Handling closing session')
|
||||
|
||||
if self._singlesession:
|
||||
if self._killonclose:
|
||||
with self._lock:
|
||||
if not self._exiting_via_atexit_handler:
|
||||
# Ensure the proc is exiting before closing
|
||||
# socket. Note that we kill the proc instead
|
||||
# of calling sys.exit(0).
|
||||
# Note that this will trigger either the atexit
|
||||
# handler or the signal handler.
|
||||
kill_current_proc()
|
||||
else:
|
||||
try:
|
||||
self.close()
|
||||
except DaemonClosedError:
|
||||
pass
|
||||
else:
|
||||
self._finish_session()
|
||||
|
||||
def _clear_sessionlock(self, done=False):
|
||||
sessionlock = self._sessionlock
|
||||
if done:
|
||||
self._sessionlock = None
|
||||
if sessionlock is not None:
|
||||
try:
|
||||
sessionlock.release()
|
||||
except Exception: # TODO: Make it more specific?
|
||||
ptvsd.server.log.exception('Session lock not released', level='debug')
|
||||
else:
|
||||
ptvsd.server.log.debug('Session lock released')
|
||||
|
||||
# internal session-related methods
|
||||
|
||||
def _bind_session(self, session):
|
||||
session_not_bound.clear()
|
||||
# TODO: Pass notify_* to session.start() instead.
|
||||
session = self.SESSION.from_raw(
|
||||
session,
|
||||
notify_closing=self._handle_session_closing,
|
||||
notify_disconnecting=self._handle_session_disconnecting,
|
||||
ownsock=True,
|
||||
**self._session_kwargs() or {}
|
||||
)
|
||||
self._session = session
|
||||
self._numsessions += 1
|
||||
|
||||
def _start_session_safely(self, threadname, **kwargs):
|
||||
try:
|
||||
self._start_session(threadname, **kwargs)
|
||||
except Exception:
|
||||
ptvsd.server.log.exception()
|
||||
with ignore_errors():
|
||||
self._finish_session()
|
||||
raise
|
||||
|
||||
def _finish_session(self):
|
||||
self._numsessions -= 1
|
||||
session_not_bound.set()
|
||||
try:
|
||||
session = self._release_session()
|
||||
ptvsd.server.log.debug('Session stopped')
|
||||
finally:
|
||||
self._clear_sessionlock()
|
||||
|
||||
if self._singlesession:
|
||||
ptvsd.server.log.debug('Closing daemon after single session')
|
||||
try:
|
||||
self.close()
|
||||
except DaemonClosedError:
|
||||
pass
|
||||
return session
|
||||
|
||||
def _release_session(self):
|
||||
session = self.session
|
||||
if not self._singlesession:
|
||||
# TODO: This shouldn't happen if we are exiting?
|
||||
self._session = None
|
||||
|
||||
try:
|
||||
session.stop()
|
||||
except NotRunningError:
|
||||
pass
|
||||
try:
|
||||
session.close()
|
||||
except ClosedError:
|
||||
pass
|
||||
|
||||
return session
|
||||
|
||||
# internal proc-related methods
|
||||
|
||||
def _install_exit_handlers(self):
|
||||
"""Set the placeholder handlers."""
|
||||
self._exithandlers.install()
|
||||
|
||||
try:
|
||||
self._exithandlers.add_atexit_handler(self._handle_atexit)
|
||||
except ValueError:
|
||||
pass
|
||||
for signum in self._exithandlers.SIGNALS:
|
||||
try:
|
||||
self._exithandlers.add_signal_handler(signum,
|
||||
self._handle_signal)
|
||||
except ValueError:
|
||||
# Already added.
|
||||
pass
|
||||
except UnsupportedSignalError:
|
||||
# TODO: This shouldn't happen.
|
||||
pass
|
||||
|
||||
def _handle_atexit(self):
|
||||
ptvsd.server.log.debug('Handling atexit')
|
||||
with self._lock:
|
||||
self._exiting_via_atexit_handler = True
|
||||
session = self.session
|
||||
|
||||
if session is not None:
|
||||
lock = threading.Lock()
|
||||
lock.acquire()
|
||||
|
||||
def wait_debugger(timeout=None):
|
||||
lock_wait(lock, timeout)
|
||||
|
||||
def wait_exiting(cfg):
|
||||
if cfg:
|
||||
self._wait_for_user()
|
||||
lock.release()
|
||||
# TODO: Rely on self._stop_debugger().
|
||||
session.handle_debugger_stopped(wait_debugger)
|
||||
session.handle_exiting(self.exitcode, wait_exiting)
|
||||
|
||||
try:
|
||||
self.close()
|
||||
except DaemonClosedError:
|
||||
pass
|
||||
if session is not None:
|
||||
session.wait_until_stopped()
|
||||
|
||||
def _handle_signal(self, signum, frame):
|
||||
ptvsd.server.log.debug('Handling signal')
|
||||
try:
|
||||
self.close()
|
||||
except DaemonClosedError:
|
||||
pass
|
||||
if not self._exiting_via_atexit_handler:
|
||||
sys.exit(0)
|
||||
|
||||
# methods for subclasses to override
|
||||
|
||||
def _start(self):
|
||||
"""Return the debugger client socket after starting the daemon."""
|
||||
raise NotImplementedError
|
||||
|
||||
def _start_session(self, threadname, **kwargs):
|
||||
self.session.start(
|
||||
threadname,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def _session_kwargs(self):
|
||||
return None
|
||||
|
||||
|
||||
class Daemon(DaemonBase):
|
||||
"""The process-level manager for the VSC protocol debug adapter."""
|
||||
|
||||
SESSION = PyDevdDebugSession
|
||||
|
||||
def __init__(self, wait_for_user=_wait_for_user,
|
||||
notify_session_debugger_ready=None,
|
||||
**kwargs):
|
||||
super(Daemon, self).__init__(wait_for_user, **kwargs)
|
||||
|
||||
self._notify_session_debugger_ready = notify_session_debugger_ready
|
||||
|
||||
@property
|
||||
def pydevd(self):
|
||||
return self._sock
|
||||
|
||||
# internal methods
|
||||
|
||||
def _start(self):
|
||||
return wrapper.PydevdSocket(
|
||||
self._handle_pydevd_message,
|
||||
self._handle_pydevd_close,
|
||||
self._getpeername,
|
||||
self._getsockname,
|
||||
)
|
||||
|
||||
def _start_session(self, threadname, **kwargs):
|
||||
super(Daemon, self)._start_session(
|
||||
threadname,
|
||||
pydevd_notify=self.pydevd.pydevd_notify,
|
||||
pydevd_request=self.pydevd.pydevd_request,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def _session_kwargs(self):
|
||||
def debugger_ready(session):
|
||||
if self._notify_session_debugger_ready is not None:
|
||||
self._notify_session_debugger_ready(session)
|
||||
|
||||
return dict(
|
||||
notify_debugger_ready=debugger_ready,
|
||||
)
|
||||
|
||||
# internal methods for PyDevdSocket().
|
||||
|
||||
def _handle_pydevd_message(self, cmdid, seq, text):
|
||||
if self.session is None or self.session.closed:
|
||||
# TODO: Do more than ignore?
|
||||
return
|
||||
self.session.handle_pydevd_message(cmdid, seq, text)
|
||||
|
||||
def _handle_pydevd_close(self):
|
||||
try:
|
||||
self.close()
|
||||
except DaemonClosedError:
|
||||
pass
|
||||
|
||||
def _getpeername(self):
|
||||
if self.session is None or self.session.closed:
|
||||
raise NotImplementedError
|
||||
return self.session.socket.getpeername()
|
||||
|
||||
def _getsockname(self):
|
||||
if self.session is None or self.session.closed:
|
||||
raise NotImplementedError
|
||||
return self.session.socket.getsockname()
|
||||
|
|
@ -1,118 +0,0 @@
|
|||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See LICENSE in the project root
|
||||
# for license information.
|
||||
|
||||
import atexit
|
||||
import os
|
||||
import platform
|
||||
import signal
|
||||
|
||||
|
||||
class AlreadyInstalledError(RuntimeError):
|
||||
"""Exit handlers were already installed."""
|
||||
|
||||
|
||||
class UnsupportedSignalError(RuntimeError):
|
||||
"""A signal is not supported."""
|
||||
|
||||
|
||||
def kill_current_proc(signum=signal.SIGTERM):
|
||||
"""Kill the current process.
|
||||
|
||||
Note that this directly kills the process (with SIGTERM, by default)
|
||||
rather than using sys.exit().
|
||||
"""
|
||||
os.kill(os.getpid(), signum)
|
||||
|
||||
|
||||
class ExitHandlers(object):
|
||||
"""Manages signal and atexit handlers."""
|
||||
|
||||
if platform.system() == 'Windows' or not hasattr(signal, 'SIGHUP'):
|
||||
# TODO: Windows *does* support these signals:
|
||||
# SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERM, SIGBREAK
|
||||
SIGNALS = []
|
||||
else:
|
||||
SIGNALS = [
|
||||
signal.SIGHUP,
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
self._signal_handlers = {sig: []
|
||||
for sig in self.SIGNALS}
|
||||
self._atexit_handlers = []
|
||||
self._installed = False
|
||||
|
||||
@property
|
||||
def supported_signals(self):
|
||||
return set(self.SIGNALS)
|
||||
|
||||
@property
|
||||
def installed(self):
|
||||
return self._installed
|
||||
|
||||
def install(self):
|
||||
"""Set the parent handlers.
|
||||
|
||||
This must be called in the main thread.
|
||||
"""
|
||||
if self._installed:
|
||||
raise AlreadyInstalledError('exit handlers already installed')
|
||||
self._installed = True
|
||||
self._install_signal_handler()
|
||||
self._install_atexit_handler()
|
||||
|
||||
# TODO: Add uninstall()?
|
||||
|
||||
def add_atexit_handler(self, handle_atexit, nodupe=True):
|
||||
"""Add an atexit handler to the list managed here."""
|
||||
if nodupe and handle_atexit in self._atexit_handlers:
|
||||
raise ValueError('atexit handler alraedy added')
|
||||
self._atexit_handlers.append(handle_atexit)
|
||||
|
||||
def add_signal_handler(self, signum, handle_signal, nodupe=True,
|
||||
ignoreunsupported=False):
|
||||
"""Add a signal handler to the list managed here."""
|
||||
# TODO: The initialization of self.SIGNALS should make this
|
||||
# special-casing unnecessary.
|
||||
if platform.system() == 'Windows':
|
||||
return
|
||||
|
||||
try:
|
||||
handlers = self._signal_handlers[signum]
|
||||
except KeyError:
|
||||
if ignoreunsupported:
|
||||
return
|
||||
raise UnsupportedSignalError(signum)
|
||||
if nodupe and handle_signal in handlers:
|
||||
raise ValueError('signal handler alraedy added')
|
||||
handlers.append(handle_signal)
|
||||
|
||||
# internal methods
|
||||
|
||||
def _install_signal_handler(self):
|
||||
# TODO: The initialization of self.SIGNALS should make this
|
||||
# special-casing unnecessary.
|
||||
if platform.system() == 'Windows':
|
||||
return
|
||||
|
||||
orig = {}
|
||||
try:
|
||||
for sig in self._signal_handlers:
|
||||
# TODO: Skip or fail if signal.getsignal() returns None?
|
||||
orig[sig] = signal.signal(sig, self._signal_handler)
|
||||
except ValueError:
|
||||
# Wasn't called in main thread!
|
||||
raise
|
||||
|
||||
def _signal_handler(self, signum, frame):
|
||||
for handle_signal in self._signal_handlers.get(signum, ()):
|
||||
handle_signal(signum, frame)
|
||||
|
||||
def _install_atexit_handler(self):
|
||||
self._atexit_handlers = []
|
||||
atexit.register(self._atexit_handler)
|
||||
|
||||
def _atexit_handler(self):
|
||||
for handle_atexit in self._atexit_handlers:
|
||||
handle_atexit()
|
||||
|
|
@ -1,202 +0,0 @@
|
|||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See LICENSE in the project root
|
||||
# for license information.
|
||||
|
||||
from __future__ import print_function, with_statement, absolute_import
|
||||
|
||||
import sys
|
||||
import threading
|
||||
import traceback
|
||||
|
||||
import ptvsd.server.log
|
||||
from ptvsd.server.reraise import reraise
|
||||
|
||||
|
||||
class Future(object):
|
||||
# TODO: docstring
|
||||
|
||||
def __init__(self, loop):
|
||||
self._lock = threading.Lock()
|
||||
self._loop = loop
|
||||
self._done = None
|
||||
self._observed = False
|
||||
self._done_callbacks = []
|
||||
self._exc_info = None
|
||||
self._handling = ptvsd.server.log.current_handler()
|
||||
|
||||
# It's expensive, so only capture the origin if logging is enabled.
|
||||
if ptvsd.server.log.file:
|
||||
self._origin = traceback.extract_stack()
|
||||
else:
|
||||
self._origin = None
|
||||
|
||||
def __del__(self):
|
||||
# Do not run any checks if Python is shutting down.
|
||||
if not sys or not self._lock:
|
||||
return
|
||||
|
||||
with self._lock:
|
||||
assert self._done or self._done is None
|
||||
exc_info = self._exc_info
|
||||
if exc_info and not self._observed:
|
||||
msg = 'Unobserved failed future'
|
||||
origin = self._origin
|
||||
if origin:
|
||||
origin = '\n'.join(traceback.format_list(origin))
|
||||
msg += ' originating from:\n\n{origin}\n\n'
|
||||
ptvsd.server.log.exception(msg, origin=origin, exc_info=exc_info)
|
||||
traceback.print_exception(*exc_info, file=sys.__stderr__)
|
||||
|
||||
def result(self):
|
||||
# TODO: docstring
|
||||
with self._lock:
|
||||
self._observed = True
|
||||
if self._exc_info:
|
||||
reraise(self._exc_info)
|
||||
return self._result
|
||||
|
||||
def exc_info(self):
|
||||
# TODO: docstring
|
||||
with self._lock:
|
||||
self._observed = True
|
||||
return self._exc_info
|
||||
|
||||
def set_result(self, result):
|
||||
# TODO: docstring
|
||||
with self._lock:
|
||||
self._result = result
|
||||
self._exc_info = None
|
||||
self._done = True
|
||||
callbacks = list(self._done_callbacks)
|
||||
|
||||
def invoke_callbacks():
|
||||
for cb in callbacks:
|
||||
cb(self)
|
||||
|
||||
self._loop.call_soon(invoke_callbacks)
|
||||
|
||||
def set_exc_info(self, exc_info):
|
||||
# TODO: docstring
|
||||
with self._lock:
|
||||
self._exc_info = exc_info
|
||||
self._done = True
|
||||
callbacks = list(self._done_callbacks)
|
||||
|
||||
def invoke_callbacks():
|
||||
for cb in callbacks:
|
||||
cb(self)
|
||||
|
||||
self._loop.call_soon(invoke_callbacks)
|
||||
|
||||
def add_done_callback(self, callback):
|
||||
# TODO: docstring
|
||||
with self._lock:
|
||||
done = self._done
|
||||
self._done_callbacks.append(callback)
|
||||
if done:
|
||||
self._loop.call_soon(lambda: callback(self))
|
||||
|
||||
def remove_done_callback(self, callback):
|
||||
# TODO: docstring
|
||||
with self._lock:
|
||||
self._done_callbacks.remove(callback)
|
||||
|
||||
|
||||
class EventLoop(object):
|
||||
# TODO: docstring
|
||||
|
||||
def __init__(self):
|
||||
self._queue = []
|
||||
self._lock = threading.Lock()
|
||||
self._event = threading.Event()
|
||||
self._event.set()
|
||||
|
||||
self._stop = False
|
||||
|
||||
def create_future(self):
|
||||
return Future(self)
|
||||
|
||||
def run_forever(self):
|
||||
try:
|
||||
while not self._stop:
|
||||
if not self._event.wait(timeout=0.1):
|
||||
continue
|
||||
with self._lock:
|
||||
queue = self._queue
|
||||
self._queue = []
|
||||
self._event.clear()
|
||||
for (f, args) in queue:
|
||||
f(*args)
|
||||
except:
|
||||
if sys is None or traceback is None:
|
||||
# Errors during shutdown are expected as this is a daemon thread,
|
||||
# so just silence it. We can't even log it at this point.
|
||||
pass
|
||||
else:
|
||||
# Try to log it, but guard against the possibility of shutdown
|
||||
# in the middle of it.
|
||||
try:
|
||||
ptvsd.server.log.exception('Exception escaped to event loop')
|
||||
except:
|
||||
pass
|
||||
|
||||
def stop(self):
|
||||
self._stop = True
|
||||
|
||||
def call_soon(self, f, *args):
|
||||
with self._lock:
|
||||
self._queue.append((f, args))
|
||||
self._event.set()
|
||||
|
||||
def call_soon_threadsafe(self, f, *args):
|
||||
return self.call_soon(f, *args)
|
||||
|
||||
|
||||
class Result(object):
|
||||
# TODO: docstring
|
||||
|
||||
__slots__ = ['value']
|
||||
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
|
||||
def wrap_async(f):
|
||||
def g(self, loop, *args, **kwargs):
|
||||
it = f(self, *args, **kwargs)
|
||||
result = Future(loop)
|
||||
if it is None:
|
||||
result.set_result(None)
|
||||
return result
|
||||
|
||||
async_handler = ptvsd.server.log.current_handler()
|
||||
|
||||
def resume(fut):
|
||||
try:
|
||||
if fut is None:
|
||||
x = next(it)
|
||||
else:
|
||||
exc_info = fut.exc_info()
|
||||
if exc_info:
|
||||
x = it.throw(*exc_info)
|
||||
else:
|
||||
x = it.send(fut.result())
|
||||
except AssertionError:
|
||||
raise
|
||||
except StopIteration:
|
||||
result.set_result(None)
|
||||
except:
|
||||
result.set_exc_info(sys.exc_info())
|
||||
else:
|
||||
if isinstance(x, Result):
|
||||
result.set_result(x.value)
|
||||
else:
|
||||
def callback(fut):
|
||||
with ptvsd.server.log.handling(async_handler):
|
||||
resume(fut)
|
||||
x.add_done_callback(callback)
|
||||
|
||||
resume(None)
|
||||
return result
|
||||
|
||||
return g
|
||||
|
|
@ -1,340 +0,0 @@
|
|||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See LICENSE in the project root
|
||||
# for license information.
|
||||
|
||||
from __future__ import print_function, with_statement, absolute_import
|
||||
|
||||
import errno
|
||||
import itertools
|
||||
import json
|
||||
import os
|
||||
import os.path
|
||||
from socket import create_connection
|
||||
import sys
|
||||
import time
|
||||
import threading
|
||||
import traceback
|
||||
|
||||
import ptvsd.server.log
|
||||
from ptvsd.server.socket import TimeoutError, convert_eof
|
||||
|
||||
|
||||
SKIP_TB_PREFIXES = [
|
||||
os.path.normcase(
|
||||
os.path.dirname(
|
||||
os.path.abspath(__file__))),
|
||||
]
|
||||
|
||||
TIMEOUT = os.environ.get('PTVSD_SOCKET_TIMEOUT')
|
||||
if TIMEOUT:
|
||||
TIMEOUT = float(TIMEOUT)
|
||||
|
||||
|
||||
if sys.version_info[0] >= 3:
|
||||
from encodings import ascii
|
||||
|
||||
def to_bytes(cmd_str):
|
||||
return ascii.Codec.encode(cmd_str)[0]
|
||||
else:
|
||||
def to_bytes(cmd_str):
|
||||
return cmd_str
|
||||
|
||||
class BrokenPipeError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def _str_or_call(m):
|
||||
# TODO: Use callable() here.
|
||||
try:
|
||||
call = m.__call__
|
||||
except AttributeError:
|
||||
return str(m)
|
||||
else:
|
||||
return str(call())
|
||||
|
||||
|
||||
class InvalidHeaderError(Exception):
|
||||
# TODO: docstring
|
||||
pass
|
||||
|
||||
|
||||
class InvalidContentError(Exception):
|
||||
# TODO: docstring
|
||||
pass
|
||||
|
||||
|
||||
class SocketIO(object):
|
||||
# TODO: docstring
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
port = kwargs.pop('port', None)
|
||||
socket = kwargs.pop('socket', None)
|
||||
own_socket = kwargs.pop('own_socket', True)
|
||||
if socket is None:
|
||||
if port is None:
|
||||
raise ValueError(
|
||||
"A 'port' or a 'socket' must be passed to SocketIO initializer as a keyword argument.") # noqa
|
||||
addr = ('127.0.0.1', port)
|
||||
socket = create_connection(addr)
|
||||
own_socket = True
|
||||
super(SocketIO, self).__init__(*args, **kwargs)
|
||||
|
||||
self.__buffer = to_bytes('')
|
||||
self.__port = port
|
||||
self.__socket = socket
|
||||
self.__own_socket = own_socket
|
||||
|
||||
def _send(self, **payload):
|
||||
ptvsd.server.log.debug('IDE <-- {0!j}', payload)
|
||||
content = json.dumps(payload).encode('utf-8')
|
||||
headers = ('Content-Length: {}\r\n\r\n'.format(len(content))
|
||||
).encode('ascii')
|
||||
|
||||
try:
|
||||
self.__socket.send(headers)
|
||||
self.__socket.send(content)
|
||||
except BrokenPipeError:
|
||||
pass
|
||||
except OSError as exc:
|
||||
if exc.errno in (errno.EPIPE, errno.ESHUTDOWN): # BrokenPipeError
|
||||
pass
|
||||
elif exc.errno not in (errno.ENOTCONN, errno.EBADF):
|
||||
raise
|
||||
|
||||
def _buffered_read_line_as_ascii(self):
|
||||
"""Return the next line from the buffer as a string.
|
||||
|
||||
Reads bytes until it encounters newline chars, and returns the bytes
|
||||
ascii decoded, newline chars are excluded from the return value.
|
||||
Blocks until: newline chars are read OR socket is closed.
|
||||
"""
|
||||
newline = '\r\n'.encode('ascii')
|
||||
while newline not in self.__buffer:
|
||||
temp = self.__socket.recv(1024)
|
||||
if not temp:
|
||||
break
|
||||
self.__buffer += temp
|
||||
|
||||
if not self.__buffer:
|
||||
return None
|
||||
|
||||
try:
|
||||
index = self.__buffer.index(newline)
|
||||
except ValueError:
|
||||
raise InvalidHeaderError('Header line not terminated')
|
||||
|
||||
line = self.__buffer[:index]
|
||||
self.__buffer = self.__buffer[index+len(newline):]
|
||||
return line.decode('ascii', 'replace')
|
||||
|
||||
def _buffered_read_as_utf8(self, length):
|
||||
# TODO: docstring
|
||||
while len(self.__buffer) < length:
|
||||
temp = self.__socket.recv(1024)
|
||||
if not temp:
|
||||
break
|
||||
self.__buffer += temp
|
||||
|
||||
if len(self.__buffer) < length:
|
||||
raise InvalidContentError(
|
||||
'Expected to read {} bytes of content, but only read {} bytes.'.format(length, len(self.__buffer))) # noqa
|
||||
|
||||
content = self.__buffer[:length]
|
||||
self.__buffer = self.__buffer[length:]
|
||||
return content.decode('utf-8', 'replace')
|
||||
|
||||
def _wait_for_message(self):
|
||||
# TODO: docstring
|
||||
# base protocol defined at:
|
||||
# https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#base-protocol
|
||||
|
||||
# read all headers, ascii encoded separated by '\r\n'
|
||||
# end of headers is indicated by an empty line
|
||||
headers = {}
|
||||
line = self._buffered_read_line_as_ascii()
|
||||
while line:
|
||||
parts = line.split(':')
|
||||
if len(parts) == 2:
|
||||
headers[parts[0]] = parts[1]
|
||||
else:
|
||||
raise InvalidHeaderError(
|
||||
"Malformed header, expected 'name: value'\n" + line)
|
||||
line = self._buffered_read_line_as_ascii()
|
||||
|
||||
# end of stream
|
||||
if not line and not headers:
|
||||
return
|
||||
|
||||
# validate headers
|
||||
try:
|
||||
length_text = headers['Content-Length']
|
||||
try:
|
||||
length = int(length_text)
|
||||
except ValueError:
|
||||
raise InvalidHeaderError(
|
||||
'Invalid Content-Length: ' + length_text)
|
||||
except NameError:
|
||||
raise InvalidHeaderError('Content-Length not specified in headers')
|
||||
except KeyError:
|
||||
raise InvalidHeaderError('Content-Length not specified in headers')
|
||||
|
||||
if length < 0 or length > 2147483647:
|
||||
raise InvalidHeaderError('Invalid Content-Length: ' + length_text)
|
||||
|
||||
# read content, utf-8 encoded
|
||||
content = self._buffered_read_as_utf8(length)
|
||||
try:
|
||||
msg = json.loads(content)
|
||||
ptvsd.server.log.debug('IDE --> {0!j}', msg)
|
||||
self._receive_message(msg)
|
||||
except ValueError:
|
||||
ptvsd.server.log.exception('IDE --> {0}', content)
|
||||
raise InvalidContentError('Error deserializing message content.')
|
||||
except json.decoder.JSONDecodeError:
|
||||
ptvsd.server.log.exception('IDE --> {0}', content)
|
||||
raise InvalidContentError('Error deserializing message content.')
|
||||
|
||||
def _close(self):
|
||||
# TODO: docstring
|
||||
if self.__own_socket:
|
||||
self.__socket.close()
|
||||
|
||||
|
||||
class IpcChannel(object):
|
||||
# TODO: docstring
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
timeout = kwargs.pop('timeout', None)
|
||||
if timeout is None:
|
||||
timeout = TIMEOUT
|
||||
super(IpcChannel, self).__init__(*args, **kwargs)
|
||||
# This class is meant to be last in the list of base classes
|
||||
# Don't call super because object's __init__ doesn't take arguments
|
||||
|
||||
self.__seq = itertools.count()
|
||||
self.__exit = False
|
||||
self.__lock = threading.Lock()
|
||||
self.__message = []
|
||||
self._timeout = timeout
|
||||
self._fail_after = None
|
||||
|
||||
def close(self):
|
||||
# TODO: docstring
|
||||
self._close()
|
||||
|
||||
def send_event(self, _name, **kwargs):
|
||||
# TODO: docstring
|
||||
with self.__lock:
|
||||
self._send(
|
||||
type='event',
|
||||
seq=next(self.__seq),
|
||||
event=_name,
|
||||
body=kwargs,
|
||||
)
|
||||
|
||||
def send_response(self, request, success=True, message=None, **kwargs):
|
||||
# TODO: docstring
|
||||
with self.__lock:
|
||||
self._send(
|
||||
type='response',
|
||||
seq=next(self.__seq),
|
||||
request_seq=int(request.get('seq', 0)),
|
||||
success=success,
|
||||
command=request.get('command', ''),
|
||||
message=message or '',
|
||||
body=kwargs,
|
||||
)
|
||||
|
||||
def set_exit(self):
|
||||
# TODO: docstring
|
||||
self.__exit = True
|
||||
|
||||
def process_messages(self):
|
||||
# TODO: docstring
|
||||
if self._timeout is not None:
|
||||
self._fail_after = time.time() + self._timeout
|
||||
while not self.__exit:
|
||||
try:
|
||||
self.process_one_message()
|
||||
except (AssertionError, EOFError):
|
||||
raise
|
||||
except Exception:
|
||||
ptvsd.server.log.exception(level=('debug' if self.__exit else 'error'))
|
||||
if not self.__exit:
|
||||
raise
|
||||
|
||||
def process_one_message(self):
|
||||
# TODO: docstring
|
||||
try:
|
||||
msg = self.__message.pop(0)
|
||||
except IndexError:
|
||||
with convert_eof():
|
||||
self._wait_for_message()
|
||||
try:
|
||||
msg = self.__message.pop(0)
|
||||
except IndexError:
|
||||
# No messages received.
|
||||
if self._fail_after is not None:
|
||||
if time.time() < self._fail_after:
|
||||
raise TimeoutError('connection closed?')
|
||||
raise EOFError('no more messages')
|
||||
if self._fail_after is not None:
|
||||
self._fail_after = time.time() + self._timeout
|
||||
|
||||
what = msg.copy()
|
||||
what.pop('arguments', None)
|
||||
what.pop('body', None)
|
||||
|
||||
with ptvsd.server.log.handling(what):
|
||||
try:
|
||||
if msg['type'] == 'request':
|
||||
self.on_request(msg)
|
||||
elif msg['type'] == 'response':
|
||||
self.on_response(msg)
|
||||
elif msg['type'] == 'event':
|
||||
self.on_event(msg)
|
||||
else:
|
||||
self.on_invalid_request(msg, {})
|
||||
except AssertionError:
|
||||
ptvsd.server.log.exception()
|
||||
raise
|
||||
except Exception:
|
||||
ptvsd.server.log.exception()
|
||||
|
||||
def on_request(self, request):
|
||||
# TODO: docstring
|
||||
assert request.get('type', '') == 'request', \
|
||||
"Only handle 'request' messages in on_request"
|
||||
|
||||
cmd = request.get('command', '')
|
||||
args = request.get('arguments', {})
|
||||
target = getattr(self, 'on_' + cmd, self.on_invalid_request)
|
||||
|
||||
try:
|
||||
target(request, args)
|
||||
except AssertionError:
|
||||
raise
|
||||
except Exception:
|
||||
self.send_response(
|
||||
request,
|
||||
success=False,
|
||||
message=traceback.format_exc(),
|
||||
)
|
||||
|
||||
def on_response(self, msg):
|
||||
# TODO: docstring
|
||||
# this class is only used for server side only for now
|
||||
raise NotImplementedError
|
||||
|
||||
def on_event(self, msg):
|
||||
# TODO: docstring
|
||||
# this class is only used for server side only for now
|
||||
raise NotImplementedError
|
||||
|
||||
def on_invalid_request(self, request, args):
|
||||
# TODO: docstring
|
||||
self.send_response(request, success=False, message='Unknown command')
|
||||
|
||||
def _receive_message(self, message):
|
||||
with self.__lock:
|
||||
self.__message.append(message)
|
||||
|
|
@ -2,14 +2,13 @@
|
|||
# Licensed under the MIT License. See LICENSE in the project root
|
||||
# for license information.
|
||||
|
||||
from __future__ import print_function, with_statement, absolute_import
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
import atexit
|
||||
import itertools
|
||||
import os
|
||||
import re
|
||||
import signal
|
||||
import socket
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
|
|
@ -19,12 +18,8 @@ try:
|
|||
except ImportError:
|
||||
import Queue as queue
|
||||
|
||||
import ptvsd.server.log
|
||||
from ptvsd.server import options
|
||||
from ptvsd.server.socket import create_server, create_client
|
||||
from ptvsd.server.messaging import JsonIOStream, JsonMessageChannel
|
||||
from ptvsd.server._util import new_hidden_thread
|
||||
|
||||
from ptvsd.common import log, messaging, options as common_opts, socket, util
|
||||
from ptvsd.server import options as server_opts
|
||||
from _pydev_bundle import pydev_monkey
|
||||
from _pydevd_bundle.pydevd_comm import get_global_debugger
|
||||
|
||||
|
|
@ -73,24 +68,24 @@ def listen_for_subprocesses():
|
|||
global subprocess_listener_socket
|
||||
assert subprocess_listener_socket is None
|
||||
|
||||
subprocess_listener_socket = create_server('localhost', 0)
|
||||
ptvsd.server.log.debug(
|
||||
subprocess_listener_socket = socket.create_server('localhost', 0)
|
||||
log.debug(
|
||||
'Listening for subprocess notifications on port {0}.',
|
||||
subprocess_listener_port())
|
||||
|
||||
atexit.register(stop_listening_for_subprocesses)
|
||||
atexit.register(kill_subprocesses)
|
||||
new_hidden_thread('SubprocessListener', _subprocess_listener).start()
|
||||
util.new_hidden_thread('SubprocessListener', _subprocess_listener).start()
|
||||
|
||||
|
||||
def stop_listening_for_subprocesses():
|
||||
ptvsd.server.log.debug('Stopping listening for subprocess notifications.')
|
||||
log.debug('Stopping listening for subprocess notifications.')
|
||||
|
||||
global subprocess_listener_socket
|
||||
if subprocess_listener_socket is None:
|
||||
return
|
||||
try:
|
||||
subprocess_listener_socket.shutdown(socket.SHUT_RDWR)
|
||||
socket.shut_down(subprocess_listener_socket)
|
||||
except Exception:
|
||||
pass
|
||||
subprocess_listener_socket = None
|
||||
|
|
@ -100,16 +95,16 @@ def kill_subprocesses():
|
|||
with subprocess_lock:
|
||||
pids = list(subprocesses.keys())
|
||||
|
||||
ptvsd.server.log.debug('Killing remaining subprocesses: PID={0}', pids)
|
||||
log.debug('Killing remaining subprocesses: PID={0}', pids)
|
||||
|
||||
for pid in pids:
|
||||
ptvsd.server.log.debug('Killing subprocess with PID={0}.', pid)
|
||||
log.debug('Killing subprocess with PID={0}.', pid)
|
||||
with subprocess_lock:
|
||||
subprocesses.pop(pid, None)
|
||||
try:
|
||||
os.kill(pid, signal.SIGTERM)
|
||||
except Exception:
|
||||
ptvsd.server.log.exception('Failed to kill process with PID={0}.', pid, level='debug')
|
||||
log.exception('Failed to kill process with PID={0}.', pid, level='debug')
|
||||
|
||||
|
||||
def subprocess_listener_port():
|
||||
|
|
@ -129,9 +124,9 @@ def _subprocess_listener():
|
|||
|
||||
n = next(counter)
|
||||
name = 'subprocess-{}'.format(n)
|
||||
ptvsd.server.log.debug('Accepted incoming connection from {0}', name)
|
||||
log.debug('Accepted incoming connection from {0}', name)
|
||||
|
||||
stream = JsonIOStream.from_socket(sock, name=name)
|
||||
stream = messaging.JsonIOStream.from_socket(sock, name=name)
|
||||
_handle_subprocess(n, stream)
|
||||
|
||||
|
||||
|
|
@ -153,7 +148,7 @@ def _handle_subprocess(n, stream):
|
|||
with subprocess_lock:
|
||||
subprocesses[self._pid] = channel
|
||||
|
||||
ptvsd.server.log.debug(
|
||||
log.debug(
|
||||
'Subprocess {0} (PID={1}) registered, notifying IDE.',
|
||||
stream.name,
|
||||
self._pid)
|
||||
|
|
@ -164,24 +159,24 @@ def _handle_subprocess(n, stream):
|
|||
return response
|
||||
|
||||
def disconnect(self):
|
||||
ptvsd.server.log.debug('Subprocess {0} disconnected, presumed to have terminated.', self._pid)
|
||||
log.debug('Subprocess {0} disconnected, presumed to have terminated.', self._pid)
|
||||
if self._pid is not None:
|
||||
with subprocess_lock:
|
||||
subprocesses.pop(self._pid, None)
|
||||
|
||||
name = 'subprocess-%d' % n
|
||||
channel = JsonMessageChannel(stream, Handlers(), name)
|
||||
channel = messaging.JsonMessageChannel(stream, Handlers(), name)
|
||||
channel.start()
|
||||
|
||||
|
||||
def notify_root(port):
|
||||
assert options.subprocess_of
|
||||
assert server_opts.subprocess_of
|
||||
|
||||
ptvsd.server.log.debug('Subprocess (PID={0}) notifying root process at port {1}', os.getpid(), options.subprocess_notify)
|
||||
conn = create_client()
|
||||
conn.connect(('localhost', options.subprocess_notify))
|
||||
stream = JsonIOStream.from_socket(conn, 'root-process')
|
||||
channel = JsonMessageChannel(stream)
|
||||
log.debug('Subprocess (PID={0}) notifying root process at port {1}', os.getpid(), server_opts.subprocess_notify)
|
||||
conn = socket.create_client()
|
||||
conn.connect(('localhost', server_opts.subprocess_notify))
|
||||
stream = messaging.JsonIOStream.from_socket(conn, 'root-process')
|
||||
channel = messaging.JsonMessageChannel(stream)
|
||||
channel.start()
|
||||
|
||||
# Send the notification about ourselves to root, and wait for it to tell us
|
||||
|
|
@ -192,7 +187,7 @@ def notify_root(port):
|
|||
# in that case, just exit immediately.
|
||||
|
||||
request = channel.send_request('ptvsd_subprocess', {
|
||||
'parentProcessId': options.subprocess_of,
|
||||
'parentProcessId': server_opts.subprocess_of,
|
||||
'processId': os.getpid(),
|
||||
'port': port,
|
||||
})
|
||||
|
|
@ -200,7 +195,7 @@ def notify_root(port):
|
|||
try:
|
||||
response = request.wait_for_response()
|
||||
except Exception:
|
||||
ptvsd.server.log.exception('Failed to send subprocess notification; exiting')
|
||||
log.exception('Failed to send subprocess notification; exiting')
|
||||
sys.exit(0)
|
||||
|
||||
# Keep the channel open until we exit - root process uses open channels to keep
|
||||
|
|
@ -208,12 +203,17 @@ def notify_root(port):
|
|||
atexit.register(lambda: channel.close())
|
||||
|
||||
if not response['incomingConnection']:
|
||||
ptvsd.server.log.debug('No IDE connection is expected for this subprocess; unpausing.')
|
||||
debugger = get_global_debugger()
|
||||
while debugger is None:
|
||||
time.sleep(0.1)
|
||||
debugger = get_global_debugger()
|
||||
debugger.ready_to_run = True
|
||||
log.debug('No IDE connection is expected for this subprocess; unpausing.')
|
||||
|
||||
# TODO: The code here exists to cancel any wait for attach in an indirect way. We need a cleaner
|
||||
# way to cancel wait_for_attach. Ideally, a cancellable wait_for_attach, which ensures that it
|
||||
# does not mess up the pydevd internal debugger states.
|
||||
|
||||
# debugger = get_global_debugger()
|
||||
# while debugger is None:
|
||||
# time.sleep(0.1)
|
||||
# debugger = get_global_debugger()
|
||||
# debugger.ready_to_run = True
|
||||
|
||||
|
||||
def patch_args(args):
|
||||
|
|
@ -228,11 +228,11 @@ def patch_args(args):
|
|||
python -R -Q warn .../ptvsd/__main__.py --host localhost --port 0 ... -m app
|
||||
"""
|
||||
|
||||
if not options.multiprocess:
|
||||
if not server_opts.multiprocess:
|
||||
return args
|
||||
|
||||
args = list(args)
|
||||
ptvsd.server.log.debug('Patching subprocess command line: {0!r}', args)
|
||||
log.debug('Patching subprocess command line: {0!r}', args)
|
||||
|
||||
# First, let's find the target of the invocation. This is one of:
|
||||
#
|
||||
|
|
@ -300,18 +300,18 @@ def patch_args(args):
|
|||
from ptvsd import __main__
|
||||
ptvsd_args = [
|
||||
__main__.__file__,
|
||||
'--host', options.host,
|
||||
'--host', server_opts.host,
|
||||
'--port', '0',
|
||||
'--wait',
|
||||
'--multiprocess',
|
||||
'--subprocess-of', str(os.getpid()),
|
||||
'--subprocess-notify', str(options.subprocess_notify or subprocess_listener_port()),
|
||||
'--subprocess-notify', str(server_opts.subprocess_notify or subprocess_listener_port()),
|
||||
]
|
||||
if ptvsd.common.options.log_dir:
|
||||
ptvsd_args += ['--log-dir', ptvsd.common.options.log_dir]
|
||||
if common_opts.log_dir:
|
||||
ptvsd_args += ['--log-dir', common_opts.log_dir]
|
||||
args[i:i] = ptvsd_args
|
||||
|
||||
ptvsd.server.log.debug('Patched subprocess command line: {0!r}', args)
|
||||
log.debug('Patched subprocess command line: {0!r}', args)
|
||||
return args
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
# Licensed under the MIT License. See LICENSE in the project root
|
||||
# for license information.
|
||||
|
||||
from __future__ import print_function, with_statement, absolute_import
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
|
||||
"""ptvsd command-line options that need to be globally available.
|
||||
|
|
@ -28,7 +28,7 @@ If target_kind is 'code', then target is the code to run.
|
|||
If target_kind is 'pid', then target is the process ID to attach to.
|
||||
"""
|
||||
|
||||
host = 'localhost'
|
||||
host = '127.0.0.1'
|
||||
"""Name or IP address of the network interface used by ptvsd.server. If runing in server
|
||||
mode, this is the interface on which it listens for incoming connections. If running
|
||||
in client mode, this is the interface to which it connects.
|
||||
|
|
|
|||
|
|
@ -1,156 +0,0 @@
|
|||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See LICENSE in the project root
|
||||
# for license information.
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
import pydevd
|
||||
from _pydev_bundle import pydev_monkey
|
||||
from _pydevd_bundle import pydevd_comm
|
||||
|
||||
import ptvsd
|
||||
import ptvsd.server.log
|
||||
from ptvsd.server import multiproc
|
||||
from ptvsd.server.socket import Address
|
||||
from ptvsd.server.daemon import Daemon, DaemonStoppedError, DaemonClosedError
|
||||
from ptvsd.server._util import new_hidden_thread
|
||||
from ptvsd.server import options
|
||||
|
||||
|
||||
@ptvsd.server.log.escaped_exceptions
|
||||
def start_server(daemon, host, port, **kwargs):
|
||||
"""Return a socket to a (new) local pydevd-handling daemon.
|
||||
|
||||
The daemon supports the pydevd client wire protocol, sending
|
||||
requests and handling responses (and events).
|
||||
|
||||
This is a replacement for _pydevd_bundle.pydevd_comm.start_server.
|
||||
"""
|
||||
sock, next_session = daemon.start_server((host, port))
|
||||
|
||||
def handle_next():
|
||||
try:
|
||||
ptvsd.server.log.debug('Waiting for session...')
|
||||
session = next_session(**kwargs)
|
||||
ptvsd.server.log.debug('Got session')
|
||||
return session
|
||||
except (DaemonClosedError, DaemonStoppedError):
|
||||
# Typically won't happen.
|
||||
ptvsd.server.log.exception('Daemon stopped while waiting for session', level='debug')
|
||||
raise
|
||||
except Exception:
|
||||
ptvsd.server.log.exception()
|
||||
return None
|
||||
|
||||
def serve_forever():
|
||||
ptvsd.server.log.debug('Waiting for initial connection...')
|
||||
handle_next()
|
||||
while True:
|
||||
ptvsd.server.log.debug('Waiting for next connection...')
|
||||
try:
|
||||
handle_next()
|
||||
except (DaemonClosedError, DaemonStoppedError):
|
||||
break
|
||||
ptvsd.server.log.debug('Done serving')
|
||||
|
||||
t = new_hidden_thread(
|
||||
target=serve_forever,
|
||||
name='sessions',
|
||||
)
|
||||
t.start()
|
||||
return sock
|
||||
|
||||
|
||||
@ptvsd.server.log.escaped_exceptions
|
||||
def start_client(daemon, host, port, **kwargs):
|
||||
"""Return a socket to an existing "remote" pydevd-handling daemon.
|
||||
|
||||
The daemon supports the pydevd client wire protocol, sending
|
||||
requests and handling responses (and events).
|
||||
|
||||
This is a replacement for _pydevd_bundle.pydevd_comm.start_client.
|
||||
"""
|
||||
sock, start_session = daemon.start_client((host, port))
|
||||
start_session(**kwargs)
|
||||
return sock
|
||||
|
||||
|
||||
# See pydevd/_vendored/pydevd/_pydev_bundle/pydev_monkey.py
|
||||
@ptvsd.server.log.escaped_exceptions
|
||||
def get_python_c_args(host, port, indC, args, setup):
|
||||
runner = '''
|
||||
import sys
|
||||
sys.path.append(r'{ptvsd_syspath}')
|
||||
from ptvsd.server import multiproc
|
||||
multiproc.init_subprocess(
|
||||
{initial_pid},
|
||||
{initial_request},
|
||||
{parent_pid},
|
||||
{parent_port},
|
||||
{first_port},
|
||||
{last_port},
|
||||
{pydevd_setup})
|
||||
{rest}
|
||||
'''
|
||||
|
||||
first_port, last_port = multiproc.subprocess_port_range
|
||||
|
||||
# __file__ will be .../ptvsd/__init__.py, and we want the ...
|
||||
ptvsd_syspath = os.path.join(ptvsd.server.__file__, '../..')
|
||||
|
||||
return runner.format(
|
||||
initial_pid=multiproc.initial_pid,
|
||||
initial_request=multiproc.initial_request,
|
||||
parent_pid=os.getpid(),
|
||||
parent_port=multiproc.listener_port,
|
||||
first_port=first_port,
|
||||
last_port=last_port,
|
||||
ptvsd_syspath=ptvsd_syspath,
|
||||
pydevd_setup=setup,
|
||||
rest=args[indC + 1])
|
||||
|
||||
|
||||
def install(pydevd_module, address,
|
||||
start_server=start_server, start_client=start_client,
|
||||
**kwargs):
|
||||
"""Configure pydevd to use our wrapper.
|
||||
|
||||
This is a bit of a hack to allow us to run our VSC debug adapter
|
||||
in the same process as pydevd. Note that, as with most hacks,
|
||||
this is somewhat fragile (since the monkeypatching sites may
|
||||
change).
|
||||
"""
|
||||
|
||||
ptvsd.server.log.debug('Installing pydevd hooks.')
|
||||
|
||||
addr = Address.from_raw(address)
|
||||
daemon = Daemon(**kwargs)
|
||||
|
||||
_start_server = (lambda p: start_server(daemon, addr.host, p))
|
||||
_start_server.orig = start_server
|
||||
_start_client = (lambda h, p: start_client(daemon, h, p))
|
||||
_start_client.orig = start_client
|
||||
|
||||
# These are the functions pydevd invokes to get a socket to the client.
|
||||
pydevd_comm.start_server = _start_server
|
||||
pydevd_comm.start_client = _start_client
|
||||
|
||||
# This is invoked when a child process is spawned with multiproc debugging enabled.
|
||||
pydev_monkey.patch_args = multiproc.patch_and_quote_args
|
||||
if not options.multiprocess and not options.no_debug:
|
||||
# This means '--multiprocess' flag was not passed via command line args. Patch the
|
||||
# new process functions here to handle multiprocess being enabled via debug options.
|
||||
ptvsd.server.log.debug('Monkey-patching multiprocess functions.')
|
||||
pydev_monkey.patch_new_process_functions()
|
||||
|
||||
# Ensure that pydevd is using our functions.
|
||||
pydevd_module.start_server = _start_server
|
||||
pydevd_module.start_client = _start_client
|
||||
__main__ = sys.modules['__main__']
|
||||
if __main__ is not pydevd:
|
||||
if getattr(__main__, '__file__', None) == pydevd.__file__:
|
||||
__main__.start_server = _start_server
|
||||
__main__.start_client = _start_client
|
||||
|
||||
return daemon
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See LICENSE in the project root
|
||||
# for license information.
|
||||
|
||||
# TODO: only absolute_import needed?
|
||||
from __future__ import print_function, with_statement, absolute_import
|
||||
|
||||
import sys
|
||||
|
||||
if sys.version_info >= (3,):
|
||||
from ptvsd.server.reraise3 import reraise # noqa: F401
|
||||
else:
|
||||
from ptvsd.server.reraise2 import reraise # noqa: F401
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See LICENSE in the project root
|
||||
# for license information.
|
||||
|
||||
|
||||
def reraise(exc_info):
|
||||
# TODO: docstring
|
||||
raise exc_info[0], exc_info[1], exc_info[2] # noqa
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See LICENSE in the project root
|
||||
# for license information.
|
||||
|
||||
|
||||
def reraise(exc_info):
|
||||
# TODO: docstring
|
||||
raise exc_info[1].with_traceback(exc_info[2])
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See LICENSE in the project root
|
||||
# for license information.
|
||||
|
||||
import pydevd
|
||||
import threading
|
||||
|
||||
from ptvsd.server.daemon import DaemonBase
|
||||
from ptvsd.server.session import DebugSession
|
||||
from ptvsd.server.wrapper import VSCLifecycleMsgProcessor
|
||||
from pydevd import init_stdout_redirect, init_stderr_redirect
|
||||
|
||||
|
||||
HOSTNAME = 'localhost'
|
||||
|
||||
|
||||
def run(address, filename, is_module, *args, **kwargs):
|
||||
# TODO: docstring
|
||||
# TODO: client/server -> address
|
||||
daemon = Daemon()
|
||||
if not daemon.wait_for_launch(address):
|
||||
return
|
||||
|
||||
debugger = pydevd.PyDB()
|
||||
# We do not want some internal methods to get executed in non-debug mode.
|
||||
debugger.init_matplotlib_support = lambda *arg: None
|
||||
debugger.run(
|
||||
file=filename,
|
||||
globals=None,
|
||||
locals=None,
|
||||
is_module=is_module,
|
||||
set_trace=False)
|
||||
|
||||
|
||||
class Daemon(DaemonBase):
|
||||
"""The process-level manager for the VSC protocol debug adapter."""
|
||||
|
||||
LAUNCH_TIMEOUT = 10000 # seconds
|
||||
|
||||
class SESSION(DebugSession):
|
||||
class MESSAGE_PROCESSOR(VSCLifecycleMsgProcessor):
|
||||
|
||||
def on_setBreakpoints(self, request, args):
|
||||
# Note: breakpoints is required (vscode will terminate
|
||||
# the debugger if that's not the case).
|
||||
# See: https://github.com/microsoft/ptvsd/issues/1408
|
||||
self.send_response(
|
||||
request,
|
||||
success=True,
|
||||
breakpoints=(
|
||||
[{'verified': False}] * len(args.get('breakpoints', ()))
|
||||
)
|
||||
)
|
||||
|
||||
def on_invalid_request(self, request, args):
|
||||
self.send_response(request, success=True)
|
||||
|
||||
def wait_for_launch(self, addr, timeout=LAUNCH_TIMEOUT):
|
||||
# TODO: docstring
|
||||
launched = threading.Event()
|
||||
_, start_session = self.start_client(addr)
|
||||
start_session(
|
||||
notify_launch=launched.set,
|
||||
)
|
||||
return launched.wait(timeout)
|
||||
|
||||
def _start(self):
|
||||
import weakref
|
||||
weak_self = weakref.ref(self) # Avoid cyclic ref
|
||||
|
||||
def on_stdout(msg):
|
||||
self = weak_self()
|
||||
if self is not None:
|
||||
self._send_output('stdout', msg)
|
||||
|
||||
def on_stderr(msg):
|
||||
self = weak_self()
|
||||
if self is not None:
|
||||
self._send_output('stderr', msg)
|
||||
|
||||
init_stdout_redirect(on_stdout)
|
||||
init_stderr_redirect(on_stderr)
|
||||
return NoSocket()
|
||||
|
||||
def _close(self):
|
||||
super(Daemon, self)._close()
|
||||
|
||||
def _send_output(self, category, output):
|
||||
if self.session is None:
|
||||
return
|
||||
self.session._msgprocessor.send_event('output',
|
||||
category=category,
|
||||
output=output)
|
||||
|
||||
|
||||
class NoSocket(object):
|
||||
"""A object with a noop socket lifecycle."""
|
||||
|
||||
def shutdown(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
|
@ -1,204 +0,0 @@
|
|||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See LICENSE in the project root
|
||||
# for license information.
|
||||
|
||||
import ptvsd.server.log
|
||||
from .socket import is_socket, close_socket
|
||||
from .wrapper import VSCodeMessageProcessor
|
||||
from ._util import TimeoutError, ClosedError, Closeable, Startable
|
||||
|
||||
|
||||
class DebugSession(Startable, Closeable):
|
||||
"""A single DAP session for a network client socket."""
|
||||
|
||||
MESSAGE_PROCESSOR = None
|
||||
|
||||
NAME = 'debug session'
|
||||
FAIL_ON_ALREADY_CLOSED = False
|
||||
FAIL_ON_ALREADY_STOPPED = False
|
||||
|
||||
@classmethod
|
||||
def from_raw(cls, raw, **kwargs):
|
||||
"""Return a session for the given data."""
|
||||
if isinstance(raw, cls):
|
||||
return raw
|
||||
if not is_socket(raw):
|
||||
# TODO: Create a new client socket from a remote address?
|
||||
# addr = Address.from_raw(raw)
|
||||
raise NotImplementedError
|
||||
client = raw
|
||||
return cls(client, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def from_server_socket(cls, server, **kwargs):
|
||||
"""Return a session for the next connection to the given socket."""
|
||||
client, _ = server.accept()
|
||||
return cls(client, ownsock=True, **kwargs)
|
||||
|
||||
def __init__(self, sock,
|
||||
notify_closing=None,
|
||||
notify_disconnecting=None,
|
||||
ownsock=False):
|
||||
super(DebugSession, self).__init__()
|
||||
|
||||
if notify_closing is not None:
|
||||
|
||||
def handle_closing(before):
|
||||
if before:
|
||||
notify_closing(self)
|
||||
|
||||
self.add_close_handler(handle_closing)
|
||||
|
||||
if notify_disconnecting is None:
|
||||
notify_disconnecting = (lambda _: None)
|
||||
self._notify_disconnecting = notify_disconnecting
|
||||
|
||||
self._sock = sock
|
||||
self._pre_socket_close = None
|
||||
if ownsock:
|
||||
|
||||
# Close the socket *after* calling sys.exit() (via notify_closing).
|
||||
def handle_closing(before):
|
||||
if before:
|
||||
return
|
||||
ptvsd.server.log.debug('Closing session socket')
|
||||
proc = self._msgprocessor
|
||||
if self._pre_socket_close is not None:
|
||||
self._pre_socket_close()
|
||||
if proc is not None:
|
||||
try:
|
||||
proc.wait_while_connected(10) # seconds
|
||||
except TimeoutError:
|
||||
ptvsd.server.log.exception('timed out waiting for disconnect', level='debug')
|
||||
close_socket(self._sock)
|
||||
|
||||
self.add_close_handler(handle_closing)
|
||||
|
||||
self._msgprocessor = None
|
||||
|
||||
@property
|
||||
def socket(self):
|
||||
return self._sock
|
||||
|
||||
@property
|
||||
def msgprocessor(self):
|
||||
return self._msgprocessor
|
||||
|
||||
def handle_debugger_stopped(self, wait=None):
|
||||
"""Deal with the debugger exiting."""
|
||||
proc = self._msgprocessor
|
||||
if proc is None:
|
||||
return
|
||||
proc.handle_debugger_stopped(wait)
|
||||
|
||||
def handle_exiting(self, exitcode=None, wait=None):
|
||||
"""Deal with the debuggee exiting."""
|
||||
proc = self._msgprocessor
|
||||
if proc is None:
|
||||
return
|
||||
proc.handle_exiting(exitcode, wait)
|
||||
|
||||
def wait_until_stopped(self):
|
||||
"""Block until all resources (e.g. message processor) have stopped."""
|
||||
proc = self._msgprocessor
|
||||
if proc is None:
|
||||
return
|
||||
# TODO: Do this in VSCodeMessageProcessor.close()?
|
||||
proc._wait_for_server_thread()
|
||||
|
||||
# internal methods
|
||||
|
||||
def _new_msg_processor(self, **kwargs):
|
||||
return self.MESSAGE_PROCESSOR(
|
||||
self._sock,
|
||||
notify_disconnecting=self._handle_vsc_disconnect,
|
||||
notify_closing=self._handle_vsc_close,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def _start(self, threadname, **kwargs):
|
||||
"""Start the message handling for the session."""
|
||||
self._msgprocessor = self._new_msg_processor(**kwargs)
|
||||
self.add_resource_to_close(self._msgprocessor)
|
||||
self._msgprocessor.start(threadname)
|
||||
return self._msgprocessor_running
|
||||
|
||||
def _stop(self):
|
||||
proc = self._msgprocessor
|
||||
if proc is None:
|
||||
return
|
||||
|
||||
ptvsd.server.log.debug('Message processor is stopping.')
|
||||
# TODO: We should not need to wait if not exiting.
|
||||
# The editor will send a "disconnect" request at this point.
|
||||
proc._wait_for_disconnect()
|
||||
proc.close()
|
||||
self._msgprocessor = None
|
||||
|
||||
def _close(self):
|
||||
ptvsd.server.log.debug('Session is closing.')
|
||||
|
||||
def _msgprocessor_running(self):
|
||||
if self._msgprocessor is None:
|
||||
return False
|
||||
# TODO: Return self._msgprocessor.is_running().
|
||||
return True
|
||||
|
||||
# internal methods for VSCodeMessageProcessor
|
||||
|
||||
def _handle_vsc_disconnect(self, pre_socket_close=None):
|
||||
ptvsd.server.log.debug('Disconnecting.')
|
||||
self._pre_socket_close = pre_socket_close # TODO: Fail if already set?
|
||||
self._notify_disconnecting(self)
|
||||
|
||||
def _handle_vsc_close(self):
|
||||
ptvsd.server.log.debug('Message processor is closing.')
|
||||
try:
|
||||
self.close()
|
||||
except ClosedError:
|
||||
pass
|
||||
|
||||
|
||||
class PyDevdDebugSession(DebugSession):
|
||||
"""A single DAP session for a network client socket."""
|
||||
|
||||
MESSAGE_PROCESSOR = VSCodeMessageProcessor
|
||||
|
||||
def __init__(self, sock,
|
||||
notify_debugger_ready=None,
|
||||
**kwargs):
|
||||
super(PyDevdDebugSession, self).__init__(sock, **kwargs)
|
||||
|
||||
def notify_debugger_ready(session, _notify=notify_debugger_ready):
|
||||
if self._notified_debugger_ready:
|
||||
return
|
||||
self._notified_debugger_ready = True
|
||||
if _notify is not None:
|
||||
_notify(session)
|
||||
|
||||
self._notified_debugger_ready = False
|
||||
self._notify_debugger_ready = notify_debugger_ready
|
||||
|
||||
def handle_pydevd_message(self, cmdid, seq, text):
|
||||
if self._msgprocessor is None:
|
||||
# TODO: Do more than ignore?
|
||||
return
|
||||
try:
|
||||
return self._msgprocessor.on_pydevd_event(cmdid, seq, text)
|
||||
except:
|
||||
ptvsd.server.log.exception('Error handling pydevd message: {0}', text)
|
||||
raise
|
||||
|
||||
# internal methods
|
||||
|
||||
def _new_msg_processor(self, **kwargs):
|
||||
return super(PyDevdDebugSession, self)._new_msg_processor(
|
||||
notify_debugger_ready=self._handle_vsc_debugger_ready,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
# internal methods for VSCodeMessageProcessor
|
||||
|
||||
def _handle_vsc_debugger_ready(self):
|
||||
ptvsd.server.log.debug('Ready to debug')
|
||||
self._notify_debugger_ready(self)
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
import platform
|
||||
import pytest
|
||||
from ptvsd.common.socket import Address
|
||||
from ptvsd.common.socket import create_server, close_socket
|
||||
|
||||
|
||||
|
|
@ -43,118 +42,3 @@ class TestSocketServerReuse(object):
|
|||
close_socket(sock1)
|
||||
if sock2 is not None:
|
||||
close_socket(sock2)
|
||||
|
||||
|
||||
class TestAddress(object):
|
||||
def test_from_raw(self):
|
||||
serverlocal = Address.as_server("localhost", 9876)
|
||||
serverremote = Address.as_server("1.2.3.4", 9876)
|
||||
clientlocal = Address.as_client("localhost", 9876)
|
||||
clientremote = Address.as_client("1.2.3.4", 9876)
|
||||
default = Address(None, 1111)
|
||||
external = Address("", 1111)
|
||||
values = [
|
||||
(serverlocal, serverlocal),
|
||||
(serverremote, serverremote),
|
||||
(clientlocal, clientlocal),
|
||||
(clientremote, clientremote),
|
||||
(None, default),
|
||||
("", external),
|
||||
([], default),
|
||||
({}, default),
|
||||
(9876, serverlocal),
|
||||
("localhost:9876", clientlocal),
|
||||
("1.2.3.4:9876", clientremote),
|
||||
("*:9876", Address.as_server("", 9876)),
|
||||
("*", external),
|
||||
(":9876", Address.as_server("", 9876)),
|
||||
("localhost", Address("localhost", 1111)),
|
||||
(":", external),
|
||||
(dict(host="localhost"), Address("localhost", 1111)),
|
||||
(dict(port=9876), serverlocal),
|
||||
(dict(host=None, port=9876), serverlocal),
|
||||
(dict(host="localhost", port=9876), clientlocal),
|
||||
(dict(host="localhost", port="9876"), clientlocal),
|
||||
]
|
||||
for value, expected in values:
|
||||
addr = Address.from_raw(value, defaultport=1111)
|
||||
assert addr == expected
|
||||
|
||||
@pytest.mark.parametrize("host", ["localhost", "127.0.0.1", "::", "1.2.3.4"])
|
||||
def test_as_server_valid_address(self, host):
|
||||
addr = Address.as_server(host, 9786)
|
||||
assert addr == Address(host, 9786, isserver=True)
|
||||
|
||||
def test_as_server_public_host(self):
|
||||
addr = Address.as_server("", 9786)
|
||||
assert addr == Address("", 9786, isserver=True)
|
||||
|
||||
def test_as_server_default_host(self):
|
||||
addr = Address.as_server(None, 9786)
|
||||
assert addr == Address("localhost", 9786, isserver=True)
|
||||
|
||||
@pytest.mark.parametrize("host", [None, "", "localhost", "1.2.3.4"])
|
||||
def test_as_server_bad_port(self, host):
|
||||
port = None
|
||||
with pytest.raises(TypeError):
|
||||
Address.as_server(host, port)
|
||||
|
||||
@pytest.mark.parametrize("host", [None, "", "localhost", "1.2.3.4"])
|
||||
@pytest.mark.parametrize("port", ["", -1, 65536])
|
||||
def test_as_server_bad_port2(self, host, port):
|
||||
with pytest.raises(ValueError):
|
||||
Address.as_server(host, port)
|
||||
|
||||
@pytest.mark.parametrize("host", ["localhost", "127.0.0.1", "::", "1.2.3.4"])
|
||||
def test_as_client_valid_address(self, host):
|
||||
addr = Address.as_client(host, 9786)
|
||||
assert addr == Address(host, 9786, isserver=False)
|
||||
|
||||
def test_as_client_public_host(self):
|
||||
addr = Address.as_client("", 9786)
|
||||
assert addr == Address("", 9786, isserver=False)
|
||||
|
||||
def test_as_client_default_host(self):
|
||||
addr = Address.as_client(None, 9786)
|
||||
assert addr == Address("localhost", 9786, isserver=False)
|
||||
|
||||
@pytest.mark.parametrize("host", [None, "", "localhost", "1.2.3.4"])
|
||||
def test_as_client_bad_port(self, host):
|
||||
port = None
|
||||
with pytest.raises(TypeError):
|
||||
Address.as_client(host, port)
|
||||
|
||||
@pytest.mark.parametrize("host", [None, "", "localhost", "1.2.3.4"])
|
||||
@pytest.mark.parametrize("port", ["", -1, 65536])
|
||||
def test_as_client_bad_port2(self, host, port):
|
||||
with pytest.raises(ValueError):
|
||||
Address.as_client(host, port)
|
||||
|
||||
@pytest.mark.parametrize("host", ["localhost", "127.0.0.1", "::", "1.2.3.4"])
|
||||
def test_new_valid_address(self, host):
|
||||
addr = Address(host, 9786)
|
||||
assert addr == Address(host, 9786, isserver=False)
|
||||
|
||||
def test_new_public_host(self):
|
||||
addr = Address("", 9786)
|
||||
assert addr == Address("", 9786, isserver=True)
|
||||
|
||||
def test_new_default_host(self):
|
||||
addr = Address(None, 9786)
|
||||
assert addr == Address("localhost", 9786, isserver=True)
|
||||
|
||||
def test_new_wildcard_host(self):
|
||||
addr = Address("*", 9786)
|
||||
assert addr == Address("", 9786, isserver=True)
|
||||
|
||||
@pytest.mark.parametrize("host", [None, "", "localhost", "1.2.3.4"])
|
||||
def test_new_bad_port(self, host):
|
||||
port = None
|
||||
with pytest.raises(TypeError):
|
||||
Address(host, port)
|
||||
|
||||
@pytest.mark.parametrize("host", [None, "", "localhost", "1.2.3.4"])
|
||||
@pytest.mark.parametrize("port", ["", -1, 65536])
|
||||
def test_new_bad_port2(self, host, port):
|
||||
with pytest.raises(ValueError):
|
||||
Address(host, port)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue