一、问题现象

在 Rocky Linux 上通过 Rocket Software Exceed TurboX(ETX)运行 EDA/GUI 软件时,启动 Qt 程序报错:

1
2
3
Maximum number of clients reached
qt.qpa.xcb: could not connect to display etxnode12.icinfra.cn:10.0
qt.qpa.plugin: Could not load the Qt platform plugin "xcb" in "" even though it was found.

第一眼容易把问题判断成 Qt 的 xcb 插件缺失。但结合第一行:

1
Maximum number of clients reached

更准确的判断是:

当前 ETX/X11 Display 已经达到 X client 连接数上限,新启动的 GUI 程序无法再连接 X server。
xcb 报错是后续表现,不是优先根因。

在 EDA 场景中,Virtuoso、Calibre、Verdi、Firefox、Java GUI、Qt GUI 等工具长期堆积在同一个远程桌面会话里,比较容易触发这个问题。


二、先理解几个概念

1. X client 不是“窗口数量”

X11 里的 client slot 是按连接计数,不是按窗口计数。

一个 X client 可以是:

  • 一个有可见窗口的 GUI 程序;
  • 一个没有顶层窗口的后台 helper;
  • 一个隐藏窗口、托盘组件、剪贴板组件;
  • 一个 Qt/Java/GTK 内部辅助进程;
  • 一个 EDA 工具启动出来的子 GUI 进程;
  • 一个远程显示代理进程。

所以:

1
X client 数量 != 桌面上能看到的窗口数量

2. xlsclients 不等于真实连接数

xlsclients 主要通过 X11 窗口树和窗口属性识别 client application。它可能看不到:

  • 没有顶层窗口的 client;
  • 没有设置 WM_COMMAND / WM_CLIENT_MACHINE 属性的 client;
  • hidden / withdrawn 状态窗口;
  • 短生命周期 client;
  • 某些 Qt、Java、Chromium、EDA helper;
  • 某些 ETX/X11 proxy 辅助连接。

因此,xlsclients 通常会低估真实 X client connection 数量。

3. lsof/ss 更接近真实连接压力

对于:

1
Maximum number of clients reached

这种问题,应该优先看 socket connection 数量。

例如:

1
lsof -nP -iTCP:6010 -sTCP:ESTABLISHED

或者:

1
ss -tanp state established | grep ':6010'

这些命令看到的是连接到 X server / ETX display 的 TCP socket,更接近真实消耗 X client slot 的数量。


三、为什么 lsof 看到 501 个,而 xlsclients 只有 240 个?

实际排查中出现了这种现象:

1
2
3
4
5
lsof -nP -iTCP:6010 -sTCP:ESTABLISHED
# 看到 501 个连接

xlsclients -display "$DISPLAY" | wc -l
# 只看到 240 个

这个差异是正常的。

原因如下:

1. lsof 看 socket,xlsclients 看可识别的 client application

lsof 看到的是 TCP 连接。

xlsclients 看到的是能够从 X11 属性中识别出来的 client。很多占用 X client slot 的连接,不一定能被 xlsclients 显示出来。

2. 一个应用可能打开多个 X connection

例如:

1
2
3
4
5
6
7
Virtuoso
Calibre RVE
Verdi
Firefox / Chromium
Java GUI
Qt GUI
Python Qt/Tk GUI

表面上可能只是一个主窗口,但内部可能有多个进程、多个 helper、多个 X connection。

3. ETX 场景存在 proxy 链路

ETX 场景里经常不是简单的:

1
Application -> X server

而是类似:

1
2
3
4
5
6
7
8
9
10
11
12
EDA/Qt/Virtuoso/Calibre GUI
        |
        |  local X11 Unix socket
        v
/tmp/.X11-unix/X1
        |
        v
etxproxy
        |
        |  TCP / ETX display channel
        v
etxnode12:6010

因此,不同位置看到的连接数量并不相同。
如果要判断 Maximum number of clients reached,应以真正触达 X server / ETX display 的连接数为准。


四、确认 DISPLAY 与端口

先看当前 shell 的 DISPLAY

1
echo "$DISPLAY"

例如:

1
etxnode12.icinfra.cn:10.0

其中 display number 是 10

X11 TCP 端口通常是:

1
6000 + display_number

所以:

1
:10.0 -> TCP 6010

可以用下面命令自动解析:

1
2
3
4
5
6
7
8
9
DISPLAY_HOST=${DISPLAY%%:*}
DISPLAY_NUM=${DISPLAY#*:}
DISPLAY_NUM=${DISPLAY_NUM%%.*}
DISPLAY_PORT=$((6000 + DISPLAY_NUM))

echo "DISPLAY=$DISPLAY"
echo "DISPLAY_HOST=$DISPLAY_HOST"
echo "DISPLAY_NUM=$DISPLAY_NUM"
echo "DISPLAY_PORT=$DISPLAY_PORT"

如果需要解析 IP:

1
2
DISPLAY_IP=$(getent ahostsv4 "$DISPLAY_HOST" | awk 'NR==1{print $1}')
echo "$DISPLAY_IP"

五、查看当前 X client 连接数

1. 在 ETX Connection Node / Display Node 上查看

假设 display 是:

1
etxnode12.icinfra.cn:10.0

对应 TCP 端口是:

1
6010

etxnode12 上执行:

1
sudo ss -Htan state established '( sport = :6010 )' | wc -l

查看明细:

1
sudo ss -Htanp state established '( sport = :6010 )'

lsof 查看:

1
sudo lsof -nP -iTCP:6010 -sTCP:ESTABLISHED

统计数量:

1
sudo lsof -nP -iTCP:6010 -sTCP:ESTABLISHED | awk 'NR>1' | wc -l

2. 按来源主机统计

1
2
3
4
5
6
7
sudo ss -Htan state established '( sport = :6010 )' \
  | awk '{print $5}' \
  | sed -E 's/:[0-9]+$//' \
  | sort \
  | uniq -c \
  | sort -nr \
  | head -50

这可以判断 501 个连接主要来自哪台应用服务器、计算节点或远程主机。

3. 按远端 IP 统计

1
2
3
4
5
6
7
sudo lsof -nP -iTCP:6010 -sTCP:ESTABLISHED \
  | awk 'NR>1{print $9}' \
  | sed -E 's/.*->([^:]+):.*/\1/' \
  | sort \
  | uniq -c \
  | sort -nr \
  | head -50

4. 在应用主机侧找具体进程

在运行 EDA/GUI 程序的 Rocky Linux 应用主机上执行:

1
2
3
4
5
6
7
DISPLAY_HOST=${DISPLAY%%:*}
DISPLAY_NUM=${DISPLAY#*:}
DISPLAY_NUM=${DISPLAY_NUM%%.*}
DISPLAY_PORT=$((6000 + DISPLAY_NUM))
DISPLAY_IP=$(getent ahostsv4 "$DISPLAY_HOST" | awk 'NR==1{print $1}')

sudo lsof -nP -iTCP@"$DISPLAY_IP":"$DISPLAY_PORT" -sTCP:ESTABLISHED

按进程统计:

1
2
3
4
5
6
sudo lsof -nP -iTCP@"$DISPLAY_IP":"$DISPLAY_PORT" -sTCP:ESTABLISHED \
  | awk 'NR>1{print $1, $2, $3}' \
  | sort \
  | uniq -c \
  | sort -nr \
  | head -50

如果看到某个进程占用异常多,例如:

1
2
3
4
120 java 12345 user1
80  virtuoso 23456 user1
60  firefox 34567 user1
40  python 45678 user1

就应该优先排查该进程是否在反复创建 X connection,或者是否有残留 GUI/helper 进程没有退出。


六、如何理解 /tmp/.X11-unix/X1etxproxy

排查时还看到类似输出:

1
2
3
u_str LISTEN 0 128 @/tmp/.X11-unix/X1  ... users:(("etxproxy",pid=56012,fd=9))
u_str LISTEN 0 128  /tmp/.X11-unix/X1  ... users:(("etxproxy",pid=56012,fd=10))
u_str ESTAB  0 0   @/tmp/.X11-unix/X1  ... users:(("etxproxy",pid=56012,fd=6))

这里要区分:

1
2
LISTEN != 已经占用的 client connection
ESTAB  = 已经建立的连接

两条 LISTEN 通常是正常的:

1
2
@/tmp/.X11-unix/X1     # Linux abstract socket
/tmp/.X11-unix/X1      # filesystem pathname socket

这表示 etxproxy 同时监听 abstract socket 和 pathname socket。

如果只有一条 ESTAB,说明本地 Unix socket 侧只看到一个已建立连接。它不能推翻 TCP 6010 上看到的 501 个连接。

避免 grep 误匹配

下面这种写法容易把 X1X10X11 都匹配出来:

1
ss -xap | grep -E '/tmp/.X11-unix/X1|@/tmp/.X11-unix/X1'

建议写得更精确:

1
sudo ss -xapn | grep -E '(@|/tmp/)\.X11-unix/X1([[:space:]]|$)'

统计 Unix socket established 连接数:

1
2
3
sudo ss -H -xapn state established \
  | grep -E '(@|/tmp/)\.X11-unix/X1([[:space:]]|$)' \
  | wc -l

七、如何查看当前 X server 最大值

1. 找到监听 6010 的进程

在 ETX node 上:

1
sudo ss -ltnp | grep ':6010'

或者:

1
sudo lsof -nP -iTCP:6010 -sTCP:LISTEN

假设 PID 是 12345,查看启动参数:

1
2
3
sudo tr '\0' ' ' < /proc/12345/cmdline
echo
ps -fp 12345 -ww

重点看是否有:

1
-maxclients 256

或者:

1
-maxclients 512

2. 查 Xorg 日志

如果底层是标准 Xorg,可以查:

1
grep -Rhi "Max clients allowed" /var/log/Xorg.*.log ~/.local/share/xorg/Xorg.*.log 2>/dev/null

也可以查 X server 相关进程:

1
ps -efww | egrep 'Xorg|Xvfb|Xvnc|Xdummy|Xwayland|etx' | grep -v grep

八、如何调高最大 X client 数量

1. 标准 X server 参数

标准 X server 支持:

1
-maxclients 64|128|256|512

常见可选值是:

1
64 / 128 / 256 / 512

如果当前是 256,可以尝试调整为:

1
-maxclients 512

例如:

1
Xorg :10 -maxclients 512 ...

或者:

1
Xvfb :10 -maxclients 512 ...

2. xorg.conf.d 配置方式

如果 ETX 底层使用标准 Xorg,并且读取系统 Xorg 配置,可以创建:

1
2
3
4
5
6
7
sudo mkdir -p /etc/X11/xorg.conf.d

sudo tee /etc/X11/xorg.conf.d/99-maxclients.conf >/dev/null <<'EOF'
Section "ServerFlags"
    Option "MaxClients" "512"
EndSection
EOF

然后重启对应 X server / ETX session。

注意:

这通常只对新启动的 X server / 新建的 ETX session 生效。
已经运行中的 session 一般不会动态改变 MaxClients。

3. ETX 场景的注意点

Rocket Exceed TurboX 有自己的 Connection Node、Session、etxproxy 和 ThinX 传输链路。公开文档中可以看到 ETX 使用 etxproxy 与 ETX client 之间的 ThinX 协议,也可以看到 Connection Node 安装配置中有 X server 起始 display number 等参数。

但是,公开文档中并不容易直接找到一个明确叫:

1
2
3
MaxClients
maximum X clients per display
X server max clients

的 ETX 专用配置项。

因此,ETX 环境中不要直接假设 /etc/X11/xorg.conf.d/99-maxclients.conf 一定生效。更稳妥的顺序是:

  1. 先确认监听 6010 的到底是 XorgXvfbXvnc,还是 ETX 专有进程;
  2. 如果是标准 Xorg/Xvfb,再使用 -maxclients 512 或 Xorg 配置;
  3. 如果是 ETX 封装进程,应优先检查 ETX Server Manager / Session Profile / Node Profile / Runtime 参数;
  4. 如果界面或配置文件中找不到,应向 Rocket Support 确认当前版本是否支持调整 X client limit。

可以搜索 ETX 配置:

1
2
sudo grep -RniE 'maxclients|MaxClients|-maxclients|Xorg|Xvfb|Xvnc' \
  /etc /opt /usr/local 2>/dev/null | head -200

查看 ETX 服务:

1
systemctl list-units --type=service | grep -Ei 'etx|exceed|rocket'

九、如果已经是 512,继续调大不是优先方向

如果当前已经达到:

1
-maxclients 512

或者实际连接数已经在 501 附近,那么继续追求更高上限通常不是最优解。

原因是:

  • 很多 X server 的标准配置上限就是 512;
  • 某些版本内部可能支持更大值,但不一定是 ETX 支持的稳定路径;
  • EDA GUI 长时间运行时,连接数持续增长往往意味着 GUI/helper 进程堆积或连接泄漏;
  • 单个远程桌面会话承载过多重型 GUI,本身不利于稳定性。

更合理的方向是:

1
2
3
4
5
找出谁占用最多连接
清理残留进程
拆分 ETX session
限制长期堆积
建立监控告警

十、临时恢复方案

1. 关闭不用的 GUI 窗口

优先让用户正常关闭:

1
2
3
4
5
6
7
Virtuoso
Calibre RVE
Verdi
Firefox / Chromium
Java GUI
Qt GUI
xterm

2. 找出占用最多连接的进程

在应用主机侧:

1
2
3
4
5
6
sudo lsof -nP -iTCP@"$DISPLAY_IP":"$DISPLAY_PORT" -sTCP:ESTABLISHED \
  | awk 'NR>1{print $1, $2, $3}' \
  | sort \
  | uniq -c \
  | sort -nr \
  | head -50

确认后温和 kill:

1
kill <pid>

必要时再:

1
kill -9 <pid>

不要直接执行:

1
pkill -u $USER

这可能误杀用户正在运行的 shell、后台任务、EDA job 或其它关键进程。

3. 重启 ETX session

如果当前 session 已经无法正常操作,最干净的方式是:

  1. 退出当前 ETX session;
  2. 在 ETX Server Manager 中结束该用户异常 session;
  3. 重新创建一个干净 session。

十一、长期治理建议

1. 按工具类型拆分 session

不要把所有重型 GUI 都放在一个 ETX session 中。

推荐:

1
2
3
4
Virtuoso / Schematic / Layout      一个 session
Calibre DRC/LVS/RVE                一个 session
Verdi / Debug                      一个 session
浏览器 / 文档 / help viewer        单独控制

2. 限制浏览器常驻

Firefox / Chromium 在远程 X11/ETX 场景里经常占用较多 X connection。
建议不要在 EDA 主 session 中长期挂多个浏览器窗口和标签页。

3. 定期清理空闲 session

建议制定策略:

1
2
3
空闲超过 N 小时提醒
空闲超过 N 天自动 suspend
长期 suspend 的 session 定期回收

4. 建立连接数监控

可以在 ETX node 上定期采样:

1
2
3
PORT=6010
COUNT=$(sudo ss -Htan state established "( sport = :$PORT )" | wc -l)
echo "$(date '+%F %T') port=$PORT established=$COUNT"

建议阈值:

1
2
3
4
>= 70%:观察
>= 80%:预警
>= 90%:通知用户清理
>= 95%:管理员介入

如果最大值是 512:

1
2
3
4
70% 约 358
80% 约 410
90% 约 461
95% 约 486

你看到的 501 已经非常接近上限。

5. 建立 Top offender 排查脚本

可以做一个日常诊断脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#!/usr/bin/env bash
set -euo pipefail

PORT="${1:-6010}"

echo "== X11 TCP established count on port $PORT =="
sudo ss -Htan state established "( sport = :$PORT )" | wc -l

echo
echo "== Top remote IPs =="
sudo ss -Htan state established "( sport = :$PORT )" \
  | awk '{print $5}' \
  | sed -E 's/:[0-9]+$//' \
  | sort \
  | uniq -c \
  | sort -nr \
  | head -30

echo
echo "== Listener =="
sudo ss -ltnp | grep ":$PORT" || true

echo
echo "== Process command line of listener =="
PID=$(sudo ss -ltnp | awk -v p=":$PORT" '$0 ~ p {print $0}' \
  | sed -n 's/.*pid=\([0-9]\+\).*/\1/p' | head -1)

if [[ -n "${PID:-}" ]]; then
  echo "PID=$PID"
  sudo tr '\0' ' ' < "/proc/$PID/cmdline"
  echo
  cat "/proc/$PID/limits" | egrep 'open files|processes' || true
else
  echo "No listener PID found."
fi

使用:

1
bash check_etx_x11_clients.sh 6010

十二、排查结论模板

遇到这类问题,可以用下面模板记录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
问题:Rocky Linux 通过 ETX 启动 Qt/EDA GUI 报 Maximum number of clients reached。

结论:
当前不是 Qt xcb 插件缺失优先,而是 ETX/X11 Display 达到 X client 连接上限。

证据:
1. DISPLAY=etxnode12.icinfra.cn:10.0,对应 TCP 6010。
2. lsof -nP -iTCP:6010 -sTCP:ESTABLISHED 看到约 501 个连接。
3. xlsclients 只有约 240 个,但 xlsclients 只能识别部分窗口型 client,不能代表真实连接数。
4. /tmp/.X11-unix/X1 上的 etxproxy LISTEN 只是本地 proxy 入口,不等于后端 6010 上的所有 client 连接。
5. 当 MaxClients 接近 512 时,新 GUI 程序无法获得 X client slot,触发 Maximum number of clients reached。

处理:
1. 临时清理异常 GUI/helper 进程,或重启用户 ETX session。
2. 查找占用连接最多的来源主机和进程。
3. 如果底层 X server 支持,可将 -maxclients 调整到 512。
4. 如果已是 512,应重点治理 session 堆积和连接泄漏。

十三、参考资料

  • Rocket Software Exceed TurboX documentation:Connection Node、Session、etxproxy、ThinX 相关说明。
  • Xserver manual:-maxclients 64|128|256|512 用于设置 X server 允许连接的最大 client 数。
  • xorg.conf manual:ServerFlags 中的 Option "MaxClients" "512" 可用于标准 Xorg 配置。
  • Linux sslsofxlsclientsxrestop 工具文档。