DiagnosticService.cs 12.1 KB
Newer Older
1
// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.
2 3 4 5 6

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
H
Heejae Chang 已提交
7
using System.Diagnostics;
8 9
using System.Threading;
using System.Threading.Tasks;
H
Heejae Chang 已提交
10
using Microsoft.CodeAnalysis.Common;
11 12 13 14 15 16
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.Diagnostics
{
    [Export(typeof(IDiagnosticService)), Shared]
17
    internal partial class DiagnosticService : IDiagnosticService
18 19 20
    {
        private const string DiagnosticsUpdatedEventName = "DiagnosticsUpdated";

21 22 23
        private readonly IAsynchronousOperationListener _listener;
        private readonly EventMap _eventMap;
        private readonly SimpleTaskQueue _eventQueue;
24

25 26
        private readonly object _gate;
        private readonly Dictionary<IDiagnosticUpdateSource, Dictionary<object, Data>> _map;
27 28

        [ImportingConstructor]
29
        public DiagnosticService([ImportMany] IEnumerable<Lazy<IAsynchronousOperationListener, FeatureMetadata>> asyncListeners) : this()
30 31
        {
            // queue to serialize events.
32 33
            _eventMap = new EventMap();
            _eventQueue = new SimpleTaskQueue(TaskScheduler.Default);
34

35
            _listener = new AggregateAsynchronousOperationListener(asyncListeners, FeatureAttribute.DiagnosticService);
36

37 38
            _gate = new object();
            _map = new Dictionary<IDiagnosticUpdateSource, Dictionary<object, Data>>();
39 40 41 42 43 44
        }

        public event EventHandler<DiagnosticsUpdatedArgs> DiagnosticsUpdated
        {
            add
            {
45
                _eventMap.AddEventHandler(DiagnosticsUpdatedEventName, value);
46 47 48 49
            }

            remove
            {
50
                _eventMap.RemoveEventHandler(DiagnosticsUpdatedEventName, value);
51 52 53 54 55
            }
        }

        private void RaiseDiagnosticsUpdated(object sender, DiagnosticsUpdatedArgs args)
        {
M
Matt Warren 已提交
56 57
            var ev = _eventMap.GetEventHandlers<EventHandler<DiagnosticsUpdatedArgs>>(DiagnosticsUpdatedEventName);
            if (ev.HasHandlers)
58
            {
59 60
                var eventToken = _listener.BeginAsyncOperation(DiagnosticsUpdatedEventName);
                _eventQueue.ScheduleTask(() =>
61 62
                {
                    UpdateDataMap(sender, args);
M
Matt Warren 已提交
63
                    ev.RaiseEvent(handler => handler(sender, args));
64 65 66 67 68 69 70
                }).CompletesAsyncOperation(eventToken);
            }
        }

        private void UpdateDataMap(object sender, DiagnosticsUpdatedArgs args)
        {
            var updateSource = sender as IDiagnosticUpdateSource;
71
            if (updateSource == null)
72 73 74 75
            {
                return;
            }

76 77
            Contract.Requires(_updateSources.Contains(updateSource));

78
            // we expect someone who uses this ability to small.
79
            lock (_gate)
80
            {
81 82 83 84 85 86
                // check cheap early bail out
                if (args.Diagnostics.Length == 0 && !_map.ContainsKey(updateSource))
                {
                    // no new diagnostic, and we don't have update source for it.
                    return;
                }
87

88
                var list = _map.GetOrAdd(updateSource, _ => new Dictionary<object, Data>());
89
                var data = updateSource.SupportGetDiagnostics ? new Data(args) : new Data(args, args.Diagnostics);
90 91 92 93

                list.Remove(data.Id);
                if (list.Count == 0 && args.Diagnostics.Length == 0)
                {
94
                    _map.Remove(updateSource);
95 96 97 98 99 100 101 102 103
                    return;
                }

                list.Add(args.Id, data);
            }
        }

        private void OnDiagnosticsUpdated(object sender, DiagnosticsUpdatedArgs e)
        {
104
            AssertIfNull(e.Diagnostics);
105 106 107 108
            RaiseDiagnosticsUpdated(sender, e);
        }

        public IEnumerable<DiagnosticData> GetDiagnostics(
109
            Workspace workspace, ProjectId projectId, DocumentId documentId, object id, bool includeSuppressedDiagnostics, CancellationToken cancellationToken)
110 111 112 113
        {
            if (id != null)
            {
                // get specific one
114
                return GetSpecificDiagnostics(workspace, projectId, documentId, id, includeSuppressedDiagnostics, cancellationToken);
115 116 117
            }

            // get aggregated ones
118
            return GetDiagnostics(workspace, projectId, documentId, includeSuppressedDiagnostics, cancellationToken);
119 120
        }

121
        private IEnumerable<DiagnosticData> GetSpecificDiagnostics(Workspace workspace, ProjectId projectId, DocumentId documentId, object id, bool includeSuppressedDiagnostics, CancellationToken cancellationToken)
122
        {
123
            foreach (var source in _updateSources)
124 125 126 127 128
            {
                cancellationToken.ThrowIfCancellationRequested();

                if (source.SupportGetDiagnostics)
                {
129
                    var diagnostics = source.GetDiagnostics(workspace, projectId, documentId, id, includeSuppressedDiagnostics, cancellationToken);
130 131 132 133 134 135 136 137 138 139 140 141 142 143
                    if (diagnostics != null && diagnostics.Length > 0)
                    {
                        return diagnostics;
                    }
                }
                else
                {
                    using (var pool = SharedPools.Default<List<Data>>().GetPooledObject())
                    {
                        AppendMatchingData(source, workspace, projectId, documentId, id, pool.Object);
                        Contract.Requires(pool.Object.Count == 0 || pool.Object.Count == 1);

                        if (pool.Object.Count == 1)
                        {
144 145
                            var diagnostics = pool.Object[0].Diagnostics;
                            return !includeSuppressedDiagnostics ? FilterSuppressedDiagnostics(diagnostics) : diagnostics;
146 147 148 149 150 151 152 153
                        }
                    }
                }
            }

            return SpecializedCollections.EmptyEnumerable<DiagnosticData>();
        }

154 155 156 157 158 159 160 161 162 163 164 165 166 167
        private static IEnumerable<DiagnosticData> FilterSuppressedDiagnostics(IEnumerable<DiagnosticData> diagnostics)
        {
            if (diagnostics != null)
            {
                foreach (var diagnostic in diagnostics)
                {
                    if (!diagnostic.IsSuppressed)
                    {
                        yield return diagnostic;
                    }
                }
            }
        }

168
        private IEnumerable<DiagnosticData> GetDiagnostics(
169
            Workspace workspace, ProjectId projectId, DocumentId documentId, bool includeSuppressedDiagnostics, CancellationToken cancellationToken)
170
        {
171
            foreach (var source in _updateSources)
172 173 174 175 176
            {
                cancellationToken.ThrowIfCancellationRequested();

                if (source.SupportGetDiagnostics)
                {
177
                    foreach (var diagnostic in source.GetDiagnostics(workspace, projectId, documentId, null, includeSuppressedDiagnostics, cancellationToken))
178
                    {
179
                        AssertIfNull(diagnostic);
180 181 182 183 184 185 186 187 188 189 190 191 192
                        yield return diagnostic;
                    }
                }
                else
                {
                    using (var list = SharedPools.Default<List<Data>>().GetPooledObject())
                    {
                        AppendMatchingData(source, workspace, projectId, documentId, null, list.Object);

                        foreach (var data in list.Object)
                        {
                            foreach (var diagnostic in data.Diagnostics)
                            {
193
                                AssertIfNull(diagnostic);
194 195 196 197
                                if (includeSuppressedDiagnostics || !diagnostic.IsSuppressed)
                                {
                                    yield return diagnostic;
                                }
198 199 200 201 202 203 204
                            }
                        }
                    }
                }
            }
        }

H
Heejae Chang 已提交
205
        public IEnumerable<UpdatedEventArgs> GetDiagnosticsUpdatedEventArgs(Workspace workspace, ProjectId projectId, DocumentId documentId, CancellationToken cancellationToken)
206 207 208 209 210 211 212 213 214 215 216
        {
            foreach (var source in _updateSources)
            {
                cancellationToken.ThrowIfCancellationRequested();

                using (var list = SharedPools.Default<List<Data>>().GetPooledObject())
                {
                    AppendMatchingData(source, workspace, projectId, documentId, null, list.Object);

                    foreach (var data in list.Object)
                    {
H
Heejae Chang 已提交
217
                        yield return new UpdatedEventArgs(data.Id, data.Workspace, data.ProjectId, data.DocumentId);
218 219 220 221 222
                    }
                }
            }
        }

223 224 225
        private void AppendMatchingData(
            IDiagnosticUpdateSource source, Workspace workspace, ProjectId projectId, DocumentId documentId, object id, List<Data> list)
        {
226
            lock (_gate)
227 228
            {
                Dictionary<object, Data> current;
229
                if (!_map.TryGetValue(source, out current))
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271
                {
                    return;
                }

                if (id != null)
                {
                    Data data;
                    if (current.TryGetValue(id, out data))
                    {
                        list.Add(data);
                    }

                    return;
                }

                foreach (var data in current.Values)
                {
                    if (TryAddData(documentId, data, d => d.DocumentId, list) ||
                        TryAddData(projectId, data, d => d.ProjectId, list) ||
                        TryAddData(workspace, data, d => d.Workspace, list))
                    {
                        continue;
                    }
                }
            }
        }

        private bool TryAddData<T>(T key, Data data, Func<Data, T> keyGetter, List<Data> result) where T : class
        {
            if (key == null)
            {
                return false;
            }

            if (key == keyGetter(data))
            {
                result.Add(data);
            }

            return true;
        }

H
Heejae Chang 已提交
272
        [Conditional("DEBUG")]
273 274 275 276 277 278 279 280
        private void AssertIfNull(ImmutableArray<DiagnosticData> diagnostics)
        {
            for (var i = 0; i < diagnostics.Length; i++)
            {
                AssertIfNull(diagnostics[i]);
            }
        }

H
Heejae Chang 已提交
281
        [Conditional("DEBUG")]
282 283 284 285 286 287 288 289
        private void AssertIfNull(DiagnosticData diagnostic)
        {
            if (diagnostic == null)
            {
                Contract.Requires(false, "who returns invalid data?");
            }
        }

290 291 292 293 294 295 296 297
        private struct Data : IEquatable<Data>
        {
            public readonly Workspace Workspace;
            public readonly ProjectId ProjectId;
            public readonly DocumentId DocumentId;
            public readonly object Id;
            public readonly ImmutableArray<DiagnosticData> Diagnostics;

H
Heejae Chang 已提交
298
            public Data(UpdatedEventArgs args) :
299 300 301 302
                this(args, ImmutableArray<DiagnosticData>.Empty)
            {
            }

H
Heejae Chang 已提交
303
            public Data(UpdatedEventArgs args, ImmutableArray<DiagnosticData> diagnostics)
304 305 306 307 308
            {
                this.Workspace = args.Workspace;
                this.ProjectId = args.ProjectId;
                this.DocumentId = args.DocumentId;
                this.Id = args.Id;
309
                this.Diagnostics = diagnostics;
310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334
            }

            public bool Equals(Data other)
            {
                return this.Workspace == other.Workspace &&
                       this.ProjectId == other.ProjectId &&
                       this.DocumentId == other.DocumentId &&
                       this.Id == other.Id;
            }

            public override bool Equals(object obj)
            {
                return (obj is Data) && Equals((Data)obj);
            }

            public override int GetHashCode()
            {
                return Hash.Combine(Workspace,
                       Hash.Combine(ProjectId,
                       Hash.Combine(DocumentId,
                       Hash.Combine(Id, 1))));
            }
        }
    }
}