Esempio n. 1
0
    def __init__(self, session, stream=None, channel=None):
        assert (stream is None) ^ (channel is None)

        try:
            lock_held = session.lock.acquire(blocking=False)
            assert lock_held, "__init__ of a Component subclass must lock its Session"
        finally:
            session.lock.release()

        super(Component, self).__init__()

        self.session = session

        if channel is None:
            stream.name = str(self)
            channel = messaging.JsonMessageChannel(stream, self)
            channel.start()
        else:
            channel.name = channel.stream.name = str(self)
            channel.handlers = self
        self.channel = channel
        self.is_connected = True

        # Do this last to avoid triggering useless notifications for assignments above.
        self.observers += [lambda *_: self.session.notify_changed()]
Esempio n. 2
0
def connect(launcher_port):
    from debugpy.common import messaging, sockets
    from debugpy.launcher import handlers

    global channel
    assert channel is None

    sock = sockets.create_client()
    sock.connect(("127.0.0.1", launcher_port))

    stream = messaging.JsonIOStream.from_socket(sock, "Adapter")
    channel = messaging.JsonMessageChannel(stream, handlers=handlers)
    channel.start()
Esempio n. 3
0
def connect(host, port):
    from debugpy.common import log, messaging, sockets
    from debugpy.launcher import handlers

    global channel, adapter_host
    assert channel is None
    assert adapter_host is None

    log.info("Connecting to adapter at {0}:{1}", host, port)

    sock = sockets.create_client()
    sock.connect((host, port))
    adapter_host = host

    stream = messaging.JsonIOStream.from_socket(sock, "Adapter")
    channel = messaging.JsonMessageChannel(stream, handlers=handlers)
    channel.start()
Esempio n. 4
0
    def _start_channel(self, stream):
        handlers = messaging.MessageHandlers(
            request=self._process_request,
            event=self._process_event,
            disconnect=self._process_disconnect,
        )
        self.channel = messaging.JsonMessageChannel(stream, handlers)
        self.channel.start()

        self.wait_for_next(
            timeline.Event(
                "output",
                {
                    "category": "telemetry",
                    "output": "ptvsd",
                    "data": {
                        "packageVersion": some.str
                    },
                },
            )
            & timeline.Event(
                "output",
                {
                    "category": "telemetry",
                    "output": "debugpy",
                    "data": {
                        "packageVersion": some.str
                    },
                },
            ))

        self.request(
            "initialize",
            {
                "pathFormat": "path",
                "clientID": self.client_id,
                "adapterID": "test",
                "linesStartAt1": True,
                "columnsStartAt1": True,
                "supportsVariableType": True,
                "supportsRunInTerminalRequest": True,
            },
        )
Esempio n. 5
0
    def test_events(self):
        EVENTS = [
            {
                "seq": 1,
                "type": "event",
                "event": "stopped",
                "body": {
                    "reason": "pause"
                },
            },
            {
                "seq": 2,
                "type": "event",
                "event": "unknown",
                "body": {
                    "something": "else"
                },
            },
        ]

        recorder = MessageHandlerRecorder()

        class Handlers(object):
            @recorder
            def stopped_event(self, event):
                assert event.event == "stopped"

            @recorder
            def event(self, event):
                assert event.event == "unknown"

        stream = JsonMemoryStream(EVENTS, [])
        channel = messaging.JsonMessageChannel(stream, Handlers())
        channel.start()
        channel.wait()

        recorder.expect(channel, EVENTS, ["stopped_event", "event"])
Esempio n. 6
0
    def test_fuzz(self):
        # Set up two channels over the same stream that send messages to each other
        # asynchronously, and record everything that they send and receive.
        # All records should match at the end.

        class Fuzzer(object):
            def __init__(self, name):
                self.name = name
                self.lock = threading.Lock()
                self.sent = []
                self.received = []
                self.responses_sent = []
                self.responses_received = []
                self.done = False

            def start(self, channel):
                self._worker = threading.Thread(
                    name=self.name,
                    target=lambda: self._send_requests_and_events(channel),
                )
                self._worker.daemon = True
                self._worker.start()

            def wait(self):
                self._worker.join()

            def done_event(self, event):
                with self.lock:
                    self.done = True

            def fizz_event(self, event):
                assert event.event == "fizz"
                with self.lock:
                    self.received.append(("event", "fizz", event.body))

            def buzz_event(self, event):
                assert event.event == "buzz"
                with self.lock:
                    self.received.append(("event", "buzz", event.body))

            def event(self, event):
                with self.lock:
                    self.received.append(("event", event.event, event.body))

            def make_and_log_response(self, request):
                x = random.randint(-100, 100)
                if x < 0:
                    exc_type = (messaging.InvalidMessageError if x %
                                2 else messaging.MessageHandlingError)
                    x = exc_type(str(x), request)
                with self.lock:
                    self.responses_sent.append((request.seq, x))
                return x

            def fizz_request(self, request):
                assert request.command == "fizz"
                with self.lock:
                    self.received.append(
                        ("request", "fizz", request.arguments))
                return self.make_and_log_response(request)

            def buzz_request(self, request):
                assert request.command == "buzz"
                with self.lock:
                    self.received.append(
                        ("request", "buzz", request.arguments))
                return self.make_and_log_response(request)

            def request(self, request):
                with self.lock:
                    self.received.append(
                        ("request", request.command, request.arguments))
                return self.make_and_log_response(request)

            def _got_response(self, response):
                with self.lock:
                    self.responses_received.append(
                        (response.request.seq, response.body))

            def _send_requests_and_events(self, channel):
                types = [
                    random.choice(("event", "request")) for _ in range(0, 100)
                ]

                for typ in types:
                    name = random.choice(("fizz", "buzz", "fizzbuzz"))
                    body = random.randint(0, 100)

                    with self.lock:
                        self.sent.append((typ, name, body))

                    if typ == "event":
                        channel.send_event(name, body)
                    elif typ == "request":
                        req = channel.send_request(name, body)
                        req.on_response(self._got_response)

                channel.send_event("done")

                # Spin until we receive "done", and also get responses to all requests.
                requests_sent = types.count("request")
                log.info("{0} waiting for {1} responses...", self.name,
                         requests_sent)
                while True:
                    with self.lock:
                        if self.done:
                            if requests_sent == len(self.responses_received):
                                break
                    time.sleep(0.1)

        fuzzer1 = Fuzzer("fuzzer1")
        fuzzer2 = Fuzzer("fuzzer2")

        server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        server_socket.bind(("localhost", 0))
        _, port = server_socket.getsockname()
        server_socket.listen(0)

        socket1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        socket1_thread = threading.Thread(
            target=lambda: socket1.connect(("localhost", port)))
        socket1_thread.start()
        socket2, _ = server_socket.accept()
        socket1_thread.join()

        try:
            io1 = socket1.makefile("rwb", 0)
            io2 = socket2.makefile("rwb", 0)

            stream1 = messaging.JsonIOStream(io1, io1, "socket1")
            channel1 = messaging.JsonMessageChannel(stream1, fuzzer1)
            channel1.start()
            fuzzer1.start(channel1)

            stream2 = messaging.JsonIOStream(io2, io2, "socket2")
            channel2 = messaging.JsonMessageChannel(stream2, fuzzer2)
            channel2.start()
            fuzzer2.start(channel2)

            fuzzer1.wait()
            fuzzer2.wait()

        finally:
            socket1.close()
            socket2.close()

        assert fuzzer1.sent == fuzzer2.received
        assert fuzzer2.sent == fuzzer1.received
        assert fuzzer1.responses_sent == fuzzer2.responses_received
        assert fuzzer2.responses_sent == fuzzer1.responses_received
Esempio n. 7
0
    def test_invalid_request_handling(self):
        REQUESTS = [
            {
                "seq": 1,
                "type": "request",
                "command": "stackTrace",
                "arguments": {
                    "AAA": {}
                },
            },
            {
                "seq": 2,
                "type": "request",
                "command": "stackTrace",
                "arguments": {}
            },
            {
                "seq": 3,
                "type": "request",
                "command": "unknown",
                "arguments": None
            },
            {
                "seq": 4,
                "type": "request",
                "command": "pause"
            },
        ]

        class Handlers(object):
            def stackTrace_request(self, request):
                request.arguments["AAA"]
                request.arguments["AAA"]["BBB"]

            def request(self, request):
                request.arguments["CCC"]

            def pause_request(self, request):
                request.arguments["DDD"]

        output = []
        stream = JsonMemoryStream(REQUESTS, output)
        channel = messaging.JsonMessageChannel(stream, Handlers())
        channel.start()
        channel.wait()

        def missing_property(name):
            return some.str.matching("Invalid message:.*" + re.escape(name) +
                                     ".*")

        assert output == [
            {
                "seq": 1,
                "type": "response",
                "request_seq": 1,
                "command": "stackTrace",
                "success": False,
                "message": missing_property("BBB"),
            },
            {
                "seq": 2,
                "type": "response",
                "request_seq": 2,
                "command": "stackTrace",
                "success": False,
                "message": missing_property("AAA"),
            },
            {
                "seq": 3,
                "type": "response",
                "request_seq": 3,
                "command": "unknown",
                "success": False,
                "message": missing_property("CCC"),
            },
            {
                "seq": 4,
                "type": "response",
                "request_seq": 4,
                "command": "pause",
                "success": False,
                "message": missing_property("DDD"),
            },
        ]
Esempio n. 8
0
    def test_responses(self):
        request1_sent = threading.Event()
        request2_sent = threading.Event()
        request3_sent = threading.Event()
        request4_sent = threading.Event()

        def iter_responses():
            request1_sent.wait()
            yield {
                "seq": 1,
                "type": "response",
                "request_seq": 1,
                "command": "next",
                "success": True,
                "body": {
                    "threadId": 3
                },
            }

            request2_sent.wait()
            yield {
                "seq": 2,
                "type": "response",
                "request_seq": 2,
                "command": "pause",
                "success": False,
                "message": "Invalid message: pause not supported",
            }

            request3_sent.wait()
            yield {
                "seq": 3,
                "type": "response",
                "request_seq": 3,
                "command": "next",
                "success": True,
                "body": {
                    "threadId": 5
                },
            }

            request4_sent.wait()

        stream = JsonMemoryStream(iter_responses(), [])
        channel = messaging.JsonMessageChannel(stream, None)
        channel.start()

        # Blocking wait.
        request1 = channel.send_request("next")
        request1_sent.set()
        log.info("Waiting for response...")
        response1_body = request1.wait_for_response()
        response1 = request1.response

        assert response1.success
        assert response1.request is request1
        assert response1.body == response1_body
        assert response1.body == {"threadId": 3}

        # Async callback, registered before response is received.
        request2 = channel.send_request("pause")
        response2 = []
        response2_received = threading.Event()

        def response2_handler(resp):
            response2.append(resp)
            response2_received.set()

        log.info("Registering callback")
        request2.on_response(response2_handler)
        request2_sent.set()

        log.info("Waiting for callback...")
        response2_received.wait()
        (response2, ) = response2

        assert not response2.success
        assert response2.request is request2
        assert response2 is request2.response
        assert response2.body == messaging.InvalidMessageError(
            "pause not supported", request2)

        # Async callback, registered after response is received.
        request3 = channel.send_request("next")
        request3_sent.set()
        request3.wait_for_response()
        response3 = []
        response3_received = threading.Event()

        def response3_handler(resp):
            response3.append(resp)
            response3_received.set()

        log.info("Registering callback")
        request3.on_response(response3_handler)

        log.info("Waiting for callback...")
        response3_received.wait()
        (response3, ) = response3

        assert response3.success
        assert response3.request is request3
        assert response3 is request3.response
        assert response3.body == {"threadId": 5}

        # Async callback, registered after channel is closed.
        request4 = channel.send_request("next")
        request4_sent.set()
        channel.wait()
        response4 = []
        response4_received = threading.Event()

        def response4_handler(resp):
            response4.append(resp)
            response4_received.set()

        log.info("Registering callback")
        request4.on_response(response4_handler)

        log.info("Waiting for callback...")
        response4_received.wait()
        (response4, ) = response4

        assert not response4.success
        assert response4.request is request4
        assert response4 is request4.response
        assert isinstance(response4.body, messaging.NoMoreMessages)
Esempio n. 9
0
    def test_requests(self):
        REQUESTS = [
            {
                "seq": 1,
                "type": "request",
                "command": "next",
                "arguments": {
                    "threadId": 3
                },
            },
            {
                "seq": 2,
                "type": "request",
                "command": "launch",
                "arguments": {
                    "program": "main.py"
                },
            },
            {
                "seq": 3,
                "type": "request",
                "command": "unknown",
                "arguments": {
                    "answer": 42
                },
            },
            {
                "seq": 4,
                "type": "request",
                "command": "pause",
                "arguments": {
                    "threadId": 5
                },
            },
        ]

        recorder = MessageHandlerRecorder()

        class Handlers(object):
            @recorder
            def next_request(self, request):
                assert request.command == "next"
                return {"threadId": 7}

            @recorder
            def launch_request(self, request):
                assert request.command == "launch"
                self._launch = request
                return messaging.NO_RESPONSE

            @recorder
            def request(self, request):
                request.respond({})

            @recorder
            def pause_request(self, request):
                assert request.command == "pause"
                self._launch.respond({"processId": 9})
                raise request.cant_handle("pause error")

        stream = JsonMemoryStream(REQUESTS, [])
        channel = messaging.JsonMessageChannel(stream, Handlers())
        channel.start()
        channel.wait()

        recorder.expect(
            channel,
            REQUESTS,
            ["next_request", "launch_request", "request", "pause_request"],
        )

        assert stream.output == [
            {
                "seq": 1,
                "type": "response",
                "request_seq": 1,
                "command": "next",
                "success": True,
                "body": {
                    "threadId": 7
                },
            },
            {
                "seq": 2,
                "type": "response",
                "request_seq": 3,
                "command": "unknown",
                "success": True,
            },
            {
                "seq": 3,
                "type": "response",
                "request_seq": 2,
                "command": "launch",
                "success": True,
                "body": {
                    "processId": 9
                },
            },
            {
                "seq": 4,
                "type": "response",
                "request_seq": 4,
                "command": "pause",
                "success": False,
                "message": "pause error",
            },
        ]
Esempio n. 10
0
    def __init__(self, sock):
        from debugpy.adapter import sessions

        self.disconnected = False

        self.server = None
        """The Server component, if this debug server belongs to Session.
        """

        self.pid = None

        stream = messaging.JsonIOStream.from_socket(sock, str(self))
        self.channel = messaging.JsonMessageChannel(stream, self)
        self.channel.start()

        try:
            self.authenticate()
            info = self.channel.request("pydevdSystemInfo")
            process_info = info("process", json.object())
            self.pid = process_info("pid", int)
            self.ppid = process_info("ppid", int, optional=True)
            if self.ppid == ():
                self.ppid = None
            self.channel.name = stream.name = str(self)

            debugpy_dir = os.path.dirname(os.path.dirname(debugpy.__file__))
            # Note: we must check if 'debugpy' is not already in sys.modules because the
            # evaluation of an import at the wrong time could deadlock Python due to
            # its import lock.
            #
            # So, in general this evaluation shouldn't do anything. It's only
            # important when pydevd attaches automatically to a subprocess. In this
            # case, we have to make sure that debugpy is properly put back in the game
            # for users to be able to use it.v
            #
            # In this case (when the import is needed), this evaluation *must* be done
            # before the configurationDone request is sent -- if this is not respected
            # it's possible that pydevd already started secondary threads to handle
            # commands, in which case it's very likely that this command would be
            # evaluated at the wrong thread and the import could potentially deadlock
            # the program.
            #
            # Note 2: the sys module is guaranteed to be in the frame globals and
            # doesn't need to be imported.
            inject_debugpy = """
if 'debugpy' not in sys.modules:
    sys.path.insert(0, {debugpy_dir!r})
    try:
        import debugpy
    finally:
        del sys.path[0]
"""
            inject_debugpy = fmt(inject_debugpy, debugpy_dir=debugpy_dir)

            try:
                self.channel.request("evaluate", {"expression": inject_debugpy})
            except messaging.MessageHandlingError:
                # Failure to inject is not a fatal error - such a subprocess can
                # still be debugged, it just won't support "import debugpy" in user
                # code - so don't terminate the session.
                log.swallow_exception(
                    "Failed to inject debugpy into {0}:", self, level="warning"
                )

            with _lock:
                # The server can disconnect concurrently before we get here, e.g. if
                # it was force-killed. If the disconnect() handler has already run,
                # don't register this server or report it, since there's nothing to
                # deregister it.
                if self.disconnected:
                    return

                if any(conn.pid == self.pid for conn in _connections):
                    raise KeyError(
                        fmt("{0} is already connected to this adapter", self)
                    )
                _connections.append(self)
                _connections_changed.set()

        except Exception:
            log.swallow_exception("Failed to accept incoming server connection:")
            self.channel.close()

            # If this was the first server to connect, and the main thread is inside
            # wait_until_disconnected(), we want to unblock it and allow it to exit.
            dont_wait_for_first_connection()

            # If we couldn't retrieve all the necessary info from the debug server,
            # or there's a PID clash, we don't want to track this debuggee anymore,
            # but we want to continue accepting connections.
            return

        parent_session = sessions.get(self.ppid)
        if parent_session is None:
            log.info("No active debug session for parent process of {0}.", self)
        else:
            try:
                parent_session.client.notify_of_subprocess(self)
            except Exception:
                # This might fail if the client concurrently disconnects from the parent
                # session. We still want to keep the connection around, in case the
                # client reconnects later. If the parent session was "launch", it'll take
                # care of closing the remaining server connections.
                log.swallow_exception("Failed to notify parent session about {0}:", self)