Fabric: 如果 ThreadingGroup 运行包括睡眠,则本地终端标准输入分离

创建于 2018-06-25  ·  22评论  ·  资料来源: fabric/fabric

我正在使用线程组来运行 shell 命令。 运行包含sleep的脚本后,本地终端将与stdin分离(在命令行上看不到按键),并且必须重置终端。

我已经尝试了很多次,发现它只发生在 ThreadingGroups 上(SerialGroups 没问题)。 sleep 命令可以位于一行(第一个命令、中间、最后一个)中的任何位置,并且可以用分号或双与号连接在一行中。 所有命令都按预期运行,但终端仍处于错误状态。

奇怪的是,如果之前的运行以未捕获的异常退出,则终端不会受到影响。

重现:

from fabric import ThreadingGroup as Group

# raise ValueError()
remotes = Group("host1.example.com", "host2.example.com")
result = remotes.run("echo 1; sleep 1; echo 2")

运行上面的脚本。 退出后,在命令行上输入一些内容。 如果没有看到输出,请输入<ctrl>+c并输入reset<enter> 。 要查看异常后行为,请取消注释raise行,运行代码,注释该行,然后再运行两次。 第一次成功运行将使终端处于良好状态。 第二个将使stdin分离。

我在测试中发现sleep存在这个问题,但其他命令可能也有同样的效果。 我也有可能只是做错了什么。 如果是这样,我很抱歉。

我的设置:
蟒蛇 3.6.4
织物 2.1.3
OSX 10.13.5,连接到 Ubuntu 14.04

Bug Needs investigation

所有22条评论

请参阅 #1814 作为可能的第二个可重现问题案例。

这对我来说听起来像是一个合法的错误,我不确定是什么导致了它。 闻起来可能是一个通用的 Unix 问题,终端管道一次连接到多个子进程,或者(特别是 #1814 的例子)围绕管道状态的竞争条件,或类似的东西。

将尝试重现并找出原因/解决方案。

此外,这可能需要在 Invoke 级别进行修复,并且可能完全在其域中(因为我还没有在纯 Invoke 上下文中对线程做太多工作;但请参阅例如 pyinvoke/invoke#194 - 这是一件事也应该在那里发生)。 在这种情况下,我会将其移至那里的票证,而 Fabric“修复”将是在修复发布后升级一个人的 Invoke。

我在 Ubuntu 16.04.2 上连接到相同的。

#1829 中关于同一问题的另一份报告。 这是我的下一个错误修复里程碑,我将专注于下一个 OSS 日(星期一)。

我只是试图重现这个(2.0 分支,Python 3.6.4,macOS 10.12),但不幸的是无法重现。 首先尝试双本地主机,然后是两个单独的远程云实例,两种方式都没有骰子; 之后我的终端就好了。

为了以防万一,打算尝试一下 Linux 容器,但由于 OP 也在 macOS 上,因此不确定它会有所作为。 我还将尝试循环运行它,看看它是否只是偶尔的重现。

如果我们以某种方式在 2.1 中引入它,我也会在 2.1 上尝试它,但这似乎不太可能。

@jensenak @nicktimko你在 100% 的时间复制这个吗? 50%? 5%?

@bitprophet在 2.1.3 上它在我的实际工作流程中经常发生(> 80%,我也并行到 6 个服务器,而不是 2 个),尽管在我从 #1814 的人为示例中它要低得多,可能是 20%。 我可以尝试提出 Docker 设置,或者尝试重新设置 Vagrant 设置失败。

@bitprophet这对我来说是 100% 的时间。 可以肯定的是,我启动了一个新的 virtualenv,只安装了 Fabric。 我测试了 2.0、2.1 和 2.2。 我粘贴的示例代码每次都会产生所描述的行为。 在所有测试中,我都连接到 Ubuntu 14.04 遥控器。

我使用的是不同版本的 OSX (10.13)。 也许这有关系? 尽管@nicktimko根本不在 OSX 上。

如果另一个版本有问题,这里是pip freeze在我的 virtualenv 中的样子:

asn1crypto==0.24.0
bcrypt==3.1.4
cffi==1.11.5
cryptography==2.3
fabric==2.2.1
idna==2.7
invoke==1.1.0
paramiko==2.4.1
pyasn1==0.4.4
pycparser==2.18
PyNaCl==1.2.1
six==1.11.0

看到所有这些都是作为结构 2.2 的依赖项安装的,我希望您的版本看起来相似。

如果我能做更多的事情来帮助,我非常愿意。 只是不太确定还能在哪里看。

我应该测试什么提交; 您最近是否进行了任何可能会影响事情的更改? 我会尝试使用上面的冻结,你也可以提供另一个冻结的reqs.txt ,我可以看看它是否对我有用/不适合我。

@nicktimko @jensenak感谢您提供额外信息。 我会继续尝试在这里重现它; 在 20% 时,我肯定没有尝试过足以触发。 我的遥控器是 Mac 和一些较旧的 Debian,我可以尝试 Ubuntu Trusty,以防万一它是特定的(这很奇怪,但是嘿,这整个事情很奇怪。)

另外,你的本地shell环境是什么? 我的是 tmux 内的(同样是 macOS 10.12)内置 Terminal.app 上的 zsh。 稍后我还将尝试围绕该角度进行一些排列。

啊哈。 这似乎是特定于 bash 的! 仍然无法在 tmux 之外的 zsh 下重现,但是当我在 bash 下尝试时,我立即得到了提到的症状。 同样在 tmux 内部,因此 tmux 没有任何意义 - 它是一个外壳。

_Why_ 这在 bash 和 zsh 下的行为会有所不同,我不知道。 可能特定于它们是如何实现的,或者(似乎更有可能)我的 zsh dotfiles 中的某些东西正在阻止这个问题? 将不得不挖掘……尽管最有可能在 Python 方面确定解决方案。

编辑:此外,即使同时多次连接到我的本地主机的 sshd 也会发生复制,这并不奇怪。 所以远端似乎无关紧要。

此外,我试图验证有关“上一次运行除外仅防止下一次运行出现问题”的注释,但这对我来说没有发生; 无论如何,我每次都会得到这种行为。

Moar:我删除了sleep看看会发生什么; 我仍然能够重现,尽管它现在有点间歇性(尽管因为这在自动循环中不容易重现,所以全部是手工重现,这意味着测试用例的数量很少,这意味着真实的发生率将是真实的很难准确测量。)

这也很好,怪异的触发器越少越好。 这闻起来像是_应该_是某个地方的一些基本的、愚蠢的线程错误,除了使竞争条件(或 w/e)更有可能的时间长度外,通常不会受到远程或本地端任何特定内容的影响。

想知道这是否与 pyinvoke/invoke#552 相关,这归结为 Invoke 的异常处理线程子类(在此处的 ThreadingGroup 中使用)可能搞砸了线程死亡检测。

我必须确保我明白(它的潜在修复,pyinvoke/invoke#553,不是一个 insta-merge,因为我们得到了一些明显功能性的东西似乎很奇怪,所以错误)然后看看应用它是否这种症状消失。

我取消了睡眠,看看会发生什么; 我仍然能够重现,尽管它现在有点断断续续

听起来像我的测试用例,我需要在它出问题之前点击它几次。 看来你对它有很好的把握

我今天注意到我也无法重现我一个月前描述的异常行为......不幸的是我不记得我当时在做什么。 :/

我确实在这里运行 bash。 好找。 这个问题在没有睡眠的情况下是间歇性的,这一事实让我怀疑这是否是某种竞争条件。

你这么说,但现在我不能再复制它了,或者至少它是非常断断续续的。 重新进入睡眠会使它更频繁地出现。 必须喜欢比赛条件。

看那个Invoke问题,记者甚至提到了一个乱七八糟的终端作为症状; 但奇怪的是,即使在 bash 下,我也无法用他的代码重现 _that_ 症状。 如果根本原因相同,仍然不会感到惊讶(与线程死亡和标准输入关闭有关,或者可能在退出之前正确设置回逐行缓冲)。

检查其他问题提到的点,针对这里的重现案例:

  • ExceptionHandlingThread.is_dead位似乎无关紧要,它可能是正确的,这有一定道理,因为它旨在处理线程中的异常,而这些情况中没有一个实际处理异常。 is_deadFalse对于所有 3 个工作线程(stdin/out/err),我希望它是。
  • 我们没有正确关闭子进程的标准输入的断言感觉更接近标记; 如果这让控制终端的标准输入附加到一个现已失效的文件描述符或其他东西......? (无论如何,我真的应该更清楚在这种情况下会发生什么。)

    • 除了……在 Fabric 的情况下,没有本地子进程,也没有文件描述符的直接传递,因此情况并非如此。

    • 这意味着问题更有可能是其他问题吗?


尝试另一种方法......错误出现后终端环境究竟发生了什么变化? 在有和没有错误损坏的情况下在 bash 下运行stty -a ,我可以看到的差异是:

  • lflags :被窃听的终端有-icanon-echo-pendin (与那些都没有减号的常规术语相比)。 假设这就是这意味着什么,不回声当然似乎是一个问题。
  • iflags :bugged-out 有-ixanyignpar (第一个例子是在错误的设置中设置,而不是取消设置)
  • oflagscflags相同, cchars (如果控制字符发生了变化,我真的很奇怪......)

根据man stty

  • icanon控制ERASE和KILL处理; 可能不是很大的差异(为什么设置或取消设置可能很有趣)
  • echo是什么意思,是否回显,显然是这个bug最大的实际问题。
  • pendin说明输入(假定标准输入)在规范切换后是否挂起(并且因为icanon显然被翻转了......是的)并且在读取变为挂起或更多输入时将重新输入到达。 不清楚为什么这很重要,或者为什么它在被窃听时正常设置和未设置(我本来期望后者,如果有的话。)
  • ixany允许任何字符“开始输出”(如果未设置,则只允许开始。好吗?)
  • ignpar表示忽略(或取消设置,不忽略)具有奇偶校验错误的字符。

总而言之,感觉像是某种更高级别的“模式”正在应用于终端,类似于我们如何将 stdin 设置为字符缓冲读取,让我们一次读取 1 个字节,而不是等待用户混搭输入。

这听起来像是显示的行为(有点……),我之前想知道; 但是阅读有问题的代码(因为 Invoke 补丁也提到了它,尽管 re:线程死亡),模式更改被描述为上下文管理器,因此无论我们如何打破该循环,它_应该_总是变得未设置。 但我现在需要三重检查。

次要:只需说stty echo设置echo就足以“修复”终端; 即使icanonpendin等仍未设置。 并没有真正的帮助,但是嘿,很高兴知道我猜。

好的! 我想我明白了,同时盯着那个上下文管理器:这可能是因为上下文管理器快照当前终端状态以在块关闭时恢复。 但是在这种情况下我们在做什么呢? 我们正在运行_两个独立的高级线程_,每个线程都在运行它自己的上下文管理器副本!

在 Invoke 中,虽然我们打算成为线程安全的,但我们目前除了我们自己的低级 IO 线程之外不测试任何东西; 99% 的“线程安全”只是使用自包含的对象状态而不是 Fabric 1 的可怕的全局模块状态。 所以这个特殊的状态保持永远不会与它自己同时运行(部分原因是“状态”实际上是控制终端,其中只有一个,所以......全局状态......)。

我还没有 100% 证明它(即将),但这不可能不是。 第二个运行的线程很可能在第一个线程已经将其设置为字符缓冲模式之后对控制终端属性进行快照; 然后,如果第二个线程也_finishes_ 秒(再次,可能但不确定)它“恢复”坏状态,有效地撤消第一个线程的恢复。

例如,确认 ECHO 标志肯定是由非第一个上下文管理器快照,然后由它恢复。 研究解决方案,我认为最终只会“尝试弄清楚 setcbreak 是否看起来已经应用,并且在这种情况下没有操作,而不是执行快照-修改-恢复舞蹈”。

应该有预期的效果,启动时稍微更干净(从不运行 setcbreak >1 次)并避免极端情况,即简单的修复可能总是将 ECHO 等设置为“on”——这会在有问题的流的情况下中断tty-like 但 _already_ 设置为不回显。 (不太可能,当然,但可能并非不可能。)

由于这是一个 Invoke-only 问题,我将在该跟踪器上给它一个家 - 我希望尽快进行测试并修复此问题,但如果你们还有其他要添加的内容,请转到https ://github.com/pyinvoke/invoke/issues/559

明确地说,一旦修复,它应该在 Invoke 1.0.2/1.1.1 中(如果我同时发布,也可能在 1.2.0 中)并且 _no_ Fabric 升级应该是必要的,只有 Invoke。

@bitprophet太棒了! 它在升级 Invoke 后工作:)
感谢你的付出。

此页面是否有帮助?
0 / 5 - 0 等级