Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 14 additions & 8 deletions src/Utilities.UnitTests/ToolTask_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,8 @@ public void ToolTaskCanChangeCanonicalErrorFormat()
_output.WriteLine($"[ToolTaskCanChangeCanonicalErrorFormat] engine.Log:\n{engine.Log}");
}

_output.WriteLine(engine.Log);

// The above command logged a canonical warning, as well as a custom error.
engine.AssertLogContains("CS0168");
engine.AssertLogContains("The variable 'foo' is declared but never used");
Expand Down Expand Up @@ -1131,21 +1133,25 @@ public void ToolTaskDoesNotHangWhenGrandchildInheritsPipeHandles()
t.BuildEngine = engine;

// cmd echoes "hello", then starts a background ping that inherits
// pipe handles. cmd exits immediately; ping outlives the 2s EOF timeout.
// pipe handles. cmd exits immediately; ping outlives the 30s EOF timeout.
t.MockCommandLineCommands = NativeMethodsShared.IsWindows
? "/c echo hello & start /b ping -n 10 127.0.0.1 > nul"
: "-c \"echo hello; sleep 10 &\"";
? "/c echo hello & start /b ping -n 40 127.0.0.1 > nul"
: "-c \"echo hello; sleep 40 &\"";

// Set a generous timeout - without the fix this would hang for the full ping duration
t.Timeout = 30000;
// Outer task timeout is generous; the EOF timeout (30s) is what bounds us.
t.Timeout = 60000;

var sw = Stopwatch.StartNew();
bool result = t.Execute();
sw.Stop();

// The tool should complete without hanging.
// The exit code may be non-zero depending on timing, but the key thing
// is that Execute() returns at all rather than hanging forever.
_output.WriteLine(engine.Log);

engine.Log.ShouldContain("hello");
// The task must return within ~30s (EOF timeout) even though the grandchild lives longer.
sw.Elapsed.TotalSeconds.ShouldBeLessThan(35, "ToolTask should be bounded by the 30s EOF timeout, not the grandchild's lifetime");
// The diagnostic message must appear so CI reports show why the wait ended.
engine.Log.ShouldContain("Pipe EOF not received");
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/Utilities/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,9 @@
<data name="ToolTask.EnvironmentVariableHeader">
<value>Environment Variables passed to tool:</value>
</data>
<data name="ToolTask.PipeEOFTimeout" xml:space="preserve">
<value>Pipe EOF not received within {0} seconds. A grandchild process may still be holding the pipe open. Output already delivered has been logged.</value>
</data>
Comment thread
JanProvaznik marked this conversation as resolved.
<data name="ToolTask.ValidateParametersFailed">
<value>MSB6011: Invalid parameters passed to the {0} task.</value>
<comment>{StrBegin="MSB6011: "}</comment>
Expand Down
5 changes: 5 additions & 0 deletions src/Utilities/Resources/xlf/Strings.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Utilities/Resources/xlf/Strings.de.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Utilities/Resources/xlf/Strings.es.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Utilities/Resources/xlf/Strings.fr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Utilities/Resources/xlf/Strings.it.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Utilities/Resources/xlf/Strings.ja.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Utilities/Resources/xlf/Strings.ko.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Utilities/Resources/xlf/Strings.pl.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Utilities/Resources/xlf/Strings.pt-BR.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Utilities/Resources/xlf/Strings.ru.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Utilities/Resources/xlf/Strings.tr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Utilities/Resources/xlf/Strings.zh-Hans.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Utilities/Resources/xlf/Strings.zh-Hant.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 10 additions & 2 deletions src/Utilities/ToolTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1128,10 +1128,18 @@ private void WaitForProcessExit(Process proc)
//
// Use a bounded timeout as a safety net for the grandchild case where
// EOF never arrives because grand child inherited the pipe and keeps it open.
Comment thread
JanProvaznik marked this conversation as resolved.
const int eofTimeoutSec = 2;
const int eofTimeoutSec = 30;

WaitHandle[] eofEvents = [_standardOutputEOF, _standardErrorEOF];
WaitHandle.WaitAll(eofEvents, TimeSpan.FromSeconds(eofTimeoutSec));
bool allEOFReceived = WaitHandle.WaitAll(eofEvents, TimeSpan.FromSeconds(eofTimeoutSec));
if (!allEOFReceived)
{
// Timeout: a grandchild process likely still holds the pipe open.
// Drain whatever data has already arrived before returning.
LogMessagesFromStandardError();
LogMessagesFromStandardOutput();
LogPrivate.LogMessageFromResources(MessageImportance.Low, "ToolTask.PipeEOFTimeout", eofTimeoutSec);
}
}
else
{
Expand Down