Open LBensman opened 6 months ago
Tagging subscribers to this area: @dotnet/area-system-diagnostics-process See info in area-owners.md if you want to be subscribed.
Its very likely number 3 will break something, although, I can't think of a case where you want to keep holding StandardOutput or StandardError after you are done with the Process.
@LBensman do you want to go further and edit the documentation? Adding a paragraph in Remarks should be good to clarify this nuance.
Summary
Per https://github.com/dotnet/runtime/blob/d099f075e45d2aa6007a22b71b45a08758559f80/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.cs#L893-L919, the closure and disposal of
StandardInput
,StandardOutput
, andStandardError
stream is not straight forward, and ownership of said streams depends on the usage of the said streams.Documentation, however, does not reflect this nuanced complexity, leaving the consumer in the dark: the examples of usage appear to be incorrect, leading to potential leak and exhaustion of resources.
Specifics
Per referenced code, including the code comment, it appears that ownership of standard streams depends on if and how such streams were used. If I read the code comment and referenced code snippet correctly,
Process
instance owns the standard streams, until and unless those streams were used in non-async operation.1 So if.StandardOutput
property is referenced, the caller takes ownership away from Process and assumes the ownership going forward. If stream is consumed viaBeginOutputReadLines()
, thenProcess
retains ownership of stream and responsible for its disposal.The documentation, however, does not reflect this information.
Process.StandardOutput
documentation does not mention ownership, and perhaps I'm blind to some .NET's convention, but do look at the example on that very page:The line
StreamReader reader = process.StandardOutput;
references the stream, thus taking ownership. Yetreader
is not used inusing
auto-disposing construct, nor does example have a call toreader.Close()
to release the reader.Thus, it appears that the example in the documentation falls victim to this ambiguity and the example actually has a resource leak due to failure to dispose reader and stream.
Same for
Error
andInput
equivalents.1 It's confusing a bit with modern TPL-based
async
usage -- async here appears to implyBeginOutputReadLines()
and similar, and notStream.ReadAsync()
that most would otherwise understand as asynchronous.Summary of issue and Recommendations
I can think of few considerations here:
Process
own all of its related resources. It seems that the streams (and their readers) are intrinsically linked toProcess
, and onceProcess
is closed and disposed of, the streams are no longer meaningfull. It would make sense thatProcess
disposal implies standard streams disposal as single coherent operation. I'm aware that this proposed change may violate existing contracts and may not be feasible (though, as mentioned, if streams are no longer meaningful, it doesn't seem like it can violate and otherwise valid use case, but I can't be certain here, so leave it up to you to evaluate this).Scope
I've reviewed code and documentation for releases 7, 8, and 9. I didn't review others, but suspect those are affected as well.