是什么导致该Scala程序在后台运行时由于tty输出而挂起?

时间:2019-04-03 21:25:46

标签: scala shell tty

假设我们有以下Scala(2.12)。它使用ProcessBuilder执行一组非常简单的命令(sleep,后跟echo)。该程序还捕获了stdout和stderr,并将所有带有相应前缀的行打印到Scala进程本身的stdout或stderr。

import scala.sys.process._
import scala.language.postfixOps

object BackgroundProcessRedirect {
  def main(args: Array[String]) = {
    val output = "sleep 5" #&& s"echo myoutput" lineStream_! ProcessLogger(line => System.err.println(s"stderr: $line"))
    output.foreach(line => System.out.println(s"stdout: $line"))
  }
}

在OS X上zsh 5.6.2或GNU bash 4.4.23(1)中执行时,除非附加了stdin,否则由于tty输出,该过程将被挂起。尽管没有命令(sleepecho)都不应该尝试从stdin读取数据,还是会发生这种情况。

# first, compile
scalac BackgroundProcessRedirect.scala

# now, run as a background process, redirecting stdout and stderr to files
scala BackgroundProcessRedirect >/tmp/scala.out 2>/tmp/scala.err &

# after 5 seconds, the process is suspended, as in the following jobs output
[1]  + <PID> suspended (tty output)  scala BackgroundProcessRedirect > /tmp/scala.out 2> /tmp/scala.err

# now, foreground the process; it will complete immediately
fg
# the output file contents are as expected
cat /tmp/scala.out
stdout: myoutput

# run the same thing again, but this time redirect stdin from /dev/null
scala BackgroundProcessRedirect </dev/null >/tmp/scala.out 2>/tmp/scala.err &

# now, the process completes normally after 5 seconds (not suspended), and the file contents are as expected
cat /tmp/scala.out
stdout: myoutput

如果程序只是从前台开始运行,那么它也不会挂起。任何想法可能是什么原因造成的?

在启动stty -tostop的终端中运行scala或在调用这些相同命令然后由scala调用的新脚本文件中运行cte对此行为没有影响

1 个答案:

答案 0 :(得分:5)

  

尽管事实上命令(睡眠或回声)都不应该尝试从标准输入中读取,但还是会发生这种情况

请注意,该消息显示为“ tty 输出”,而不是“ tty 输入”。因此,它抱怨该过程产生输出,而不是它从stdin读取。这很奇怪,原因有两个:

  1. 常规输出到stdout或stderr通常不会导致后台进程挂起
  2. 无论如何,您都将重定向stdout和stderr,因此即使将终端设置为在输出上挂起,这种情况也不会发生。

通常仅在进程使用tty功能而不是写入标准流(例如,写入屏幕上的特定位置或通常需要诅咒或类似功能的任何东西)时,才发生输出暂停。

您的程序不会执行此操作,但是显然scala二进制文件¹。尝试使用scala className&运行任何Scala程序时,至少我可以重现您的问题,但是当我改用java -cp /usr/share/scala/lib/scala-library.jar:. className&时,它就消失了。

所以我的结论是scala与tty的交互方式会破坏后台进程,而解决方法是改用java


¹查看/usr/bin/scala之后,似乎正在调用stty以保存和恢复终端设置。显然,如果该进程在后台运行(因此未正确连接到tty)是行不通的,这就是该进程被挂起的原因。