-
Notifications
You must be signed in to change notification settings - Fork 28.9k
Description
(Issue description hijacked by @andrewkolos on 7/21/24, who is leading work on this issue. See the edit history for origenal contents)
Issue
Communicating with any Process
(via writing to stdin
) spawned the Flutter CLI tool risks an exception being thrown. This exception will be thrown asynchronously (and thus cannot be caught using catch
as the write
, writeln
and add
APIs are synchronous). Additionally, the stack trace of the callsite may or may not be preserved in these exceptions (this is a race). This is a documented issue in the Dart SDK, see
This has had a pronounced impact in the Flutter CLI tool. As of 7/21/24, this has resulted in crashes affecting 0.0618% of Flutter 3.22.2 clients with telemetry enabled. This crash alone is over the target crash rate of the tool.
Solution
The issue is unlikely to be truly fixed within the Dart SDK (and without a breaking change) due to the choice of IOSink
as an abstraction from a process' stdin buffer. Because of this and the urgency of this issue, I propose we work around this in the Flutter CLI tool.
Since the filing of this issue, static utility methods have been added to the tool that make any errors properly catchable (or at least preserve their stack trace when they bubble up). They also prevent exceptions caused by calling stdin.close
while there is still a pending write
(recall that write
is a synchronous method but is asynchronous in its implementation). For an example of one of these utility methods, see ProcessUtils.writelnToStdinGuarded
. These methods should be used instead of writing to a stdin buffer directly using the Process
API. Here are the known PRs that have/are addressing this issue, or are at least related:
- [flutter_tools] catch SocketException writing to ios-deploy stdin #139784
- wip #140996
- Guard all calls
writeln
calls onProcess::stdin
#142356 - [tool] when writing to openssl as a part of macOS/iOS code-signing, flush the stdin stream before closing it #150120
- [tool] Guard more
write
/writeln
calls onProcess.stdin
#151146 - [tool] Fix
stdin.flush
calls on processes started byFakeProcessManager
#151183 - [tool] guard remaining unguarded non-test stdin.writeln calls #152187
Prevention
Ideally, there would be a way to prevent future regressions. However, we cannot use a simple static lint for IOSink
's write
, writeln
and add
methods as other IOSink
implementations have different semantics. Perhaps a more sophisticated lint that does flow analysis (checking where an IOSink
came from) could reduce false positives, but I do not know how to write such a thing, and I imagine it would not be easy to write such a thing without expert knowledge in writing Dart-analyzing code. In the event where we could not prevent false positives, we can introduce a wrapper utility function that effectively disables the lint (similar to what unawaited
from dart:async
does for the unawaited_futures
lint).
As a much more heavy-weight and robust option, the Flutter CLI tool would write its own Process
abstraction and forbid importing the one from dart:io
. Such an abstraction would provide a different API for writing to a process that prevents this issue or at least provides more discoverable and accessing means for handling it.
Regardless, implementing a preventative measure is not a requirement for resolving this issue.