掘金 后端 ( ) • 2024-07-01 09:58

highlight: dracula theme: Chinese-red

0 背景

这是一篇用来发泄情绪的文章,被一个愚蠢的问题折腾了好久。

不想看背景的可直接阅读 1 问题 章节,或 2.3 演示

情况是这样的,最近接手了一个 Windows 的 Golang 项目,它是一个多节点运行的服务。由于公司信息安全的特殊规定,我们必须将编译后的程序放到固定的远程虚拟机中运行测试,而不能在我们的本机上调试。有过经历的读者们可能知道,公司的那种虚拟机吧,无法连外网,且环境非常简单。终端程序只有 Windows 自带的 Windows Powershell 和 cmd。于是我选择了 Powershell 作为我的调试终端。

平时我的工作开发环境是 Ubuntu。工作之外的个人开发,也多用的 Ubuntu。虽然有时也会在我的 Windows 上做开发,但终端程序使用的都是 Windows Terminal 这样更现代的终端,或 tabby 这种第三方终端,亦或者是 VS Code 等编辑工具自带的终端。可以说,我几乎从来没有使用 Windows Powershell 调试程序的经验。

这是我接手该项目后,第一次调试该程序。而问题,就发生在 Windows Powershell 上。

1 问题

描述一下问题发生的经过。

因为这个服务需要多端通信,因此我分别在两台 Windows 虚拟机上运行该程序,用的都是 Powershell 终端。所以我得在两个虚拟机之间来回切换看运行情况;又因为该服务会将内部不同模块的日志分别打印在不同的地方,譬如有的日志分布在不同的日志文件中,有的日志则直接打印在终端标准输出上。所以即使在同一个虚拟机里,我也得在各种日志文件以及 Powershell 终端之间切换查看运行日志。

很忙的调试工作对吧,确实如此。于是在这繁忙的切换过程中,我发现了一个神奇的现象。

有时候,程序的某些逻辑突然就不执行了,仿佛整个程序卡死了。但是,有些日志文件又依然在增加日志,有的却没有。问题非必现,并且似乎没有任何规律。可能突然某一瞬间就卡住了,也可能运行半个小时也无事发生。

这让我百思不得其解。首先想到的肯定是程序本身的逻辑 bug,譬如,哪里死锁了。但无论我如何改代码,问题依然在发生,且依然没有任何业务逻辑上的规律。

2 原因及解决方法

2.1 原因

我完全没有往终端本身的问题上去想,一是是因为没有经验,另外也是我自己想当然了。直到我注意到两个现象:

  1. 日志文件的日志每次卡死时阻塞和继续写的可能不相同,但终端上的输出一定是阻塞的;
  2. 每当这种时候我都需要按下两次 ctrl + c 才能把程序终止。

两个现象的同时出现,让我终于认识到也许不是程序的问题,而是终端阻塞了程序。于是我开始去网上查,终于找到了问题的根本原因。

在 Windows 自带的终端软件中,无论是 cmd 还是 Powershell,都提供了一个叫快速编辑的功能。这个功能的初衷是让用户更方便地选中终端上的文字。于是,在用户用鼠标点击,选中终端上的内容时,哪怕只是一个空白字符,终端就会阻塞 stdout/stderr,导致程序在输出终端日志的地方阻塞,无法继续执行。这时,需要用户按下回车键,或 ctrl + c,程序才会继续执行。

我因为要来回切换终端、日志文件,以及虚拟机本身,再加上虚拟机运行总归会有一些不顺畅,因此在操作的过程中可能无意间就触发了终端的该特性,导致程序阻塞。

2.2 解决方法

解决方法也很简单,在终端的属性设置里,取消勾选快速编辑即可,也就是不用这个功能。当然如果有条件的话,最好还是直接用更现代的终端软件。

那么那些更现代的终端软件是如何解决用户选中终端上的输出文字的呢?以 Window 自家的 Windows Terminal 为例,当用户做出鼠标拖选动作时,终端会停止自动下滚,让终端上的输出画面停在用户拖选的时刻,但不阻塞程序。也就是说,程序依然在输出文字,只是终端没有切到程序最新的输出行。

2.3 演示(ScreenToGif)

本节来演示上面讨论的问题,以及该问题在 Windows Terminal 下的表现。演示用到了一段简单的代码:

func main() {
num := 0
for {
log.Printf("Output, %d", num)
num++
time.Sleep(1 * time.Second)
}
}

下图演示了 cmd/powershell(我这里用的是 cmd,powershell 同理)下问题的出现以及取消快速编辑后的效果:

终端阻塞

可以看到,程序打印时,我的鼠标随意点击一个地方或拖选一个区域,就会使整个程序的输出阻塞,直到我按下回车键,或 ctrl + c,阻塞才会解除。而当我在属性中关闭了快速编辑后,再次点击终端,则不会再出现阻塞问题。当然,此时也无法选择输出的文字了。

下面是 Windows Terminal 的表现:

Windows Terminal 表现

很酷炫,Windows Terminal 中,不但可以方便地选中输出的文字,而且还不会阻塞终端上的输出。

3 结语

就,如果有条件的话,尽量就别用 Windows 自带的 cmd 或 powershell 了。

文章内容有些罗嗦,毕竟有吐槽的成分在。但无论如何,希望本文的内容能帮助到每一个阅读过本文的读者,感谢阅读和支持!