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

Jef*_*ans 5 shell scala tty

假设我们有以下Scala(2.12)。它使用ProcessBuilder执行非常简单的命令集(a 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"))
  }
}
Run Code Online (Sandbox Code Playgroud)

zsh5.6.2或GNU bash4.4.23(1)中的OS X上执行时,除非附加了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
Run Code Online (Sandbox Code Playgroud)

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

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

sep*_*p2k 5

尽管没有命令(sleep或echo)都不应尝试从stdin读取,但还是会发生这种情况

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

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

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

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

因此,我的结论是,scala与tty的交互方式会破坏后台进程,java而应使用替代方法。


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