0%

解决vscode调试python遇到connection refused问题

最近用vscode远程开发功能调试一台centos虚拟机上的python的时候遇到connection refused问题,而之前在windows本地环境一直是好的,非常奇怪。研究了2天的时间,才把问题找到,通过定位问题,把vscode的调试器的架构也有了更深入的了解,下面记录下问题定位过程。

环境

  • VS Code version: 1.44.2
  • Python Extension version: 2020.4.74986
  • OS and version: centos7
  • Python version: 2.7.5

vscode调试器架构

官方参考:

vscode Debugger Extension:https://code.visualstudio.com/api/extension-guides/debugger-extension

Debug Adapter Protocol:https://microsoft.github.io/debug-adapter-protocol/overview

为了解耦vscode和具体语言的调试器,中间增加了一个Debug Adapter,它是在独立进程运行的,vscode和Debug Adapter之间通过Debug Adapter Protocol通信。

Python的Debug Adapter就是在Microsoft Python Extension中实现的。

深入调试交互过程

通过一个Windows正常环境来探索调式器交互过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"version": "0.2.0",
"configurations": [
{
"name": "Python: 当前文件",
"type": "python",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"port": 50624
}
]
}

上面的launch.json指定了具体端口,便于我们分析进程之间的关系,打上断点,启动一个调试,然后通过端口分析进程之间的关系。

上述9088进程就是Debug Adapter,运行代码如下:

14272进程如下:

正是vscode终端打印的:

ptvsd_launcher.py是调用的ptvsd,上述命令就是ptvsd --client --host localhost --port 50624 D:/test/pytest/main.py。

然后再结合阅读python-extensionptvsd的源代码,终于理清上述进程关系,下面是launch的过程,attach的过程有所区别。

image-20200423150535221

  1. vscode Debugger UI启动一个调试,首先会创建Debug Adapter进程,就是上述9088进程,它们之间是通过stdin和stdout通信的。Python Debug Adapter的实现是通过派生node debug adapter实现的。
  2. Debug Adapter进程会根据launch.json的host和port进行TCP监听,如果不设置host就是localhost,如果不设置port,会自动随机选择一个。
  3. Debug Adapter启动调试程序,实际是通过ptvsd完成的,运行在单独的进程,就是上述14272进程,launch的情况下ptvsd以client mode运行,也就是ptvsd主动连接Debug Adapter,因为这时Debug Adapter先于调试程序运行,调试程序可以第一时间知道Debug Adapter的端口。如果是attach的情况,就需要IDE连接调试程序,告诉调试程序Debug Adapter的地址,然后调试程序再连接Debug Adapter。
  4. 调式程序连接到Debug Adapter,然后按照DAP进行调试交互。

问题定位

弄清楚调式的交互过程后,就容易定位问题了,connection refused问题就出在ptvsd连接Debug Adapter的时候,一开始以为是不是Debug Adapter退出了,但是ps查看进程是正常的。通过阅读源代码发现ptvsd连接Debug Adapter的超时时间可以在launch.json中设置,默认是20秒(20000),我将timeout改大了一点,便于分析问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"version": "0.2.0",
"configurations": [
{
"name": "Python: 当前文件",
"type": "python",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"port": 50624,
"timeout": 200000
}
]
}

然后通过netstat查看,发现Debug Adapter并不是绑定在127.0.0.1。

image-20200423154359596

进一步查看/etc/hosts文件,发现localhost正是10.0.2.15,是eth0的ip。

而Debug Adapter启动调试程序时的–host参数总是为localhost,不管launch.json中如何设置。

自此问题基本定位清楚:

  1. Debug Adapter中监听是通过node net模块实现,net模块对于localhost会通过getaddrinfo获取ip,而getaddrinfo会通过/etc/hosts查询ip;

  2. ptvsd中是通过Python socket模块实现tcp通信,socke模块connect时,如果地址是localhost,它总是会使用127.0.0.1,bind也是一样。下述代码除了返回/etc/hosts中的ip,总是会加上127.0.0.1。

    1
    socket.getaddrinfo("localhost", None, socket.AF_INET, socket.SOCK_STREAM)

问题解决

  1. 方案1:launch.json中指定host为127.0.0.1且必须为127.0.0.1
  2. 方案2:Debug Adapter启动调试程序时的–host参数从launch.json中获取,同时launch.json中指定host为任意合法ip
  3. Debug Adapter总是绑定到0.0.0.0,也就是任意地址
  4. ptvsd连接Debug Adapter时也根据getaddrinfo将localhost转换为ip,并选择非127.0.0.1的ip

第一种方案是最简单的,其他方案需要修改python-extensionptvsd的源代码。

以上都是仍然使用ptvsd的解决方法,还有一个方法是切换到最新的debugpy也可以解决问题,参照microsoft/ptvsd#2104

-------------本文结束感谢您的阅读-------------