Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interactive processes do not flush process.stdout until process terminates #5607

Closed
DartBot opened this issue Oct 2, 2012 · 7 comments
Closed
Assignees
Labels
area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. closed-as-intended Closed as the reported issue is expected behavior library-io

Comments

@DartBot
Copy link

DartBot commented Oct 2, 2012

This issue was originally filed by @butlermatt


If using Process.start() to launch an interactive process, you cannot retrieve any of the data from Process.stdout until after the process has terminated in some way. As an example see the following code:
(Assumes linux with BSD games installed)
=== Code ===
import 'dart:io';

void main() {
  var userIn = new StringInputStream(stdin);
  var p = Process.start('/usr/games/adventure', []);
  p.onError = (e) {
    print('Error: $e');
    exit(1);
  };
  
  p.onStart = () {
    p.stdout.onData = () {
      var str = new String.fromCharCodes(p.stdout.read());
      print(str);
    };
    
    var errFromProcess = new StringInputStream(p.stderr);
    errFromProcess.onLine = () {
      var str = errFromProcess.readLine();
      print(str);
    };
    
    userIn.onLine = () {
      var str = userIn.readLine().trim();
      print('Sending: $str');
      p.stdin.writeString('$str\n');

    };
  };
  
// p.onExit = (code) {
// p.close();
// exit(code);
// };
}
=== end Code ===

Now if you run this application, nothing will appear. In command however type in:
no
quit
yes

Each on their own line. no will respond to "do you wish instructions?"
quit will request the process to exit
and yes will confirm the request. Once you type 'yes' then the actual output of the process is THEN sent to the p.stdout.onData() function. However if I uncomment out the p.onExit(); then that data is never received. It only appears to flush the stream if it is closed by the process not by the application.

@kasperl
Copy link

kasperl commented Oct 5, 2012

Added Area-IO, Triaged labels.

@madsager
Copy link
Contributor

madsager commented Oct 5, 2012

Set owner to @sgjesse.

@sgjesse
Copy link
Contributor

sgjesse commented Oct 9, 2012

This is caused by libc which by default puts stdout int buffered mode when it is not connected to a terminal. On Linux stdbuf from GNU cureutils can be used to change the libc default buffering. So changing

  var p = Process.start('/usr/games/adventure', []);

to

  var p = Process.start('/usr/bin/stdbuf', ["--output=L", "/usr/games/adventure"]);

makes the game playable.

For more background see http://www.pixelbeat.org/programming/stdio_buffering/ and the stdbuf man page.


Added AsDesigned label.

@kevmoo
Copy link
Member

kevmoo commented May 14, 2014

Removed Area-IO label.
Added Area-Library, Library-IO labels.

@DartBot
Copy link
Author

DartBot commented Jan 21, 2015

This comment was originally written by mikelewis256...@gmail.com


This still seems to be an issue for other types of executables. For example, this bash script:

=== Code ===
#!/usr/bin/env bash

read -p "name: " name
echo "$name"
=== end Code ===

will fail to display the "name: " prompt. Passing the script through stdbuf as above doesn't help.

@sgjesse
Copy link
Contributor

sgjesse commented Jan 22, 2015

The "name:" prompt should not be displayed, as read only displays it if the input is coming from a terminal. This is from the documentation for the bash read builtin:

              -p prompt
                     Display prompt on standard error, without a trailing new‐
                     line, before attempting to read any input. The prompt is
                     displayed only if input is coming from a terminal.

PS.: The code in the bug no longer runs, as it was pre Dart 1.0 dart:io library. The following works:

import 'dart:convert';
import 'dart:io';

void main() {
  Process.start('./test.sh', []).then((p) {
    p.stdout.transform(new Utf8Decoder()).listen(print);
    p.stderr.transform(new Utf8Decoder()).listen(print);
    stdin.pipe(p.stdin);
  });
}

@DartBot DartBot added Type-Defect area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. library-io closed-as-intended Closed as the reported issue is expected behavior labels Jan 22, 2015
@Sidewinder1138
Copy link

I'm still having this problem, on Windows platform, and I don't seem to be able to find any way to alleviate it.

I'm doing this:
Process.start(exePath, [], workingDirectory: workingDir, mode: ProcessStartMode.detachedWithStdio) .then((process) { process.stdout.pipe(stdout); process.stderr.pipe(stderr); });

It works, and I start to see some of the stdout from the exe I'm launching, but then it stops. When I kill the exe, the rest of the stdout dumps out. Any hints on how to fix this would be greatly appreciated!

gnprice added a commit to gnprice/zulip-flutter that referenced this issue Oct 3, 2023
This provides the guts of an implementation of zulip#60.

I'd have liked to write this in Dart, rather than in shell.
But when running this script interactively (vs. in CI),
a key ingredient for a good developer experience is that the
child processes like `flutter test` should have their stdout
and stderr connected directly to those of the parent script,
i.e. to the developer's terminal.  Crucially, that lets those
processes know to print their output in a form that takes advantage
of terminal features to get a good interactive experience.
The gold standard for a CLI tool is to have a way to control
that choice directly, but `flutter test` and some other
Dart-based tools lack that capability.

And in Dart, as far as I can tell, there simply is no way to
start a child process that inherits any of the parent's file
handles; instead the child's output will always be wired up to
pipes for the parent to consume.  There's advice for how the
parent can copy the output, chunk by chunk, to its own output:
  dart-lang/sdk#44573
  dart-lang/sdk#5607
  https://stackoverflow.com/questions/72183086/dart-dont-buffer-process-stdout-tell-process-that-it-is-running-from-a-termina

but that doesn't solve the problem of the child's behavior
changing when it sees a pipe instead of a terminal.

The nastiest consequence of this behavior is that the output of
`flutter test` is hundreds of lines long, even for our small codebase,
even when only a single test case fails or none at all. That's
fine in CI, but pretty much intolerable for a development workflow.

So, shell it is.  (Python, or Javascript with Node, or many other
languages would also handle this just fine, but would mean an extra
system of dependencies to manage.)  Specifically, Bash.

---

Fortunately, using Bash does mean we get to reuse the work that's
already gone into the `tools/test` script in zulip-mobile.
This commit introduces a bare-bones version, with most of the
features (notably --diff and its friends) stripped out,
and the test suites replaced with a first two for our codebase.

This version also uses `set -u` and `set -o pipefail`, unlike
the one in zulip-mobile, in addition to `set -e`.
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. closed-as-intended Closed as the reported issue is expected behavior library-io
Projects
None yet
Development

No branches or pull requests

6 participants