提交 ae9dfe17 编写于 作者: M Matt Warren

Add sync path for loading text

上级 5353525d
......@@ -66,7 +66,12 @@ internal PreviewTextLoader(SourceText documentText)
public override Task<TextAndVersion> LoadTextAndVersionAsync(Workspace workspace, DocumentId documentId, CancellationToken cancellationToken)
{
return Task.FromResult(TextAndVersion.Create(_text, VersionStamp.Create()));
return Task.FromResult(LoadTextAndVersion(workspace, documentId, cancellationToken));
}
internal override TextAndVersion LoadTextAndVersion(Workspace workspace, DocumentId documentId, CancellationToken cancellationToken)
{
return TextAndVersion.Create(_text, VersionStamp.Create());
}
}
}
......
......@@ -101,6 +101,34 @@ public override async Task<TextAndVersion> LoadTextAndVersionAsync(Workspace wor
return textAndVersion;
}
internal override TextAndVersion LoadTextAndVersion(Workspace workspace, DocumentId documentId, CancellationToken cancellationToken)
{
DateTime prevLastWriteTime = FileUtilities.GetFileTimeStamp(_path);
TextAndVersion textAndVersion;
// Open file for reading with FileShare mode read/write/delete so that we do not lock this file.
using (var stream = FileUtilities.RethrowExceptionsAsIOException(() => new FileStream(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete, bufferSize: 4096, useAsync: false)))
{
var version = VersionStamp.Create(prevLastWriteTime);
var text = CreateText(stream, workspace);
textAndVersion = TextAndVersion.Create(text, version, _path);
}
// Check if the file was definitely modified and closed while we were reading. In this case, we know the read we got was
// probably invalid, so throw an IOException which indicates to our caller that we should automatically attempt a re-read.
// If the file hasn't been closed yet and there's another writer, we will rely on file change notifications to notify us
// and reload the file.
DateTime newLastWriteTime = FileUtilities.GetFileTimeStamp(_path);
if (!newLastWriteTime.Equals(prevLastWriteTime))
{
var message = string.Format(WorkspacesResources.File_was_externally_modified_colon_0, _path);
throw new IOException(message);
}
return textAndVersion;
}
private string GetDebuggerDisplay()
{
return nameof(Path) + " = " + Path;
......
......@@ -31,7 +31,7 @@ public AdditionalTextDocument(TextDocumentState document)
/// </summary>
public override SourceText GetText(CancellationToken cancellationToken = default(CancellationToken))
{
var text = _document.GetText(cancellationToken);
var text = _document.GetTextSync(cancellationToken);
return text;
}
}
......
......@@ -119,10 +119,6 @@ private sealed class RecoverableText : RecoverableWeakValueSource<SourceText>
public RecoverableText(RecoverableTextAndVersion parent, SourceText text)
: base(new ConstantValueSource<SourceText>(text))
{
// TODO: refactor recoverable text like recoverable tree so that
// we can have tree/node concept in recoverable text as well.
// basically tree is handle that can live in memory and node is
// data that come and go.
_parent = parent;
}
......
......@@ -105,7 +105,9 @@ protected static ValueSource<TextAndVersion> CreateStrongText(TextAndVersion tex
protected static ValueSource<TextAndVersion> CreateStrongText(TextLoader loader, DocumentId documentId, SolutionServices services, bool reportInvalidDataException)
{
return new AsyncLazy<TextAndVersion>(
c => LoadTextAsync(loader, documentId, services, reportInvalidDataException, c), cacheResult: true);
asynchronousComputeFunction: c => LoadTextAsync(loader, documentId, services, reportInvalidDataException, c),
synchronousComputeFunction: c => LoadTextSync(loader, documentId, services, reportInvalidDataException, c),
cacheResult: true);
}
protected static ValueSource<TextAndVersion> CreateRecoverableText(TextAndVersion text, SolutionServices services)
......@@ -116,7 +118,10 @@ protected static ValueSource<TextAndVersion> CreateRecoverableText(TextAndVersio
protected static ValueSource<TextAndVersion> CreateRecoverableText(TextLoader loader, DocumentId documentId, SolutionServices services, bool reportInvalidDataException)
{
return new RecoverableTextAndVersion(
new AsyncLazy<TextAndVersion>(c => LoadTextAsync(loader, documentId, services, reportInvalidDataException, c), cacheResult: false),
new AsyncLazy<TextAndVersion>(
asynchronousComputeFunction: c => LoadTextAsync(loader, documentId, services, reportInvalidDataException, c),
synchronousComputeFunction: c => LoadTextSync(loader, documentId, services, reportInvalidDataException, c),
cacheResult: false),
services.TemporaryStorage);
}
......@@ -169,6 +174,51 @@ protected static async Task<TextAndVersion> LoadTextAsync(TextLoader loader, Doc
}
}
protected static TextAndVersion LoadTextSync(TextLoader loader, DocumentId documentId, SolutionServices services, bool reportInvalidDataException, CancellationToken cancellationToken)
{
int retries = 0;
while (true)
{
try
{
using (ExceptionHelpers.SuppressFailFast())
{
var result = loader.LoadTextAndVersion(services.Workspace, documentId, cancellationToken);
return result;
}
}
catch (OperationCanceledException)
{
// if load text is failed due to a cancellation, make sure we propagate it out to the caller
throw;
}
catch (IOException e)
{
if (++retries > MaxRetries)
{
services.Workspace.OnWorkspaceFailed(new DocumentDiagnostic(WorkspaceDiagnosticKind.Failure, e.Message, documentId));
return TextAndVersion.Create(SourceText.From(string.Empty, Encoding.UTF8), VersionStamp.Default, documentId.GetDebuggerDisplay());
}
// fall out to try again
}
catch (InvalidDataException e)
{
// TODO: Adjust this behavior in the future if we add support for non-text additional files
if (reportInvalidDataException)
{
services.Workspace.OnWorkspaceFailed(new DocumentDiagnostic(WorkspaceDiagnosticKind.Failure, e.Message, documentId));
}
return TextAndVersion.Create(SourceText.From(string.Empty, Encoding.UTF8), VersionStamp.Default, documentId.GetDebuggerDisplay());
}
// try again after a delay
Task.Delay(RetryDelay).Wait(cancellationToken);
}
}
public ITemporaryTextStorage Storage
{
get
......@@ -237,12 +287,18 @@ public async Task<SourceText> GetTextAsync(CancellationToken cancellationToken)
return textAndVersion.Text;
}
public SourceText GetText(CancellationToken cancellationToken)
public SourceText GetTextSync(CancellationToken cancellationToken)
{
var textAndVersion = this.textAndVersionSource.GetValue(cancellationToken);
return textAndVersion.Text;
}
public VersionStamp GetVersionSync(CancellationToken cancellationToken)
{
var textAndVersion = this.textAndVersionSource.GetValue(cancellationToken);
return textAndVersion.Version;
}
public async Task<VersionStamp> GetTextVersionAsync(CancellationToken cancellationToken)
{
// try fast path first
......
......@@ -4,6 +4,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis
{
......@@ -17,6 +18,14 @@ public abstract class TextLoader
/// </summary>
public abstract Task<TextAndVersion> LoadTextAndVersionAsync(Workspace workspace, DocumentId documentId, CancellationToken cancellationToken);
/// <summary>
/// Load a text and a version of the document in the workspace.
/// </summary>
internal virtual TextAndVersion LoadTextAndVersion(Workspace workspace, DocumentId documentId, CancellationToken cancellationToken)
{
return LoadTextAndVersionAsync(workspace, documentId, cancellationToken).WaitAndGetResult_CanCallOnBackground(cancellationToken);
}
/// <summary>
/// Creates a new TextLoader from an already existing source text and version.
/// </summary>
......@@ -59,6 +68,11 @@ public override Task<TextAndVersion> LoadTextAndVersionAsync(Workspace workspace
{
return Task.FromResult(_textAndVersion);
}
internal override TextAndVersion LoadTextAndVersion(Workspace workspace, DocumentId documentId, CancellationToken cancellationToken)
{
return _textAndVersion;
}
}
private class TextContainerLoader : TextLoader
......@@ -76,7 +90,12 @@ internal TextContainerLoader(SourceTextContainer container, VersionStamp version
public override Task<TextAndVersion> LoadTextAndVersionAsync(Workspace workspace, DocumentId documentId, CancellationToken cancellationToken)
{
return Task.FromResult(TextAndVersion.Create(_container.CurrentText, _version, _filePath));
return Task.FromResult(LoadTextAndVersion(workspace, documentId, cancellationToken));
}
internal override TextAndVersion LoadTextAndVersion(Workspace workspace, DocumentId documentId, CancellationToken cancellationToken)
{
return TextAndVersion.Create(_container.CurrentText, _version, _filePath);
}
}
}
......
......@@ -475,6 +475,14 @@ public ReuseVersionLoader(DocumentState oldDocumentState, SourceText newText)
return GetProperTextAndVersion(oldText, _newText, version, _oldDocumentState.FilePath);
}
internal override TextAndVersion LoadTextAndVersion(Workspace workspace, DocumentId documentId, CancellationToken cancellationToken)
{
var oldText = _oldDocumentState.GetTextSync(cancellationToken);
var version = _oldDocumentState.GetVersionSync(cancellationToken);
return GetProperTextAndVersion(oldText, _newText, version, _oldDocumentState.FilePath);
}
}
private static TextAndVersion GetProperTextAndVersion(SourceText oldText, SourceText newText, VersionStamp version, string filePath)
......
......@@ -2257,6 +2257,50 @@ public async Task TestAdditionalFilesStandalone()
}
}
[Fact, Trait(Traits.Feature, Traits.Features.Workspace)]
public void TestLoadTextSync()
{
var files = GetAnalyzerReferenceSolutionFiles();
CreateFiles(files);
using (var ws = new AdhocWorkspace(DesktopMefHostServices.DefaultServices))
{
var projectFullPath = Path.Combine(this.SolutionDirectory.Path, @"AnalyzerSolution\CSharpProject_AnalyzerReference.csproj");
var loader = new MSBuildProjectLoader(ws);
var infos = loader.LoadProjectInfoAsync(projectFullPath).Result;
var doc = infos[0].Documents[0];
var tav = doc.TextLoader.LoadTextAndVersion(ws, doc.Id, CancellationToken.None);
var adoc = infos[0].AdditionalDocuments.First(a => a.Name == "XamlFile.xaml");
var atav = adoc.TextLoader.LoadTextAndVersion(ws, adoc.Id, CancellationToken.None);
Assert.Contains("Window", atav.Text.ToString(), StringComparison.Ordinal);
}
}
[Fact, Trait(Traits.Feature, Traits.Features.Workspace)]
public void TestGetTextSync()
{
var files = GetAnalyzerReferenceSolutionFiles();
CreateFiles(files);
using (var ws = MSBuildWorkspace.Create())
{
var projectFullPath = Path.Combine(this.SolutionDirectory.Path, @"AnalyzerSolution\CSharpProject_AnalyzerReference.csproj");
var proj = ws.OpenProjectAsync(projectFullPath).Result;
var doc = proj.Documents.First();
var text = doc.State.GetTextSync(CancellationToken.None);
var adoc = proj.AdditionalDocuments.First(a => a.Name == "XamlFile.xaml");
var atext = adoc.State.GetTextSync(CancellationToken.None);
Assert.Contains("Window", atext.ToString(), StringComparison.Ordinal);
}
}
[Fact, Trait(Traits.Feature, Traits.Features.Workspace), WorkItem(546171, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/546171")]
public void TestCSharpExternAlias()
{
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册