DiagnosticService.cs 13.9 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
using System.Threading;
H
Heejae Chang 已提交
9
using Microsoft.CodeAnalysis.Common;
10 11 12 13 14 15
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Roslyn.Utilities;

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

H
Heejae Chang 已提交
20 21
        private static readonly DiagnosticEventTaskScheduler s_eventScheduler = new DiagnosticEventTaskScheduler(blockingUpperBound: 100);

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

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

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

            // use diagnostic event task scheduler so that we never flood async events queue with million of events.
            // queue itself can handle huge number of events but we are seeing OOM due to captured data in pending events.
H
Heejae Chang 已提交
37
            _eventQueue = new SimpleTaskQueue(s_eventScheduler);
38

39
            _listener = new AggregateAsynchronousOperationListener(asyncListeners, FeatureAttribute.DiagnosticService);
40

41 42
            _gate = new object();
            _map = new Dictionary<IDiagnosticUpdateSource, Dictionary<object, Data>>();
43 44 45 46 47 48
        }

        public event EventHandler<DiagnosticsUpdatedArgs> DiagnosticsUpdated
        {
            add
            {
49
                _eventMap.AddEventHandler(DiagnosticsUpdatedEventName, value);
50 51 52 53
            }

            remove
            {
54
                _eventMap.RemoveEventHandler(DiagnosticsUpdatedEventName, value);
55 56 57 58 59
            }
        }

        private void RaiseDiagnosticsUpdated(object sender, DiagnosticsUpdatedArgs args)
        {
60 61 62
            Contract.ThrowIfNull(sender);
            var source = (IDiagnosticUpdateSource)sender;

M
Matt Warren 已提交
63
            var ev = _eventMap.GetEventHandlers<EventHandler<DiagnosticsUpdatedArgs>>(DiagnosticsUpdatedEventName);
64
            if (!RequireRunningEventTasks(source, ev))
65
            {
66
                return;
67
            }
68 69 70 71 72 73 74 75 76 77 78 79

            var eventToken = _listener.BeginAsyncOperation(DiagnosticsUpdatedEventName);
            _eventQueue.ScheduleTask(() =>
            {
                if (!UpdateDataMap(source, args))
                {
                    // there is no change, nothing to raise events for.
                    return;
                }

                ev.RaiseEvent(handler => handler(sender, args));
            }).CompletesAsyncOperation(eventToken);
80 81
        }

82 83
        private bool RequireRunningEventTasks(
            IDiagnosticUpdateSource source, EventMap.EventHandlerSet<EventHandler<DiagnosticsUpdatedArgs>> ev)
84
        {
85 86 87 88 89 90 91 92 93 94 95
            // basically there are 2 cases when there is no event handler registered. 
            // first case is when diagnostic update source itself provide GetDiagnostics functionality. 
            // in that case, DiagnosticService doesn't need to track diagnostics reported. so, it bail out right away.
            // second case is when diagnostic source doesn't provide GetDiagnostics functionality. 
            // in that case, DiagnosticService needs to track diagnostics reported. so it need to enqueue background 
            // work to process given data regardless whether there is event handler registered or not.
            // this could be separated in 2 tasks, but we already saw cases where there are too many tasks enqueued, 
            // so I merged it to one. 

            // if it doesn't SupportGetDiagnostics, we need to process reported data, so enqueue task.
            if (!source.SupportGetDiagnostics)
96
            {
97
                return true;
98 99
            }

100 101
            return ev.HasHandlers;
        }
102

103 104 105
        private bool UpdateDataMap(IDiagnosticUpdateSource source, DiagnosticsUpdatedArgs args)
        {
            // we expect source who uses this ability to have small number of diagnostics.
106
            lock (_gate)
107
            {
108 109
                Contract.Requires(_updateSources.Contains(source));

110
                // check cheap early bail out
111
                if (args.Diagnostics.Length == 0 && !_map.ContainsKey(source))
112 113
                {
                    // no new diagnostic, and we don't have update source for it.
114
                    return false;
115
                }
116

117
                var diagnosticDataMap = _map.GetOrAdd(source, _ => new Dictionary<object, Data>());
118

119 120
                diagnosticDataMap.Remove(args.Id);
                if (diagnosticDataMap.Count == 0 && args.Diagnostics.Length == 0)
121
                {
122 123
                    _map.Remove(source);
                    return true;
124 125
                }

126 127 128 129
                var data = source.SupportGetDiagnostics ? new Data(args) : new Data(args, args.Diagnostics);
                diagnosticDataMap.Add(args.Id, data);

                return true;
130 131 132 133 134
            }
        }

        private void OnDiagnosticsUpdated(object sender, DiagnosticsUpdatedArgs e)
        {
135
            AssertIfNull(e.Diagnostics);
136 137 138 139
            RaiseDiagnosticsUpdated(sender, e);
        }

        public IEnumerable<DiagnosticData> GetDiagnostics(
140
            Workspace workspace, ProjectId projectId, DocumentId documentId, object id, bool includeSuppressedDiagnostics, CancellationToken cancellationToken)
141 142 143 144
        {
            if (id != null)
            {
                // get specific one
145
                return GetSpecificDiagnostics(workspace, projectId, documentId, id, includeSuppressedDiagnostics, cancellationToken);
146 147 148
            }

            // get aggregated ones
149
            return GetDiagnostics(workspace, projectId, documentId, includeSuppressedDiagnostics, cancellationToken);
150 151
        }

152
        private IEnumerable<DiagnosticData> GetSpecificDiagnostics(Workspace workspace, ProjectId projectId, DocumentId documentId, object id, bool includeSuppressedDiagnostics, CancellationToken cancellationToken)
153
        {
154
            foreach (var source in _updateSources)
155 156 157 158 159
            {
                cancellationToken.ThrowIfCancellationRequested();

                if (source.SupportGetDiagnostics)
                {
160
                    var diagnostics = source.GetDiagnostics(workspace, projectId, documentId, id, includeSuppressedDiagnostics, cancellationToken);
161 162 163 164 165 166 167 168 169 170 171 172 173 174
                    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)
                        {
175 176
                            var diagnostics = pool.Object[0].Diagnostics;
                            return !includeSuppressedDiagnostics ? FilterSuppressedDiagnostics(diagnostics) : diagnostics;
177 178 179 180 181 182 183 184
                        }
                    }
                }
            }

            return SpecializedCollections.EmptyEnumerable<DiagnosticData>();
        }

185 186 187 188 189 190 191 192 193 194 195 196 197 198
        private static IEnumerable<DiagnosticData> FilterSuppressedDiagnostics(IEnumerable<DiagnosticData> diagnostics)
        {
            if (diagnostics != null)
            {
                foreach (var diagnostic in diagnostics)
                {
                    if (!diagnostic.IsSuppressed)
                    {
                        yield return diagnostic;
                    }
                }
            }
        }

199
        private IEnumerable<DiagnosticData> GetDiagnostics(
200
            Workspace workspace, ProjectId projectId, DocumentId documentId, bool includeSuppressedDiagnostics, CancellationToken cancellationToken)
201
        {
202
            foreach (var source in _updateSources)
203 204 205 206 207
            {
                cancellationToken.ThrowIfCancellationRequested();

                if (source.SupportGetDiagnostics)
                {
208
                    foreach (var diagnostic in source.GetDiagnostics(workspace, projectId, documentId, null, includeSuppressedDiagnostics, cancellationToken))
209
                    {
210
                        AssertIfNull(diagnostic);
211 212 213 214 215 216 217 218 219 220 221 222 223
                        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)
                            {
224
                                AssertIfNull(diagnostic);
225 226 227 228
                                if (includeSuppressedDiagnostics || !diagnostic.IsSuppressed)
                                {
                                    yield return diagnostic;
                                }
229 230 231 232 233 234 235
                            }
                        }
                    }
                }
            }
        }

H
Heejae Chang 已提交
236
        public IEnumerable<UpdatedEventArgs> GetDiagnosticsUpdatedEventArgs(Workspace workspace, ProjectId projectId, DocumentId documentId, CancellationToken cancellationToken)
237 238 239 240 241 242 243 244 245 246 247
        {
            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 已提交
248
                        yield return new UpdatedEventArgs(data.Id, data.Workspace, data.ProjectId, data.DocumentId);
249 250 251 252 253
                    }
                }
            }
        }

254 255 256
        private void AppendMatchingData(
            IDiagnosticUpdateSource source, Workspace workspace, ProjectId projectId, DocumentId documentId, object id, List<Data> list)
        {
257
            lock (_gate)
258 259
            {
                Dictionary<object, Data> current;
260
                if (!_map.TryGetValue(source, out current))
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302
                {
                    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 已提交
303
        [Conditional("DEBUG")]
304 305 306 307 308 309 310 311
        private void AssertIfNull(ImmutableArray<DiagnosticData> diagnostics)
        {
            for (var i = 0; i < diagnostics.Length; i++)
            {
                AssertIfNull(diagnostics[i]);
            }
        }

H
Heejae Chang 已提交
312
        [Conditional("DEBUG")]
313 314 315 316 317 318 319 320
        private void AssertIfNull(DiagnosticData diagnostic)
        {
            if (diagnostic == null)
            {
                Contract.Requires(false, "who returns invalid data?");
            }
        }

321 322 323 324 325 326 327 328
        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 已提交
329
            public Data(UpdatedEventArgs args) :
330 331 332 333
                this(args, ImmutableArray<DiagnosticData>.Empty)
            {
            }

H
Heejae Chang 已提交
334
            public Data(UpdatedEventArgs args, ImmutableArray<DiagnosticData> diagnostics)
335 336 337 338 339
            {
                this.Workspace = args.Workspace;
                this.ProjectId = args.ProjectId;
                this.DocumentId = args.DocumentId;
                this.Id = args.Id;
340
                this.Diagnostics = diagnostics;
341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365
            }

            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))));
            }
        }
    }
}