5

Suppose we have the following Scala (2.12). It is using ProcessBuilder to execute a very trivial set of commands (a sleep, followed by an echo). The program also captures both stdout and stderr, and prints all lines - with those respective prefixes - to either stdout or stderr of the Scala process itself.

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"))
  }
}

When executed on OS X, either in zsh 5.6.2 or GNU bash 4.4.23(1), the process becomes suspended due to tty output unless stdin is attached. This happens despite the fact that neither command (sleep nor echo) should be trying to read from 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

If the program is simply run in the foreground to begin with, then it also doesn't become suspended. Any ideas what could be causing this?

Running stty -tostop in either the terminal that launches scala, or in a new script file that invokes these same commands, and is then invoked by scala, has no effect on this behavior.

Jeff Evans
  • 1,257
  • 1
  • 15
  • 31
  • scala doesn't have anything to do with job control. It's the shell/terminal (see linked answer) that stops jobs from producing background output. – Dima Apr 04 '19 at 01:35
  • @dima Then how do you explain the fact that it doesn't suspend if you run it with `java` instead of `scala` (see my answer)? Or the fact that it suspends despite stdout and stderr being redirected (so there isn't actually any output)? – sepp2k Apr 04 '19 at 11:58
  • @sepp2k no, it does not get stopped if output is redirected. And it DOES (at least for me), when ran with `java`. Not sure why it didn't for you, I tried it with oracle jdk 1.8, and openjdk 1.11, both suspend on output (as they should). – Dima Apr 04 '19 at 12:15
  • @dima OP is redirecting the output in the question. Are you saying that if you run the exact same command as OP (including the redirection), you don't get a suspended process? Because I do and clearly OP does, too. Also do you get a suspended process for `echo hello&`? Because the linked answer and your comment make it sound as if that should happen (i.e. that it would suspend on regular output), but I've never seen a system where that's the default behaviour. – sepp2k Apr 04 '19 at 12:29
  • Yes, if output is redirected, it will not be suspended.With `echo hello&` you will, probably, not get it suspended, because if completes before it's put into background. But with something like `(sleep 5 && echo hello)&` (which is what OP does), it will get suspended (unless your terminal has `-tostop`, which seems to be the default on many terminals nowadays, so, if you can't get it to suspend like that, try `stty tostop`, and then run it again). – Dima Apr 04 '19 at 12:56
  • @Dima "Yes, if output is redirected, it will not be suspended." That is not true on OP's system or mine. And that's why this isn't a duplicate. Note that `(sleep 5 && echo hello)&` works fine on my system (and I maintain that that has been the default for at least two decades - not just nowadays), but OP's command still causes a suspend on my system. So clearly the issue with OP's command (which again: redirects both stdout and stderr) is something else and this is not a duplicate. – sepp2k Apr 04 '19 at 14:00
  • @sepp2k what is "your system"? It is a duplicate, because it is the same question as the other one ... even if you are not satisfied with the answer. – Dima Apr 04 '19 at 14:04
  • @Dima My system is Scala 2.12.8 in bash or zsh on urxvt on Arch Linux, but it also occurs on OSX (with iTerm2 and the default Apple Terminal using bash, zsh or fish and the same Scala version). I suspect that the only thing that matters is the Scala version. Specifically the `scala` shell script calls `stty`, which I assume is what causes the issue. – sepp2k Apr 04 '19 at 14:15
  • Regarding the duplicateness of this, I strongly feel that "I have this command that redirects all output and still is suspended because of supposed 'tty output'" is a fundamentally different question then "I have this command that actually produces output and is suspended because of 'tty output'". – sepp2k Apr 04 '19 at 14:15
  • @sepp2k if "on your system" a command that redirects output is suspended ... because of a terminal output, that's certainly a bug in "your system". On _my system_ I cannot reproduce this behavior, (and OP didn't say it either - he only said that the original command got suspended, which is correct and expected). – Dima Apr 04 '19 at 14:24
  • @Dima As I said, I already tested on two systems, and OP did say that. OP is running the command `scala BackgroundProcessRedirect >/tmp/scala.out 2>/tmp/scala.err &`, which does redirect both stdout and stderr. And OP is saying that this command is suspended. So both on my system, my colleague's OSX system and OP's system the above command is suspended despite redirecting both stdout and stderr (and not actually producing any output even if you use `fg` to put it in the foreground). And that's what this question is about. – sepp2k Apr 04 '19 at 14:28
  • He said that he ran the command, but did not say that it got suspended. I ran it on OSX, Ubuntu, and CentOS, and got very consistent, and expected results. Not sure what else is left here to discuss. I am going to shut up now. – Dima Apr 04 '19 at 14:31
  • @Dima From the OP: `[1] + suspended (tty output) scala BackgroundProcessRedirect > /tmp/scala.out 2> /tmp/scala.err` – sepp2k Apr 04 '19 at 14:53
  • @sepp2k Ah, yeah, you need to disconnect stdin too. You are correct, that's because scala script does `stty` (and java does not). So, the behavior is: without redirect both scala and java get stopped. With redirect of all three streams, neither scala nor java is stopped. Redirect stdout and sterr, but _not_ stdin - scala is stopped, and java is not (because of the stty call). – Dima Apr 04 '19 at 15:15
  • To clarify, yes, it was getting suspended, I thought I stated that. And I can confirm that running the same via `java` doesn't (thanks @sepp2k). This was a trivial example, but my actual problem has to do with a more complicated command (`spark-shell`), so I suspect that it is doing something funky with tty, the same way that `scala` is here. Regardless of that, I found the behavior here perplexing (redirecting stdin from `/dev/null` stopping the suspension despite the shell reporting it was due to `tty output`). I also updated the question to illustrate how the linked answer doesn't help. – Jeff Evans Apr 04 '19 at 15:55
  • For me, I had to do `stty tostop` to even reproduce any of this. And `stty -tostop` definitely does help )to turn it back to default). You do need to run it before launching scala, beyond that, I am not sure how to help ... Other than suggesting to just disconnect from stdin too, because it makes sense. – Dima Apr 04 '19 at 22:44

1 Answers1

5

This happens despite the fact that neither command (sleep nor echo) should be trying to read from stdin

Note that the message says "tty output", not "tty input". So it's complaining about the fact that the process produces output, not that it reads from stdin. That's weird for two reasons:

  1. Regular output to stdout or stderr doesn't usually cause background processes to be suspended
  2. You're redirecting stdout and stderr anyway, so even if your terminal were setup to suspend on output, that shouldn't happen in this case.

Suspension on output usually only happens if the process uses tty features beyond just writing to the standard streams (such as writing to specific position on the screen or generally anything that requires curses or similar functionality).

Your program doesn't do this, but apparently the scala binary does¹. At least I can reproduce your problem when trying to run any Scala program with scala className&, but it goes away when I use java -cp /usr/share/scala/lib/scala-library.jar:. className& instead.

So my conclusion is that scala interacts with the tty in a way that breaks background processes and a workaround is to use java instead.


¹ After looking at /usr/bin/scala, it looks like it's calling stty to save and restore the terminal settings. Apparently that doesn't work if the process is run in the background (and thus not properly connected to tty) and that's why the process is suspended.

sepp2k
  • 363,768
  • 54
  • 674
  • 675
  • The Scala code delegates to Java, specifically the Java ProcessBuilder. I can recreate this exact same situation in Java, just found the Scala example program to be more concise. I could augment my answer with that, if it would be useful. – Jeff Evans Apr 03 '19 at 22:14
  • 1
    @JeffEvans I wasn't suggesting to replace your Scala code with Java, but to use `java` instead of `scala` to run it. What I'm saying is that I get the same issue as you when running your code with `scala BackgroundProcessRedirect&` (or even `scala HelloWorld&`), but the issue goes away with `java -cp /usr/share/scala/lib/scala-library.jar:. BackgroundProcess&` (without changing your code at all). – sepp2k Apr 03 '19 at 22:23
  • Thanks, @sepp2k, you are correct that running the exact same thing with `java` does not result in the suspension. – Jeff Evans Apr 04 '19 at 15:56