diff --git a/sdk/lib/_internal/vm/bin/process_patch.dart b/sdk/lib/_internal/vm/bin/process_patch.dart index 76c36d85db2..c267d2f2b5e 100644 --- a/sdk/lib/_internal/vm/bin/process_patch.dart +++ b/sdk/lib/_internal/vm/bin/process_patch.dart @@ -282,6 +282,11 @@ base class _ProcessImpl extends _ProcessImplNativeWrapper implements _Process { if (_modeHasStdio(_mode)) { // stdin going to process. _stdin = new _StdSink(new _Socket._writePipe().._owner = this); + // Ignore errors if the `Process.stdin.done` future is not consumed. + // Developers catch errors writing to `Process.stdin` by consuming + // `Process.stdin.done` or calling `Process.stdin.flush()`. + _stdin!.done.ignore(); + // stdout coming from process. _stdout = new _StdStream(new _Socket._readPipe().._owner = this); // stderr coming from process. diff --git a/sdk/lib/io/process.dart b/sdk/lib/io/process.dart index 5acfa04a23d..fef648aeb53 100644 --- a/sdk/lib/io/process.dart +++ b/sdk/lib/io/process.dart @@ -492,6 +492,29 @@ abstract interface class Process { Stream> get stderr; /// The standard input stream of the process as an [IOSink]. + /// + /// `stdin` is implemented as a pipe between the parent process and the + /// spawned subprocess. + /// + /// Data added to the [IOSink] (E.g. `Process.stdin.writeln('Hello!')`) is + /// written to to the pipe asynchronously. + /// + /// Errors writing the data (e.g. due to the subprocess exiting) can be + /// caught by awaiting `Process.stdin.flush()`. For example: + /// + /// ```dart + /// import 'dart:io'; + /// + /// main() async { + /// final process = await Process.start('false', const []); + /// process.stdin.writeln('Hello World\n'); // May already have exited. + /// try { + /// await process.stdin.flush(); + /// } catch (e) { + /// print(e); + /// } + /// } + /// ``` IOSink get stdin; /// The process id of the process. diff --git a/tests/standalone/io/process_stdin_broken_pipe_test.dart b/tests/standalone/io/process_stdin_broken_pipe_test.dart new file mode 100644 index 00000000000..4b99b236229 --- /dev/null +++ b/tests/standalone/io/process_stdin_broken_pipe_test.dart @@ -0,0 +1,54 @@ +// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +// +// Verify that failing to write to `Process.stdin` results in an exception +// being thrown by `process.stdin.flush()` and `process.stdin.done`. +// +// See https://github.com/dart-lang/sdk/issues/48501 +// +// VMOptions= +// VMOptions=--short_socket_read +// VMOptions=--short_socket_write +// VMOptions=--short_socket_read --short_socket_write + +import "package:expect/expect.dart"; +import 'dart:async'; +import 'dart:io'; +import 'dart:math'; + +import "process_test_util.dart"; + +Future test(Process process) async {} + +void main() async { + if (!Platform.isLinux && !Platform.isMacOS) { + print('test not supported on ${Platform.operatingSystem}'); + return; + } + + final process = await Process.start('false', const []); + try { + for (var i = 0; i < 20; ++i) { + // Ensure that the pipe is broken while we are writing. + process.stdin.add([1, 2, 3]); + await Future.delayed(const Duration(milliseconds: 50)); + } + + try { + await process.stdin.flush(); + Expect.fail('await process.stdin.flush(): expected exception'); + } on SocketException catch (e) { + Expect.equals(32, e.osError!.errorCode); // Broken pipe + } + + try { + await process.stdin.done; + Expect.fail('await process.stdin.done: expected exception'); + } on SocketException catch (e) { + Expect.equals(32, e.osError!.errorCode); // Broken pipe + } + } finally { + process.kill(); + } +}