Tcl fileevent 在 64 位版本的 Tcl 上挂起

Tcl fileevent hangs on 64 bit version of Tcl

我在 Linux 上使用 64 位 8.4.3 Tcl 非线程,我遇到了一个奇怪的行为。

我的 C++ 应用程序有一个计时器,它使用 XtAppProcessEvent 执行一些 Xt 处理程序。其中一个处理程序调用 Tcl_DoOneEvent.

我有一个 Tcl 脚本,它打开一个空管道并将一个文件事件附加到打开的通道。

set jobId [open "| "]
fileevent $jobId readable

这会完成多次。一段时间后,当频道名称为 file35 时,该工具会挂起。使用 Tcl 库的调试版本。表明在执行了以下部分后readyMasks[0]一直为0:

// tclUnixNotfy.c:772
numFound = select(tsdPtr->numFdBits,
    (SELECT_MASK *) &tsdPtr->readyMasks[0],
    (SELECT_MASK *) &tsdPtr->readyMasks[MASK_SIZE],
    (SELECT_MASK *) &tsdPtr->readyMasks[2*MASK_SIZE], timeoutPtr);

输入检查掩码为 72 (1001000)。


这是奇怪的部分:

当我到达工具挂起的部分时,如果我打开一个新的 shell 选项卡,该工具将不再挂起并按预期继续执行。打开新的 shell 选项卡时,readyMasks 变为 72。

该工具在32位时运行正常。我无法将发生的事情与 64 位联系起来。

我已经在 redhat 5、6 和 7 上试过了,没有任何区别。

所以,我想通了这个问题。

这个问题基本上是由于 integer 而不是 long 造成的。这在 Tcl 中是在 tclUnixChan.c 中完成的,同时计算需要在 tsdPtr->checkMasks:

中修改的索引和位
index = fd/(NBBY*sizeof(fd_mask)); 
bit = 1 << (fd%(NBBY*sizeof(fd_mask)));

位线需要1L <<而不是1 <<fd_mask 本身是一个 long 并在 sys/types.h 中定义。这只是问题的一部分。

sys/types.h 也有在设置或清除 fd_masks 中的位时应使用的宏定义(FD_CLRFD_SETFD_ISSETFD_ZERO).

此问题已在版本 8.4.9 中部分修复,并在版本 8.4.20 中完全修复。

修复是通过使用 types.h 中定义的宏而不是手动操作 fd_mask 位来完成的。

版本 8.4.9 中引入的部分修复在 tclUnixNotfy.c:

  1. struct SelectMasks 已创建,它使用 fd_settypes.h
  2. 中定义的类型
  3. ThreadSpecificData checkMasksreadyMasks 字段从 fd_mask 数组更改为 SelectMasks
  4. Tcl_CreateFileHandlerTcl_DeleteFileHandlerTcl_WaitForEventNotifierThreadProc 中用于处理掩码的
  5. indexbit 变量被删除并FD_CLRFD_SETFD_ISSETFD_ZERO 被代替。
  6. Tcl_WaitForEventreadyMasks 分配给 checkMasks 而不是在调用 select
  7. 之前使用 memcpy
  8. 现在删除了调用 select 时将 readyMasks 转换为 SELECT_MASK *& 运算符现在用于 SelectMasks 类型的结构元素 fd_set直接。

版本 8.4.20 中引入的修复的另一部分在 tclUnixChan.c:

  1. indexbitTclUnixWaitForFile 中用于处理掩码的变量已被删除,并且 FD_CLRFD_SETFD_ISSETFD_ZERO 被改用了。
  2. TclUnixWaitForFile. 中使用
  3. fd_set 类型而不是创建 fd_mask 数组
  4. 在调用 select 时将掩码转换为 SELECT_MASK * 现在已被删除,& 运算符现在直接用于创建的 fd_set 变量。

我已经根据 8.4.98.4.20 中所做的更改对当前版本的 8.4.3 进行了修补,因为我没有升级整个 Tcl 的灵​​活性]版本。


我关于为什么要打开一个新的 shell window 让工具起作用的理论:

由于在错误大小的容器中移动并使用具有奇怪长度 3*MASK_SIZEmemcpy 导致内存损坏,我最终指向的 fd 是 window 管理相关因此为什么打开一个新的 shell 得到 select 到 return 非零值,因为在我指向的那些错误的 fd 频道上有数据。

让我得出这个理论的是 strace 输出以及 lsof 输出:

strace 挂起时的输出重复了以下部分:

poll([{fd=4, events=POLLIN}, {fd=5, events=POLLIN|POLLPRI|POLLRDNORM|POLLRDBAND}], 2, 0) = 0 (Timeout)
recvfrom(4, 0x80e21f4, 4096, 0, 0, 0)   = -1 EAGAIN (Resource temporarily unavailable)
recvfrom(4, 0x80e21f4, 4096, 0, 0, 0)   = -1 EAGAIN (Resource temporarily unavailable)
poll([{fd=4, events=POLLIN}, {fd=5, events=POLLIN|POLLPRI|POLLRDNORM|POLLRDBAND}], 2, 0) = 0 (Timeout)
recvfrom(6, 0x8380d64, 4096, 0, 0, 0)   = -1 EAGAIN (Resource temporarily unavailable)
select(36, [3 6], [], [], {0, 0})       = 0 (Timeout)
recvfrom(6, 0x8380d64, 4096, 0, 0, 0)   = -1 EAGAIN (Resource temporarily unavailable)
recvfrom(6, 0x8380d64, 4096, 0, 0, 0)   = -1 EAGAIN (Resource temporarily unavailable)
select(36, [3 6], [], [], {0, 0})       = 0 (Timeout)
recvfrom(6, 0x8380d64, 4096, 0, 0, 0)   = -1 EAGAIN (Resource temporarily unavailable)
poll([{fd=4, events=POLLIN}, {fd=5, events=POLLIN|POLLPRI|POLLRDNORM|POLLRDBAND}], 2, 0) = 0 (Timeout)
rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
rt_sigaction(SIGCHLD, NULL, {0x4c3cd70, [CHLD], SA_RESTORER|SA_RESTART|SA_SIGINFO, 0x37eec0f790}, 8) = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0

strace 当我打开一个新的 shell 选项卡时输出更改为:

poll([{fd=4, events=POLLIN}, {fd=5, events=POLLIN|POLLPRI|POLLRDNORM|POLLRDBAND}], 2, 0) = 1 ([{fd=4, revents=POLLIN}])
recvfrom(4, "X6140=[=12=][=12=]7[=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=]"..., 4096, 0, NULL, NULL) = 256
recvfrom(4, 0x80e21f4, 4096, 0, 0, 0)   = -1 EAGAIN (Resource temporarily unavailable)
recvfrom(4, 0x80e21f4, 4096, 0, 0, 0)   = -1 EAGAIN (Resource temporarily unavailable)
poll([{fd=4, events=POLLIN}, {fd=5, events=POLLIN|POLLPRI|POLLRDNORM|POLLRDBAND}], 2, 0) = 0 (Timeout)
recvfrom(6, "X1/40=[=12=][=12=]7[=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=]"..., 4096, 0, NULL, NULL) = 256
recvfrom(6, 0x8380d64, 4096, 0, 0, 0)   = -1 EAGAIN (Resource temporarily unavailable)
select(36, [3 6], [], [], {0, 0})       = 1 (in [3], left {0, 0})
recvfrom(6, 0x8380d64, 4096, 0, 0, 0)   = -1 EAGAIN (Resource temporarily unavailable)
read(35, "", 4096)                      = 0
close(35)                               = 0

lsof 输出显示通道 46 的以下内容:

myexec 13626 aymansalah    4u  IPv4 1607796326       0t0        TCP rhe6x64:38787->nx-svr:7016 (ESTABLISHED)
myexec 13626 aymansalah    6u  IPv4 1607837231       0t0        TCP rhe6x64:38788->nx-svr:7016 (ESTABLISHED)

通道 46 是将我的机器连接到 NX 服务器的通道。


参考文献: