| File: Workspace\Solution\TextLoader.cs | Web Access |
| Project: src\roslyn\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.IO; using System.Runtime.CompilerServices; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis; /// <summary> /// A class that represents access to a source text and its version from a storage location. /// </summary> public abstract class TextLoader { private static readonly ConditionalWeakTable<Type, StrongBox<bool>> s_isObsoleteLoadTextAndVersionAsyncOverriden = new(); private const double MaxDelaySecs = 1.0; private const int MaxRetries = 5; internal static readonly TimeSpan RetryDelay = TimeSpan.FromSeconds(MaxDelaySecs / MaxRetries); internal virtual string? FilePath => null; /// <summary> /// <see langword="true"/> if the document that holds onto this loader should do so with a strong reference, versus /// a reference that will take the contents of this loader and store them in a recoverable form (e.g. a memory /// mapped file within the <em>same</em> process). This should be used when the underlying data is already stored /// in a recoverable form somewhere else and it would be wasteful to store another copy. For example, a document /// that is backed by memory-mapped contents in <em>another</em> process does not need to dump it's content to /// another memory-mapped file in the process it lives in. It can always recover the text from the original /// process. /// </summary> internal virtual bool AlwaysHoldStrongly => false; /// <summary> /// True if <see cref="LoadTextAndVersionAsync(LoadTextOptions, CancellationToken)"/> reloads <see cref="SourceText"/> from its original binary representation (e.g. file on disk). /// </summary> internal virtual bool CanReloadText => false; /// <summary> /// Load a text and a version of the document. /// </summary> /// <param name="options"> /// Implementations of this method should use <see cref="LoadTextOptions.ChecksumAlgorithm"/> when creating <see cref="SourceText"/> from an original binary representation and /// ignore it otherwise. /// Callers of this method should pass <see cref="LoadTextOptions"/> specifying the desired properties of <see cref="SourceText"/>. The implementation may return a <see cref="SourceText"/> /// that does not satisfy the given requirements. For example, legacy types that do not override this method would ignore all <paramref name="options"/>. /// </param> /// <param name="cancellationToken">Cancellation token.</param> /// <exception cref="IOException" /> /// <exception cref="InvalidDataException"/> /// <exception cref="OperationCanceledException"/> public virtual Task<TextAndVersion> LoadTextAndVersionAsync(LoadTextOptions options, CancellationToken cancellationToken) { #pragma warning disable CS0618 // Type or member is obsolete if (s_isObsoleteLoadTextAndVersionAsyncOverriden.GetValue( GetType(), _ => new StrongBox<bool>(new Func<Workspace, DocumentId, CancellationToken, Task<TextAndVersion>>(LoadTextAndVersionAsync).Method.DeclaringType != typeof(TextLoader))).Value) { return LoadTextAndVersionAsync(workspace: null, documentId: null, cancellationToken); } #pragma warning restore throw new NotImplementedException($"{GetType()} must override {nameof(LoadTextAndVersionAsync)}"); } /// <summary> /// Load a text and a version of the document. /// </summary> /// <param name="workspace">Obsolete. Null.</param> /// <param name="documentId">Obsolete. Null.</param> /// <exception cref="IOException" /> /// <exception cref="InvalidDataException"/> /// <exception cref="OperationCanceledException"/> [Obsolete("Use/override LoadTextAndVersionAsync(LoadTextOptions, CancellationToken)")] public virtual Task<TextAndVersion> LoadTextAndVersionAsync(Workspace? workspace, DocumentId? documentId, CancellationToken cancellationToken) => LoadTextAndVersionAsync(new LoadTextOptions(SourceHashAlgorithms.Default), cancellationToken); /// <summary> /// Load a text and a version of the document in the workspace. /// </summary> /// <exception cref="IOException" /> /// <exception cref="InvalidDataException"/> /// <exception cref="OperationCanceledException"/> internal virtual TextAndVersion LoadTextAndVersionSynchronously(LoadTextOptions options, CancellationToken cancellationToken) { // this implementation exists in case a custom derived type does not have access to internals return LoadTextAndVersionAsync(options, cancellationToken).WaitAndGetResult_CanCallOnBackground(cancellationToken); } internal async Task<TextAndVersion> LoadTextAsync(LoadTextOptions options, CancellationToken cancellationToken) { var retries = 0; while (true) { try { return await LoadTextAndVersionAsync(options, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); } catch (IOException e) { if (++retries > MaxRetries) { return CreateFailedText(e.Message); } // fall out to try again } catch (InvalidDataException e) { return CreateFailedText(e.Message); } // try again after a delay await Task.Delay(RetryDelay, cancellationToken).ConfigureAwait(false); } } internal TextAndVersion LoadTextSynchronously(LoadTextOptions options, CancellationToken cancellationToken) { var retries = 0; while (true) { try { return LoadTextAndVersionSynchronously(options, cancellationToken); } catch (IOException e) { if (++retries > MaxRetries) { return CreateFailedText(e.Message); } // fall out to try again } catch (InvalidDataException e) { return CreateFailedText(e.Message); } cancellationToken.ThrowIfCancellationRequested(); // try again after a delay Thread.Sleep(RetryDelay); } } private TextAndVersion CreateFailedText(string message) { return TextAndVersion.Create( SourceText.From(string.Empty, Encoding.UTF8), VersionStamp.Default, this.FilePath, message); } /// <summary> /// Creates a new <see cref="TextLoader"/> from an already existing source text and version. /// </summary> public static TextLoader From(TextAndVersion textAndVersion) { if (textAndVersion == null) { throw new ArgumentNullException(nameof(textAndVersion)); } return new TextDocumentLoader(textAndVersion); } /// <summary> /// Creates a <see cref="TextLoader"/> from a <see cref="SourceTextContainer"/> and version. /// /// The text obtained from the loader will be the current text of the container at the time /// the loader is accessed. /// </summary> public static TextLoader From(SourceTextContainer container, VersionStamp version, string? filePath = null) { if (container == null) { throw new ArgumentNullException(nameof(container)); } return new TextContainerLoader(container, version, filePath); } private sealed class TextDocumentLoader : TextLoader { private readonly TextAndVersion _textAndVersion; internal TextDocumentLoader(TextAndVersion textAndVersion) => _textAndVersion = textAndVersion; public override async Task<TextAndVersion> LoadTextAndVersionAsync(LoadTextOptions options, CancellationToken cancellationToken) => _textAndVersion; internal override TextAndVersion LoadTextAndVersionSynchronously(LoadTextOptions options, CancellationToken cancellationToken) => _textAndVersion; } private sealed class TextContainerLoader : TextLoader { private readonly SourceTextContainer _container; private readonly VersionStamp _version; private readonly string? _filePath; internal TextContainerLoader(SourceTextContainer container, VersionStamp version, string? filePath) { _container = container; _version = version; _filePath = filePath; } internal override string? FilePath => _filePath; public override async Task<TextAndVersion> LoadTextAndVersionAsync(LoadTextOptions options, CancellationToken cancellationToken) => LoadTextAndVersionSynchronously(options, cancellationToken); internal override TextAndVersion LoadTextAndVersionSynchronously(LoadTextOptions options, CancellationToken cancellationToken) => TextAndVersion.Create(_container.CurrentText, _version, _filePath); } }