* 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:
Karthik Nadig 2019-08-02 13:08:07 -07:00 committed by GitHub
parent 0ea50467aa
commit e093c86fba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 290 additions and 4405 deletions

View file

@ -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,

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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()

View file

@ -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

View file

@ -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):

View file

@ -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

View file

@ -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,

View 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)

View file

@ -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
#=======================================================================================================================

View file

@ -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

View file

@ -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")

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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
View 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

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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,

View file

@ -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()

View file

@ -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()

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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])

View file

@ -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

View file

@ -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

View file

@ -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)