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

Process.start detachedWithStdio stdout is buffered when not desired #44573

Closed
Sidewinder1138 opened this issue Dec 30, 2020 · 5 comments
Closed
Labels
area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. library-io needs-info We need additional information from the issue author (auto-closed after 14 days if no response)

Comments

@Sidewinder1138
Copy link

Sidewinder1138 commented Dec 30, 2020

I'm trying to use Process.start on Windows to just launch an app (I'm making a CLI tool), and then output that app's stdout/stderr to terminal. It works, but I noticed that the stdout is being buffered, it outputs in "real-time" at first, but then halts, and only when I kill the launched process does it dump all the remaining output.

Here's what I'm doing:

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

I've looked at #5607, but it was just closed as if this isn't a problem, which I'm pretty sure it is, at least on Windows.

I'm running from cmd.exe, inside Windows Terminal.

@a-siva a-siva added area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. library-io labels Jan 5, 2021
@a-siva
Copy link
Contributor

a-siva commented Jan 5, 2021

//cc @aam

@sortie
Copy link
Contributor

sortie commented Jan 5, 2021

Programs usually default to buffering stdout whenever stdout is not a terminal, such as a file or in a pipe in this case (ProcessStartMode.detachedWithStdio). That part is to be expected and has nothing to do with the Dart. Can you confirm that the program in question has switched stdout to be line buffered explicitly, or is correctly flushing stdout at key points? Otherwise it will get the fully buffered behavior in common programming languages whenever stdout is not a terminal.

If the program is in fact flushing its buffers on newlines, and Dart doesn't pipe it, then there is a bug in Dart. But we need to confirm that is the case before jumping to that conclusion.

If you just want to forward stdout and stderr and not capture them, and not input anything on stdin, then you can use ProcessStartMode.detached instead that lets the child process use the same input/output/error handles.

@sortie sortie added the needs-info We need additional information from the issue author (auto-closed after 14 days if no response) label Jan 5, 2021
@Sidewinder1138
Copy link
Author

So, my goal is to write a CLI tool in Dart that you can call from the terminal, and then it will in turn launch another program also in the terminal. I guess the problem is that, I don't know how to tell the process that I'm starting to behave as if it is being run from the terminal, that's what I actually want. Think of it like: I can write a BASH script and have that launch various programs, and their output goes to the terminal as expected... I'd like to do something similar but written in Dart (that way it can be smarter). It might also be helpful to have my Dart app watching stdout/stderr for various reasons, but in the end I need it to be the case that any output from the launched process shows up to the end user in the terminal... On linux there is a trick to force the process you are launching to behave as if it is being called from a terminal, but not sure how to do such a thing on Windows. Hope this makes at least some sense. :)

@no-response no-response bot removed the needs-info We need additional information from the issue author (auto-closed after 14 days if no response) label Jan 5, 2021
@aam
Copy link
Contributor

aam commented Jan 5, 2021

@Sidewinder1138 how quickly does the buffering kick in for you?

I was trying to reproduce this effect and couldn't. Here is the sample program I used - it seems to be producing the output on the console as I ran it in Windows cmd window:

pipe.dart:

import 'dart:io';

main() {
	Process.start('counter.exe', [],
	              mode: ProcessStartMode.detachedWithStdio)
        .then((process) {
          process.stdout.pipe(stdout);
          process.stderr.pipe(stderr);
        }
     );
}```

counter.cc:

#include <stdio.h>

int main(int argc, char** argv) {
for (int i = 0; i < 100010001000; i++) {
printf("printing %d\n", i);
}
}


c:> dart.exe pipe.dart
printing 1
...

@aam aam added the needs-info We need additional information from the issue author (auto-closed after 14 days if no response) label Jan 6, 2021
@no-response
Copy link

no-response bot commented Aug 15, 2021

Without additional information, we are unfortunately not sure how to resolve this issue. We are therefore reluctantly going to close this bug for now. Please don't hesitate to comment on the bug if you have any more information for us; we will reopen it right away!
Thanks for your contribution.

@no-response no-response bot closed this as completed Aug 15, 2021
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`.
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. library-io needs-info We need additional information from the issue author (auto-closed after 14 days if no response)
Projects
None yet
Development

No branches or pull requests

4 participants