这可能是 libX11 中有关处理用于 xcb_wait_for_reply 的请求号的已知问题。
在引入 libxcb v1.5 代码以在内部各处使用 64 位序列号的某个时间点,并添加了逻辑以在进入那些仍采用 32 位序列号的公共 API 时扩大序列号。
以下是提交的 libxcb 错误报告的引述(实际电子邮件已删除):
我们有一个执行大量 XDrawString 和 XDrawLine 的应用程序。几个小时后,应用程序因 XIOError 退出。
XIOError 在文件 xcb_io.c 中的 libX11 中调用,函数 _XReply。它没有得到 xcb_wait_for_reply 的响应。
libxcb 1.5 很好,libxcb 1.8.1 不是。二等分 libxcb 指向此提交:
提交 ed37b087519ecb9e74412e4df8f8a217ab6d12a9 作者:Jamey Sharp 日期:2010 年 10 月 9 日星期六 17:13:45 -0700
xcb_in: Use 64-bit sequence numbers internally everywhere.
Widen sequence numbers on entry to those public APIs that still take
32-bit sequence numbers.
Signed-off-by: Jamey Sharp <jamey@xxxxxx.xxx>
在 1.8.1 之上恢复它会有所帮助。
向libxcb添加跟踪我发现用于xcb_wait_for_reply的最后一个请求号是:4294900463和4294965487(_XReply函数的while循环中的两个调用),半秒后:63215(然后调用XIOError)。widen_request 也是 63215,我预计是 63215+2^32。因此,请求似乎没有正确扩大。
上面的提交还将 poll_for_reply 中的比较从 XCB_SEQUENCE_COMPARE_32 更改为 XCB_SEQUENCE_COMPARE。也许扩展从未正常工作,但从未观察到,因为只比较了较低的 32 位。
重现问题
这是提交的错误报告中用于重现问题的原始代码片段:
for(;;) {
XDrawLine(dpy, w, gc, 10, 60, 180, 20);
XFlush(dpy);
}
显然这个问题可以用更简单的代码重现:
for(;;) {
XNoOp(dpy);
}
根据提交的 libxcb 错误报告,这些条件需要重现(假设重现代码在 xdraw.c 中):
- libxcb >= 1.8(即包括提交 ed37b08)
- 32位编译:gcc -m32 -lX11 -o xdraw xdraw.c
- 序列计数器换行。
建议的补丁
可以在 libxcb 1.8.1 之上应用的建议补丁是这样的:
diff --git a/src/xcb_io.c b/src/xcb_io.c
index 300ef57..8616dce 100644
--- a/src/xcb_io.c
+++ b/src/xcb_io.c
@@ -454,7 +454,7 @@ void _XSend(Display *dpy, const char *data, long size)
static const xReq dummy_request;
static char const pad[3];
struct iovec vec[3];
- uint64_t requests;
+ unsigned long requests;
_XExtension *ext;
xcb_connection_t *c = dpy->xcb->connection;
if(dpy->flags & XlibDisplayIOError)
@@ -470,7 +470,7 @@ void _XSend(Display *dpy, const char *data, long size)
if(dpy->xcb->event_owner != XlibOwnsEventQueue || dpy->async_handlers)
{
uint64_t sequence;
- for(sequence = dpy->xcb->last_flushed + 1; sequence <= dpy->request; ++sequence)
+ for(sequence = dpy->xcb->last_flushed + 1; (unsigned long) sequence <= dpy->request; ++sequence)
append_pending_request(dpy, sequence);
}
requests = dpy->request - dpy->xcb->last_flushed;
详细的技术说明
请在下面找到Jonas Petersen 的详细技术解释(也包含在上述错误报告中):
你好,
这里有两个补丁。第一个修复了 32 位序列换行错误。第二个补丁只为另一个相关声明添加了注释。
补丁包含一些细节。以下是可能感兴趣的人的全部故事:
在向服务器发出 4 294 967 296 个请求后,Xlib (libx11) 将使应用程序崩溃并显示“致命 IO 错误 11(资源暂时不可用)”。这就是 Xlib 内部 32 位序列回绕的时候。
大多数应用程序可能很难达到这个数字,但如果它们达到了,它们就有机会神秘死亡。例如,当我开始进行一些压力测试时,我正在处理的应用程序总是在大约 20 小时后崩溃。它使用 gktmm2、像素图和 gc 以每秒 40 帧的速度以全高清分辨率(在 Ubuntu 上)通过 Xlib 进行一些密集绘图。一些优化确实将宽限期延长到大约 35 小时,但它仍然会崩溃。
接下来是令人沮丧的几周的挖掘和调试,以意识到它不在我的应用程序中,也不在 gtkmm、gtk 或 glib 中,而是 Xlib 中的这个小错误,显然自 2006 年 10 月 6 日以来就存在。
花了一段时间才发现数字 0x100000000 (2^32) 具有一定的相关性。(很多)后来证明它只能用 Xlib 复制,例如使用以下代码:
while(1) { XDrawPoint(display, drawable, gc, x, y); XFlush(显示);}
这可能需要一两个小时,但是当它达到 42.94 亿时,它会爆炸成“Fatal IO error 11”。
然后我了解到,即使 Xlib 使用内部 32 位序列号,它们在此过程中也会(巧妙地)扩大到 64 位,以便 32 位序列可以在扩大后的 64 位序列中不中断地进行换行。显然,这肯定有什么问题。
Fatal IO 错误在 _XReply() 中没有得到应有的回复时发出,但原因是在 Xlib 32 位序列号换行时的 _XSend() 中较早。
问题是当它回绕到 0 时,'last_flushed' 的值仍将位于上边界(例如 0xffffffff)。_XSend() (xcb_io.c) 中有两个位置在此状态下失败,因为它们依赖于这些值始终是连续的,第一个位置是:
请求= dpy->请求-dpy->xcb->last_flushed;
在 request = 0x0 和 last_flushed = 0xffffffff 的情况下,它将 0xffffffff00000001 分配给“请求”,然后分配给 XCB 作为请求的数量(数量)。这是主要杀手。
第二个位置是这样的:
for(sequence = dpy->xcb->last_flushed + 1; 序列 <= dpy->request;\++sequence)
在请求 = 0x0(小于 last_flushed)的情况下,永远没有机会进入循环,因此忽略了一些请求。
解决方案是在这两个位置“解包”dpy->request,从而保留与last_flushed相关的序列。
uint64_t unwrapped_request = ((uint64_t)(dpy->request < \ dpy->xcb->last_flushed) << 32) + dpy->request;
如果“request”小于“last_flushed”,它会创建一个临时的 64 位请求编号,该编号设置为第 8 位。然后在两个位置使用它而不是 dpy->request。
我不确定在原地使用该语句是否比使用变量更有效。
require_socket() 中的另一行一开始让我担心:
dpy->xcb->last_flushed = dpy->request = 发送;
那是一个 64 位、32 位、64 位的赋值。当将其分配给“请求”时,它将截断“发送”到 32 位,然后还将截断的值分配给(64 位)“last_flushed”。但这似乎很重要。我添加了一个注释,解释了下一个糟糕的灵魂调试序列问题...... :-)
Jonas Petersen (2): xcb_io: Fix Xlib 32-bit request number wrapping xcb_io: 添加注释解释混合类型双重赋值
src/xcb_io.c | 14 +++++++++++--- 1个文件更改,11个插入(+),3个删除(-)
-- 1.7.10.4
祝你好运!