Beispiel #1
0
        /// <summary>
        /// Creates a new TextLoader from an already existing source text and version.
        /// </summary>
        public static TextLoader From(TextAndVersion textAndVersion)
        {
            if (textAndVersion == null)
            {
                throw new ArgumentNullException("textAndVersion");
            }

            return new TextDocumentLoader(textAndVersion);
        }
Beispiel #2
0
 protected static ValueSource <TextAndVersion> CreateRecoverableText(TextAndVersion text, SolutionServices services)
 {
     return(new RecoverableTextAndVersion(CreateStrongText(text), services.TemporaryStorage));
 }
Beispiel #3
0
 protected static ValueSource <TextAndVersion> CreateStrongText(TextAndVersion text)
 {
     return(new ConstantValueSource <TextAndVersion>(text));
 }
Beispiel #4
0
        public DocumentState UpdateText(TextAndVersion newTextAndVersion, PreservationMode mode)
        {
            if (newTextAndVersion == null)
            {
                throw new ArgumentNullException("newTextAndVesion");
            }

            var newTextSource = mode == PreservationMode.PreserveIdentity
                ? CreateStrongText(newTextAndVersion)
                : CreateRecoverableText(newTextAndVersion, this.solutionServices);

            // always chain incremental parsing request, it will internally put 
            // appropriate request such as full parsing request if there are too many pending 
            // incremental parsing requests hanging around.
            var newTreeSource = CreateLazyIncrementallyParsedTree(this.treeSource, newTextSource);

            return new DocumentState(
                this.LanguageServices,
                this.solutionServices,
                this.info,
                this.options,
                newTextSource,
                newTreeSource);
        }
        public static SourceGeneratedDocumentState Create(
            string hintName,
            SourceText generatedSourceText,
            SyntaxTree generatedSyntaxTree,
            DocumentId documentId,
            ISourceGenerator sourceGenerator,
            HostLanguageServices languageServices,
            SolutionServices solutionServices,
            CancellationToken cancellationToken
            )
        {
            var options  = generatedSyntaxTree.Options;
            var filePath = generatedSyntaxTree.FilePath;

            var textAndVersion = TextAndVersion.Create(generatedSourceText, VersionStamp.Create());
            ValueSource <TextAndVersion> textSource = new ConstantValueSource <TextAndVersion>(
                textAndVersion
                );

            var root = generatedSyntaxTree.GetRoot(cancellationToken);

            Contract.ThrowIfNull(
                languageServices.SyntaxTreeFactory,
                "We should not have a generated syntax tree for a language that doesn't support trees."
                );

            if (languageServices.SyntaxTreeFactory.CanCreateRecoverableTree(root))
            {
                // We will only create recoverable text if we can create a recoverable tree; if we created a
                // recoverable text but not a new tree, it would mean tree.GetText() could still potentially return
                // the non-recoverable text, but asking the document directly for it's text would give a recoverable
                // text with a different object identity.
                textSource = CreateRecoverableText(textAndVersion, solutionServices);

                generatedSyntaxTree = languageServices.SyntaxTreeFactory.CreateRecoverableTree(
                    documentId.ProjectId,
                    filePath: generatedSyntaxTree.FilePath,
                    options,
                    textSource,
                    generatedSourceText.Encoding,
                    root
                    );
            }

            var treeAndVersion = TreeAndVersion.Create(generatedSyntaxTree, textAndVersion.Version);

            return(new SourceGeneratedDocumentState(
                       languageServices,
                       solutionServices,
                       documentServiceProvider: null,
                       new DocumentInfo.DocumentAttributes(
                           documentId,
                           name: hintName,
                           folders: SpecializedCollections.EmptyReadOnlyList <string>(),
                           options.Kind,
                           filePath: filePath,
                           isGenerated: true,
                           designTimeOnly: false
                           ),
                       options,
                       sourceText: null, // don't strongly hold the text
                       textSource,
                       treeAndVersion,
                       sourceGenerator,
                       hintName
                       ));
        }
Beispiel #6
0
 protected static ValueSource<TextAndVersion> CreateRecoverableText(TextAndVersion text, SolutionServices services)
 {
     return new RecoverableTextAndVersion(CreateStrongText(text), services.TemporaryStorage);
 }
Beispiel #7
0
        public TextDocumentState UpdateText(TextAndVersion newTextAndVersion, PreservationMode mode)
        {
            if (newTextAndVersion == null)
            {
                throw new ArgumentNullException(nameof(newTextAndVersion));
            }

            var newTextSource = mode == PreservationMode.PreserveIdentity
                ? CreateStrongText(newTextAndVersion)
                : CreateRecoverableText(newTextAndVersion, this.solutionServices);

            return new TextDocumentState(
                this.solutionServices,
                this.info,
                newTextSource);
        }
Beispiel #8
0
        /// <summary>
        /// Creates a new solution instance with the additional document specified updated to have the text
        /// and version specified.
        /// </summary>
        public Solution WithAdditionalDocumentText(DocumentId documentId, TextAndVersion textAndVersion, PreservationMode mode = PreservationMode.PreserveValue)
        {
            var newState = _state.WithAdditionalDocumentText(documentId, textAndVersion, mode);
            if (newState == _state)
            {
                return this;
            }

            return new Solution(newState);
        }
 public new AdditionalDocumentState UpdateText(TextAndVersion newTextAndVersion, PreservationMode mode)
 => (AdditionalDocumentState)base.UpdateText(newTextAndVersion, mode);
Beispiel #10
0
 public new AnalyzerConfigDocumentState UpdateText(TextAndVersion newTextAndVersion, PreservationMode mode)
 => (AnalyzerConfigDocumentState)base.UpdateText(newTextAndVersion, mode);
Beispiel #11
0
        protected internal void OnDocumentOpened(DocumentId documentId, SourceTextContainer textContainer, bool isCurrentContext = true)
        {
            CheckDocumentIsInCurrentSolution(documentId);
            CheckDocumentIsClosed(documentId);

            using (this.serializationLock.DisposableWait())
            {
                var oldSolution = this.CurrentSolution;
                var oldDocument = oldSolution.GetDocument(documentId);
                var oldText     = oldDocument.GetTextAsync(CancellationToken.None).WaitAndGetResult(CancellationToken.None);

                using (this.stateLock.DisposableWrite())
                {
                    var openDocuments = GetProjectOpenDocuments_NoLock(documentId.ProjectId);
                    if (openDocuments != null)
                    {
                        openDocuments.Add(documentId);
                    }
                    else
                    {
                        this.projectToOpenDocumentsMap.Add(documentId.ProjectId, new HashSet <DocumentId> {
                            documentId
                        });
                    }
                }

                // keep open document text alive by using PreserveIdentity
                var newText         = textContainer.CurrentText;
                var currentSolution = oldSolution;

                if (oldText != newText)
                {
                    if (oldText.ContentEquals(newText))
                    {
                        // if the supplied text is the same as the previous text, then add with same version
                        var version           = oldDocument.GetTextVersionAsync(CancellationToken.None).WaitAndGetResult(CancellationToken.None);
                        var newTextAndVersion = TextAndVersion.Create(newText, version, oldDocument.FilePath);
                        currentSolution = oldSolution.WithDocumentText(documentId, newTextAndVersion, PreservationMode.PreserveIdentity);
                    }
                    else
                    {
                        currentSolution = oldSolution.WithDocumentText(documentId, newText, PreservationMode.PreserveIdentity);
                    }
                }

                var newSolution = this.SetCurrentSolution(currentSolution);

                var tracker = new TextTracker(this, documentId, textContainer);
                this.textTrackers.Add(documentId, tracker);
                this.AddTextToDocumentIdMapping_NoLock(textContainer, documentId, isCurrentContext);
                tracker.Connect();

                var newDoc = newSolution.GetDocument(documentId);
                this.OnDocumentTextChanged(newDoc);

                this.RaiseWorkspaceChangedEventAsync(WorkspaceChangeKind.DocumentChanged, oldSolution, newSolution, documentId: documentId);
                var tsk = this.RaiseDocumentOpenedEventAsync(newDoc); // don't await this
            }

            // register outside of lock since it may call user code.
            this.RegisterText(textContainer);
        }
Beispiel #12
0
        private static TreeAndVersion IncrementallyParse(
            TextAndVersion newTextAndVersion,
            TreeAndVersion oldTreeAndVersion,
            CancellationToken cancellationToken)
        {
            var newText = newTextAndVersion.Text;
            var oldTree = oldTreeAndVersion.Tree;

            var oldText = oldTree.GetText(cancellationToken);
            var newTree = oldTree.WithChangedText(newText);
            Contract.ThrowIfNull(newTree);

            return MakeNewTreeAndVersion(oldTree, oldText, oldTreeAndVersion.Version, newTree, newText, newTextAndVersion.Version);
        }
Beispiel #13
0
        private static TreeAndVersion CreateTreeAndVersion(
            ValueSource<TextAndVersion> newTextSource,
            ProjectId cacheKey, string filePath,
            ParseOptions options, HostLanguageServices languageServices,
            PreservationMode mode, TextAndVersion textAndVersion,
            CancellationToken cancellationToken)
        {
            var text = textAndVersion.Text;

            var treeFactory = languageServices.GetService<ISyntaxTreeFactoryService>();

            var tree = treeFactory.ParseSyntaxTree(filePath, options, text, cancellationToken);

            var root = tree.GetRoot(cancellationToken);
            if (mode == PreservationMode.PreserveValue && treeFactory.CanCreateRecoverableTree(root))
            {
                tree = treeFactory.CreateRecoverableTree(cacheKey, tree.FilePath, tree.Options, newTextSource, text.Encoding, root);
            }

            Contract.ThrowIfNull(tree);

            // text version for this document should be unique. use it as a starting point.
            return TreeAndVersion.Create(tree, textAndVersion.Version);
        }
Beispiel #14
0
        public virtual async Task <VersionStamp> GetTopLevelChangeTextVersionAsync(CancellationToken cancellationToken)
        {
            TextAndVersion textAndVersion = await this.textAndVersionSource.GetValueAsync(cancellationToken).ConfigureAwait(false);

            return(textAndVersion.Version);
        }
Beispiel #15
0
 internal TextDocumentLoader(TextAndVersion textAndVersion)
 {
     this.textAndVersion = textAndVersion;
 }
        public override async Task <TextAndVersion> LoadTextAndVersionAsync(Workspace workspace, DocumentId documentId, CancellationToken cancellationToken)
        {
            ValidateFileLength(workspace, Path);

            var prevLastWriteTime = FileUtilities.GetFileTimeStamp(Path);

            TextAndVersion textAndVersion;

            // In many .NET Framework versions (specifically the 4.5.* series, but probably much earlier
            // and also later) there is this particularly interesting bit in FileStream.BeginReadAsync:
            //
            //     // [ed: full comment clipped for brevity]
            //     //
            //     // If we did a sync read to fill the buffer, we could avoid the
            //     // problem, and any async read less than 64K gets turned into a
            //     // synchronous read by NT anyways...
            //     if (numBytes < _bufferSize)
            //     {
            //         if (_buffer == null) _buffer = new byte[_bufferSize];
            //         IAsyncResult bufferRead = BeginReadCore(_buffer, 0, _bufferSize, null, null, 0);
            //         _readLen = EndRead(bufferRead);
            //
            // In English, this means that if you do a asynchronous read for smaller than _bufferSize,
            // this is implemented by the framework by starting an asynchronous read, and then
            // blocking your thread until that read is completed. The comment implies this is "fine"
            // because the asynchronous read will actually be synchronous and thus EndRead won't do
            // any blocking -- it'll be an effective no-op. In theory, everything is fine here.
            //
            // In reality, this can end very poorly. That read in fact can be asynchronous, which means the
            // EndRead will enter a wait and block the thread. If we are running that call to ReadAsync on a
            // thread pool thread that completed a previous piece of IO, it means there has to be another
            // thread available to service the completion of that request in order for our thread to make
            // progress. Why is this worse than the claim about the operating system turning an
            // asynchronous read into a synchronous one? If the underlying native ReadFile completes
            // synchronously, that would mean just our thread is being blocked, and will be unblocked once
            // the kernel gets done with our work. In this case, if the OS does do the read asynchronously
            // we are now dependent on another thread being available to unblock us.
            //
            // So how does ths manifest itself? We have seen dumps from customers reporting hangs where
            // we have over a hundred thread pool threads all blocked on EndRead() calls as we read this stream.
            // In these cases, the user had just completed a build that had a bunch of XAML files, and
            // this resulted in many .g.i.cs files being written and updated. As a result, Roslyn is trying to
            // re-read them to provide a new compilation to the XAML language service that is asking for it.
            // Inspecting these dumps and sampling some of the threads made some notable discoveries:
            //
            // 1. When there was a read blocked, it was the _last_ chunk that we were reading in the file in
            //    the file that we were reading. This leads me to believe that it isn't simply very slow IO
            //    (like a network drive), because in that case I'd expect to see some threads in different
            //    places than others.
            // 2. Some stacks were starting by the continuation of a ReadAsync, and some were the first read
            //    of a file from the background parser. In the first case, all of those threads were if the
            //    files were over 4K in size. The ones with the BackgroundParser still on the stack were files
            //    less than 4K in size.
            // 3. The "time unresponsive" in seconds correlated with roughly the number of threads we had
            //    blocked, which makes me think we were impacted by the once-per-second hill climbing algorithm
            //    used by the thread pool.
            //
            // So what's my analysis? When the XAML language service updated all the files, we kicked off
            // background parses for all of them. If the file was over 4K the asynchronous read actually did
            // happen (see point #2), but we'd eventually block the thread pool reading the last chunk.
            // Point #1 confirms that it was always the last chunk. And in small file cases, we'd block on
            // the first chunk. But in either case, we'd be blocking off a thread pool thread until another
            // thread pool thread was available. Since we had enough requests going (over a hundred),
            // sometimes the user got unlucky and all the threads got blocked. At this point, the CLR
            // started slowly kicking off more threads, but each time it'd start a new thread rather than
            // starting work that would be needed to unblock a thread, it just handled an IO that resulted
            // in another file read hitting the end of the file and another thread would get blocked. The
            // CLR then must kick off another thread, rinse, repeat. Eventually it'll make progress once
            // there's no more pending IO requests, everything will complete, and life then continues.
            //
            // To work around this issue, we set bufferSize to 1, which means that all reads should bypass
            // this logic. This is tracked by https://github.com/dotnet/corefx/issues/6007, at least in
            // corefx. We also open the 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: 1, useAsync: true)))
            {
                var version = VersionStamp.Create(prevLastWriteTime);

                // we do this so that we asynchronously read from file. and this should allocate less for IDE case.
                // but probably not for command line case where it doesn't use more sophisticated services.
                using var readStream = await SerializableBytes.CreateReadableStreamAsync(stream, cancellationToken : cancellationToken).ConfigureAwait(false);

                var text = CreateText(readStream, 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.
            var 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);
        }
Beispiel #17
0
 public override Task <TextAndVersion> LoadTextAndVersionAsync(Workspace workspace, DocumentId documentId, CancellationToken cancellationToken)
 {
     return(Task.FromResult(TextAndVersion.Create(this.container.CurrentText, this.version, this.filePath)));
 }
Beispiel #18
0
 internal TextDocumentLoader(TextAndVersion textAndVersion)
 {
     this.textAndVersion = textAndVersion;
 }
Beispiel #19
0
 public new DocumentState UpdateText(TextAndVersion newTextAndVersion, PreservationMode mode)
 {
     return((DocumentState)base.UpdateText(newTextAndVersion, mode));
 }
Beispiel #20
0
 protected static ValueSource<TextAndVersion> CreateStrongText(TextAndVersion text)
 {
     return new ConstantValueSource<TextAndVersion>(text);
 }
        public new DocumentState UpdateText(TextAndVersion newTextAndVersion, PreservationMode mode)
        {
            if (newTextAndVersion == null)
            {
                throw new ArgumentNullException(nameof(newTextAndVersion));
            }

            var newTextSource = mode == PreservationMode.PreserveIdentity
                ? CreateStrongText(newTextAndVersion)
                : CreateRecoverableText(newTextAndVersion, this.solutionServices);

            // always chain incremental parsing request, it will internally put 
            // appropriate request such as full parsing request if there are too many pending 
            // incremental parsing requests hanging around.
            //
            // However, don't bother with the chaining if this is a document that doesn't support
            // syntax trees.  The chaining will keep old data alive (like the old tree source,
            // which itself is keeping an old tree source which itself is keeping a ... alive),
            // causing a slow memory leak.
            var newTreeSource = !this.SupportsSyntaxTree
                ? ValueSource<TreeAndVersion>.Empty
                : CreateLazyIncrementallyParsedTree(_treeSource, newTextSource);

            return new DocumentState(
                this.LanguageServices,
                this.solutionServices,
                this.info,
                _options,
                sourceTextOpt: null,
                textSource: newTextSource,
                treeSource: newTreeSource);
        }
Beispiel #22
0
        /// <summary>
        /// Creates a new solution instance with the additional document specified updated to have the text
        /// and version specified.
        /// </summary>
        public SolutionState WithAdditionalDocumentText(DocumentId documentId, TextAndVersion textAndVersion, PreservationMode mode = PreservationMode.PreserveValue)
        {
            if (documentId == null)
            {
                throw new ArgumentNullException(nameof(documentId));
            }

            if (textAndVersion == null)
            {
                throw new ArgumentNullException(nameof(textAndVersion));
            }

            CheckContainsAdditionalDocument(documentId);

            var oldDocument = this.GetAdditionalDocumentState(documentId);

            return WithTextDocumentState(oldDocument.UpdateText(textAndVersion, mode), textChanged: true);
        }
Beispiel #23
0
 internal override TextAndVersion LoadTextAndVersionSynchronously(Workspace workspace, DocumentId documentId, CancellationToken cancellationToken)
 {
     return(TextAndVersion.Create(_container.CurrentText, _version, _filePath));
 }