Skip to content

Conversation

@ivanbasov
Copy link
Contributor

@ivanbasov ivanbasov commented May 10, 2018

Customer scenario

Visual Studio closes or restarts and attempts to close and restart Roslyn which attempts to do the same with Interactive Host. Interactive Host attempts to send a message that it is closing back to Roslyn. However, Roslyn disappears just before. This is a race condition.

Bugs this fixes

https://devdiv.visualstudio.com/DevDiv/_workitems/edit/514822

Workarounds, if any

No

Risk

Low

Performance impact

None

Is this a regression from a previous update?

No

Root cause analysis

We haven't considered all possible timing combinations happening between threads.

How was the bug found?

Customer reports (Watson)

@ivanbasov ivanbasov added Bug Area-Interactive Tenet-Reliability Customer telemetry indicates that the product is failing in a crash/hang/dataloss manner. labels May 10, 2018
@ivanbasov ivanbasov added this to the 15.7 milestone May 10, 2018
@ivanbasov ivanbasov requested review from jinujoseph and tmat May 10, 2018 21:43
@ivanbasov ivanbasov requested a review from a team as a code owner May 10, 2018 21:43
@ivanbasov
Copy link
Contributor Author

ivanbasov commented May 11, 2018

retest this please #Resolved

@ivanbasov
Copy link
Contributor Author

ivanbasov commented May 11, 2018

@tmat, @jinujoseph please review #Resolved

@ivanbasov
Copy link
Contributor Author

ivanbasov commented May 14, 2018

@tmat, @jinujoseph please review. This is a regression fix we are going to insert into 15.7 servicing. #Resolved

}
}

await _host.TryGetOrCreateRemoteServiceAsync(processPendingOutput: true).ConfigureAwait(false);
Copy link
Member

@tmat tmat May 14, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

await _host.TryGetOrCreateRemoteServiceAsync(processPendingOutput: true).ConfigureAwait(false); [](start = 20, length = 95)

Why is it ok to run this outside of the lock now? #Resolved

@tmat
Copy link
Member

tmat commented May 14, 2018

                else

if (previousService == null) return default; #Resolved


Refers to: src/Interactive/Features/Interactive/Core/InteractiveHost.cs:348 in 9973ba0. [](commit_id = 9973ba0, deletion_comment = False)

@tmat
Copy link
Member

tmat commented May 14, 2018

            Debug.Assert(currentRemoteService != null);

if (currentRemoteService == null) return default; #Resolved


Refers to: src/Interactive/Features/Interactive/Core/InteractiveHost.cs:328 in 9973ba0. [](commit_id = 9973ba0, deletion_comment = False)

@tmat
Copy link
Member

tmat commented May 14, 2018

        return (_lazyRemoteService?.InitializedService != null &&

Extract variable. #Resolved


Refers to: src/Interactive/Features/Interactive/Core/InteractiveHost.cs:73 in 9973ba0. [](commit_id = 9973ba0, deletion_comment = False)

@tmat
Copy link
Member

tmat commented May 14, 2018

    public void Dispose()

Comment: Dispose may be called anytime. #Resolved


Refers to: src/Interactive/Features/Interactive/Core/InteractiveHost.cs:227 in 9973ba0. [](commit_id = 9973ba0, deletion_comment = False)

@tmat
Copy link
Member

tmat commented May 14, 2018

    private TextWriter _output;

lock (_outputGuard) { _output.Write ... } #Resolved


Refers to: src/Interactive/Features/Interactive/Core/InteractiveHost.cs:41 in 9973ba0. [](commit_id = 9973ba0, deletion_comment = False)

@tmat
Copy link
Member

tmat commented May 14, 2018

    private TextWriter _errorOutput;

lock (_errorOutputGuard) { _errorOutput.Write ... } #Resolved


Refers to: src/Interactive/Features/Interactive/Core/InteractiveHost.cs:42 in 9973ba0. [](commit_id = 9973ba0, deletion_comment = False)

{
ReportProcessExited(process);
return TryGetOrCreateRemoteServiceAsync(processPendingOutput: true);
}
Copy link
Member

@tmat tmat May 14, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep #Resolved

_processExitHandlerStatus = ProcessExitHandlerStatus.Handled;
// Should set _processExitHandlerStatus before calling OnProcessExited to avoid deadlocks.
// Calling the host should be within the lock to prevent its disposing during the execution.
await _host.OnProcessExited(Process).ConfigureAwait(false);
Copy link
Member

@tmat tmat May 14, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

await _host.OnProcessExited(Process).ConfigureAwait(false); [](start = 27, length = 60)

Move outside of the lock and keep. #Resolved

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_host?.OnProcessExited


In reply to: 188128872 [](ancestors = 188128872)

}
}

internal void Dispose(bool joinThreads)
Copy link
Member

@tmat tmat May 14, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

internal void Dispose(bool joinThreads) [](start = 11, length = 40)

Comment: may be called anytime #Resolved

@tmat
Copy link
Member

tmat commented May 14, 2018

        DisposeRemoteService(disposing: true);

before disposing remote service

lock (_outputGuard) _output = TextWriter.Null;
lock (_errorOutputGuard) _errorOutput = TextWriter.Null;
}
``` #Resolved

---
Refers to: src/Interactive/Features/Interactive/Core/InteractiveHost.cs:230 in 9973ba0. [](commit_id = 9973ba043a399d5232fbd0e0fdbab8220706c553, deletion_comment = False)

@ivanbasov ivanbasov requested a review from cston May 15, 2018 00:46

lock(_errorOutputGuard)
{
_errorOutputGuard = TextWriter.Null;
Copy link
Contributor

@cston cston May 15, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_errorOutput = ... #Resolved

private TextWriter _output;
private TextWriter _errorOutput;
private object _outputGuard;
private object _errorOutputGuard;
Copy link
Contributor

@cston cston May 15, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

readonly for both. And do we need two lock objects or could the same object be used for both _output and _errorOutput? #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

  1. Added readonly
  2. Discussed with Tomas to use a common lock or separate ones and found that separate ones should be better to avoid two channels to be executed independently.

In reply to: 188322002 [](ancestors = 188322002)

var writer = error ? ErrorOutput : Output;
var writer = error ? _errorOutput : _output;
writer.Write(buffer, 0, count);
}
Copy link
Contributor

@cston cston May 15, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to lock around writer.Write()? #Resolved

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.


In reply to: 188322596 [](ancestors = 188322596)

lock (_errorOutputGuard)
{
_errorOutput.WriteLine(FeaturesResources.Failed_to_launch_0_process_exit_code_colon_1_with_output_colon, _hostPath, process.ExitCode);
_errorOutput.WriteLine(process.StandardError.ReadToEnd());
Copy link
Member

@tmat tmat May 15, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

process.StandardError.ReadToEnd() [](start = 43, length = 33)

Move process.StandardError.ReadToEnd() out of the lock. #Resolved

}

public TextWriter Output
public void SetOutput(TextWriter value)
Copy link
Member

@tmat tmat May 15, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing line break #Resolved

// disposed or not reset:
Debug.Assert(currentRemoteService != null);
// Remote service may be disposed anytime.
if (currentRemoteService == null) { return default; }
Copy link
Member

@tmat tmat May 15, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{ [](start = 50, length = 1)

style: braces on separate lines #Resolved

}
else
{
if (previousService == null) { return default; }
Copy link
Member

@tmat tmat May 15, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{ return default; } [](start = 53, length = 19)

style: braces on separate lines #Resolved

Copy link
Member

@tmat tmat May 15, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to go after newService.Dispose(joinThreads: false);

Actually, even better: just move if (currentRemoteService == null) { return default; } as the first statement of the for loop and then this is not needed


In reply to: 188362651 [](ancestors = 188362651)

Copy link
Member

@tmat tmat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🕐

oldOutput.Flush();
}
var oldOutput = Interlocked.Exchange(ref _output, value);
oldOutput.Flush();
Copy link
Member

@tmat tmat May 15, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we should now do this:

lock (_outputGuard)
{
  _output.Flush();
  _output = value;
}

And then call SetOutput(TextWriter.Null); from Dispose #Resolved

Copy link
Member

@tmat tmat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@ivanbasov ivanbasov merged commit db46bcf into dotnet:dev15.7.x May 15, 2018
@ivanbasov ivanbasov deleted the interactive branch May 15, 2018 23:53
agocke added a commit to agocke/roslyn that referenced this pull request May 16, 2018
…Microsoft.VisualStudio.InteractiveWindow.dll (dotnet#26770)"

This reverts commit db46bcf.
@ivanbasov ivanbasov restored the interactive branch May 16, 2018 18:09
agocke added a commit that referenced this pull request May 16, 2018
…Microsoft.VisualStudio.InteractiveWindow.dll (#26770)" (#26892)

This reverts commit db46bcf.
ivanbasov added a commit to ivanbasov/roslyn that referenced this pull request Jun 13, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Approved to merge Area-Interactive Bug Tenet-Reliability Customer telemetry indicates that the product is failing in a crash/hang/dataloss manner.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants