RecoverableTextAndVersion.cs 6.2 KB
Newer Older
J
Jonathon Marolf 已提交
1 2 3
// 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.
P
Pilchie 已提交
4

5 6 7
#nullable enable

using System.Diagnostics.CodeAnalysis;
P
Pilchie 已提交
8 9 10 11
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Internal.Log;
12
using Microsoft.CodeAnalysis.Text;
P
Pilchie 已提交
13 14 15 16 17
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis
{
    /// <summary>
18
    /// A recoverable TextAndVersion source that saves its text to temporary storage.
P
Pilchie 已提交
19
    /// </summary>
20
    internal class RecoverableTextAndVersion : ValueSource<TextAndVersion>, ITextVersionable
P
Pilchie 已提交
21
    {
22
        private readonly ITemporaryStorageService _storageService;
P
Pilchie 已提交
23

24 25
        private SemaphoreSlim? _lazyGate;
        private ValueSource<TextAndVersion>? _initialSource;
26

27
        private RecoverableText? _text;
28
        private VersionStamp _version;
29 30
        private string? _filePath;
        private Diagnostic? _loadDiagnostic;
P
Pilchie 已提交
31 32 33

        public RecoverableTextAndVersion(
            ValueSource<TextAndVersion> initialTextAndVersion,
34
            ITemporaryStorageService storageService)
P
Pilchie 已提交
35
        {
36
            _initialSource = initialTextAndVersion;
37
            _storageService = storageService;
P
Pilchie 已提交
38 39
        }

40
        private SemaphoreSlim Gate => LazyInitialization.EnsureInitialized(ref _lazyGate, SemaphoreSlimFactory.Instance);
41

42
        public ITemporaryTextStorage? Storage => _text?.Storage;
43

44
        public override bool TryGetValue([MaybeNullWhen(false)] out TextAndVersion value)
45
        {
C
CyrusNajmabadi 已提交
46
            if (_text != null && _text.TryGetValue(out var text))
47
            {
48
                value = TextAndVersion.Create(text, _version, _filePath, _loadDiagnostic);
49 50 51 52
                return true;
            }
            else
            {
53
                value = null!;
54 55 56 57
                return false;
            }
        }

P
Pilchie 已提交
58 59
        public bool TryGetTextVersion(out VersionStamp version)
        {
M
Matt Warren 已提交
60
            version = _version;
61 62 63

            // if the TextAndVersion has not been stored yet, but it has been observed
            // then try to get version from cached value.
C
CyrusNajmabadi 已提交
64
            if (version == default)
65
            {
66
                if (TryGetValue(out var textAndVersion))
67 68 69
                {
                    version = textAndVersion.Version;
                }
70 71 72 73
                else if (_initialSource is ITextVersionable textVersionable)
                {
                    return textVersionable.TryGetTextVersion(out version);
                }
74 75
            }

C
CyrusNajmabadi 已提交
76
            return version != default;
P
Pilchie 已提交
77 78
        }

C
CyrusNajmabadi 已提交
79
        public override TextAndVersion GetValue(CancellationToken cancellationToken = default)
P
Pilchie 已提交
80
        {
81
            if (_text == null)
P
Pilchie 已提交
82
            {
83 84 85 86
                using (Gate.DisposableWait(cancellationToken))
                {
                    if (_text == null)
                    {
87
                        return InitRecoverable(_initialSource!.GetValue(cancellationToken));
88 89
                    }
                }
P
Pilchie 已提交
90
            }
91

92
            return TextAndVersion.Create(_text.GetValue(cancellationToken), _version, _filePath, _loadDiagnostic);
P
Pilchie 已提交
93 94
        }

C
CyrusNajmabadi 已提交
95
        public override async Task<TextAndVersion> GetValueAsync(CancellationToken cancellationToken = default)
P
Pilchie 已提交
96
        {
97
            if (_text == null)
P
Pilchie 已提交
98
            {
M
Matt Warren 已提交
99
                using (await Gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false))
100 101 102
                {
                    if (_text == null)
                    {
103
                        return InitRecoverable(await _initialSource!.GetValueAsync(cancellationToken).ConfigureAwait(false));
104 105
                    }
                }
P
Pilchie 已提交
106
            }
107 108

            var text = await _text.GetValueAsync(cancellationToken).ConfigureAwait(false);
109
            return TextAndVersion.Create(text, _version, _filePath, _loadDiagnostic);
P
Pilchie 已提交
110 111
        }

112
        private TextAndVersion InitRecoverable(TextAndVersion textAndVersion)
P
Pilchie 已提交
113
        {
114 115 116
            _initialSource = null;
            _version = textAndVersion.Version;
            _filePath = textAndVersion.FilePath;
117
            _loadDiagnostic = textAndVersion.LoadDiagnostic;
118 119 120 121 122
            _text = new RecoverableText(this, textAndVersion.Text);
            _text.GetValue(CancellationToken.None); // force access to trigger save
            return textAndVersion;
        }

123
        private sealed class RecoverableText : WeaklyCachedRecoverableValueSource<SourceText>
124 125
        {
            private readonly RecoverableTextAndVersion _parent;
126
            private ITemporaryTextStorage? _storage;
127 128 129 130 131 132 133

            public RecoverableText(RecoverableTextAndVersion parent, SourceText text)
                : base(new ConstantValueSource<SourceText>(text))
            {
                _parent = parent;
            }

134
            public ITemporaryTextStorage? Storage => _storage;
135

136 137 138 139 140 141
            protected override async Task<SourceText> RecoverAsync(CancellationToken cancellationToken)
            {
                Contract.ThrowIfNull(_storage);

                using (Logger.LogBlock(FunctionId.Workspace_Recoverable_RecoverTextAsync, _parent._filePath, cancellationToken))
                {
142
                    return await _storage.ReadTextAsync(cancellationToken).ConfigureAwait(false);
143 144 145 146 147
                }
            }

            protected override SourceText Recover(CancellationToken cancellationToken)
            {
P
Paul Harrington 已提交
148 149
                Contract.ThrowIfNull(_storage);

150 151
                using (Logger.LogBlock(FunctionId.Workspace_Recoverable_RecoverText, _parent._filePath, cancellationToken))
                {
152
                    return _storage.ReadText(cancellationToken);
153 154 155
                }
            }

156
            protected override async Task SaveAsync(SourceText text, CancellationToken cancellationToken)
157
            {
P
Paul Harrington 已提交
158 159
                Contract.ThrowIfFalse(_storage == null); // Cannot save more than once

160
                var storage = _parent._storageService.CreateTemporaryTextStorage(cancellationToken);
J
Jonathon Marolf 已提交
161
                await storage.WriteTextAsync(text, cancellationToken).ConfigureAwait(false);
162 163 164

                // make sure write is done before setting _storage field
                Interlocked.CompareExchange(ref _storage, storage, null);
165
            }
P
Pilchie 已提交
166 167
        }
    }
168
}