~ubuntu-branches/ubuntu/trusty/monodevelop/trusty-proposed

« back to all changes in this revision

Viewing changes to src/addins/AspNet/MonoDevelop.AspNet.Mvc/RazorEditorParserFixed/BackgroundParser.cs

  • Committer: Package Import Robot
  • Author(s): Jo Shields
  • Date: 2013-05-12 09:46:03 UTC
  • mto: This revision was merged to the branch mainline in revision 29.
  • Revision ID: package-import@ubuntu.com-20130512094603-mad323bzcxvmcam0
Tags: upstream-4.0.5+dfsg
ImportĀ upstreamĀ versionĀ 4.0.5+dfsg

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
ļ»æ// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
 
2
// Licensed under the Microsoft Public License (MS-PL)
 
3
 
 
4
using System;
 
5
using System.Collections.Generic;
 
6
using System.Diagnostics;
 
7
using System.Diagnostics.CodeAnalysis;
 
8
using System.IO;
 
9
using System.Linq;
 
10
using System.Text;
 
11
using System.Threading;
 
12
using System.Threading.Tasks;
 
13
using System.Web.Razor.Parser.SyntaxTree;
 
14
using System.Web.Razor.Resources;
 
15
using System.Web.Razor.Text;
 
16
using System.Web.Razor.Utils;
 
17
using System.Web.Razor;
 
18
using System.Web.Razor.Editor;
 
19
 
 
20
namespace RazorEditorParserFixed
 
21
{
 
22
    internal class BackgroundParser : IDisposable
 
23
    {
 
24
        private MainThreadState _main;
 
25
        private BackgroundThread _bg;
 
26
 
 
27
        public BackgroundParser(RazorEngineHost host, string fileName)
 
28
        {
 
29
            _main = new MainThreadState(fileName);
 
30
            _bg = new BackgroundThread(_main, host, fileName);
 
31
 
 
32
            _main.ResultsReady += (sender, args) => OnResultsReady(args);
 
33
        }
 
34
 
 
35
        /// <summary>
 
36
        /// Fired on the main thread.
 
37
        /// </summary>
 
38
        public event EventHandler<DocumentParseCompleteEventArgs> ResultsReady;
 
39
 
 
40
        public bool IsIdle
 
41
        {
 
42
            get { return _main.IsIdle; }
 
43
        }
 
44
 
 
45
        public void Start()
 
46
        {
 
47
            _bg.Start();
 
48
        }
 
49
 
 
50
        public void Cancel()
 
51
        {
 
52
            _main.Cancel();
 
53
        }
 
54
 
 
55
        public void QueueChange(TextChange change)
 
56
        {
 
57
            _main.QueueChange(change);
 
58
        }
 
59
 
 
60
        [SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "_main", Justification = "MainThreadState is disposed when the background thread shuts down")]
 
61
        public void Dispose()
 
62
        {
 
63
            _main.Cancel();
 
64
        }
 
65
 
 
66
        public IDisposable SynchronizeMainThreadState()
 
67
        {
 
68
            return _main.Lock();
 
69
        }
 
70
 
 
71
        protected virtual void OnResultsReady(DocumentParseCompleteEventArgs args)
 
72
        {
 
73
            var handler = ResultsReady;
 
74
            if (handler != null)
 
75
            {
 
76
                handler(this, args);
 
77
            }
 
78
        }
 
79
 
 
80
        internal static bool TreesAreDifferent(Block leftTree, Block rightTree, IEnumerable<TextChange> changes)
 
81
        {
 
82
            return TreesAreDifferent(leftTree, rightTree, changes, CancellationToken.None);
 
83
        }
 
84
 
 
85
        internal static bool TreesAreDifferent(Block leftTree, Block rightTree, IEnumerable<TextChange> changes, CancellationToken cancelToken)
 
86
        {
 
87
            // Apply all the pending changes to the original tree
 
88
            // PERF: If this becomes a bottleneck, we can probably do it the other way around,
 
89
            //  i.e. visit the tree and find applicable changes for each node.
 
90
            foreach (TextChange change in changes)
 
91
            {
 
92
                cancelToken.ThrowIfCancellationRequested();
 
93
                Span changeOwner = leftTree.LocateOwner(change);
 
94
 
 
95
                // Apply the change to the tree
 
96
                if (changeOwner == null)
 
97
                {
 
98
                    return true;
 
99
                }
 
100
                EditResult result = changeOwner.EditHandler.ApplyChange(changeOwner, change, force: true);
 
101
                changeOwner.ReplaceWith(result.EditedSpan);
 
102
            }
 
103
 
 
104
            // Now compare the trees
 
105
            bool treesDifferent = !leftTree.EquivalentTo(rightTree);
 
106
            return treesDifferent;
 
107
        }
 
108
 
 
109
        private abstract class ThreadStateBase
 
110
        {
 
111
#if RAZOREDITOR_DEBUG
 
112
            private int _id = -1;
 
113
#endif
 
114
            protected ThreadStateBase()
 
115
            {
 
116
            }
 
117
 
 
118
            [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "This method is only empty in Release builds. In Debug builds it contains references to instance variables")]
 
119
            [Conditional("RAZOREDITOR_DEBUG")]
 
120
            protected void SetThreadId(int id)
 
121
            {
 
122
#if RAZOREDITOR_DEBUG
 
123
                _id = id;
 
124
#endif
 
125
            }
 
126
 
 
127
            [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "This method is only empty in Release builds. In Debug builds it contains references to instance variables")]
 
128
            [Conditional("RAZOREDITOR_DEBUG")]
 
129
            protected void EnsureOnThread()
 
130
            {
 
131
#if RAZOREDITOR_DEBUG
 
132
                Debug.Assert(_id != -1, "SetThreadId was never called!");
 
133
                Debug.Assert(Thread.CurrentThread.ManagedThreadId == _id, "Called from an unexpected thread!");
 
134
#endif
 
135
            }
 
136
 
 
137
            [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "This method is only empty in Release builds. In Debug builds it contains references to instance variables")]
 
138
            [Conditional("RAZOREDITOR_DEBUG")]
 
139
            protected void EnsureNotOnThread()
 
140
            {
 
141
#if RAZOREDITOR_DEBUG
 
142
                Debug.Assert(_id != -1, "SetThreadId was never called!");
 
143
                Debug.Assert(Thread.CurrentThread.ManagedThreadId != _id, "Called from an unexpected thread!");
 
144
#endif
 
145
            }
 
146
        }
 
147
 
 
148
        private class MainThreadState : ThreadStateBase, IDisposable
 
149
        {
 
150
            private CancellationTokenSource _cancelSource = new CancellationTokenSource();
 
151
            private ManualResetEventSlim _hasParcel = new ManualResetEventSlim(false);
 
152
            private CancellationTokenSource _currentParcelCancelSource;
 
153
 
 
154
#if EDITOR_TRACING
 
155
            private string _fileName;
 
156
#endif
 
157
            private object _stateLock = new object();
 
158
            private IList<TextChange> _changes = new List<TextChange>();
 
159
 
 
160
            public MainThreadState(string fileName)
 
161
            {
 
162
#if EDITOR_TRACING
 
163
                _fileName = fileName;
 
164
#endif
 
165
 
 
166
                SetThreadId(Thread.CurrentThread.ManagedThreadId);
 
167
            }
 
168
 
 
169
            public event EventHandler<DocumentParseCompleteEventArgs> ResultsReady;
 
170
 
 
171
            public CancellationToken CancelToken
 
172
            {
 
173
                get { return _cancelSource.Token; }
 
174
            }
 
175
 
 
176
            public bool IsIdle
 
177
            {
 
178
                get
 
179
                {
 
180
                    lock (_stateLock)
 
181
                    {
 
182
                        return _currentParcelCancelSource == null;
 
183
                    }
 
184
                }
 
185
            }
 
186
 
 
187
            public void Cancel()
 
188
            {
 
189
                EnsureOnThread();
 
190
                _cancelSource.Cancel();
 
191
            }
 
192
 
 
193
            public IDisposable Lock()
 
194
            {
 
195
                Monitor.Enter(_stateLock);
 
196
                return new DisposableAction(() => Monitor.Exit(_stateLock));
 
197
            }
 
198
 
 
199
            public void QueueChange(TextChange change)
 
200
            {
 
201
#if EDITOR_TRACING
 
202
                RazorEditorTrace.TraceLine(RazorResources.Trace_QueuingParse, Path.GetFileName(_fileName), change);
 
203
#endif
 
204
                EnsureOnThread();
 
205
                lock (_stateLock)
 
206
                {
 
207
                    // CurrentParcel token source is not null ==> There's a parse underway
 
208
                    if (_currentParcelCancelSource != null)
 
209
                    {
 
210
                        _currentParcelCancelSource.Cancel();
 
211
                    }
 
212
 
 
213
                    _changes.Add(change);
 
214
                    _hasParcel.Set();
 
215
                }
 
216
            }
 
217
 
 
218
            public WorkParcel GetParcel()
 
219
            {
 
220
                EnsureNotOnThread(); // Only the background thread can get a parcel
 
221
                _hasParcel.Wait(_cancelSource.Token);
 
222
                _hasParcel.Reset();
 
223
                lock (_stateLock)
 
224
                {
 
225
                    // Create a cancellation source for this parcel
 
226
                    _currentParcelCancelSource = new CancellationTokenSource();
 
227
 
 
228
                    var changes = _changes;
 
229
                    _changes = new List<TextChange>();
 
230
                    return new WorkParcel(changes, _currentParcelCancelSource.Token);
 
231
                }
 
232
            }
 
233
 
 
234
            public void ReturnParcel(DocumentParseCompleteEventArgs args)
 
235
            {
 
236
                lock (_stateLock)
 
237
                {
 
238
                    // Clear the current parcel cancellation source
 
239
                    if (_currentParcelCancelSource != null)
 
240
                    {
 
241
                        _currentParcelCancelSource.Dispose();
 
242
                        _currentParcelCancelSource = null;
 
243
                    }
 
244
 
 
245
                    // If there are things waiting to be parsed, just don't fire the event because we're already out of date
 
246
                    if (_changes.Any())
 
247
                    {
 
248
                        return;
 
249
                    }
 
250
                }
 
251
                var handler = ResultsReady;
 
252
                if (handler != null)
 
253
                {
 
254
                    handler(this, args);
 
255
                }
 
256
            }
 
257
 
 
258
            public void Dispose()
 
259
            {
 
260
                Dispose(true);
 
261
                GC.SuppressFinalize(this);
 
262
            }
 
263
 
 
264
            protected virtual void Dispose(bool disposing)
 
265
            {
 
266
                if (disposing)
 
267
                {
 
268
                    if (_currentParcelCancelSource != null)
 
269
                    {
 
270
                        _currentParcelCancelSource.Dispose();
 
271
                        _currentParcelCancelSource = null;
 
272
                    }
 
273
                    _cancelSource.Dispose();
 
274
                    _hasParcel.Dispose();
 
275
                }
 
276
            }
 
277
        }
 
278
 
 
279
        private class BackgroundThread : ThreadStateBase
 
280
        {
 
281
            private MainThreadState _main;
 
282
            private Thread _backgroundThread;
 
283
            private CancellationToken _shutdownToken;
 
284
            private RazorEngineHost _host;
 
285
            private string _fileName;
 
286
            private Block _currentParseTree;
 
287
            private IList<TextChange> _previouslyDiscarded = new List<TextChange>();
 
288
 
 
289
            public BackgroundThread(MainThreadState main, RazorEngineHost host, string fileName)
 
290
            {
 
291
                // Run on MAIN thread!
 
292
                _main = main;
 
293
                _backgroundThread = new Thread(WorkerLoop) {
 
294
                                        IsBackground = true,
 
295
                                };
 
296
                _shutdownToken = _main.CancelToken;
 
297
                _host = host;
 
298
                _fileName = fileName;
 
299
 
 
300
                SetThreadId(_backgroundThread.ManagedThreadId);
 
301
            }
 
302
 
 
303
            // **** ANY THREAD ****
 
304
            public void Start()
 
305
            {
 
306
                _backgroundThread.Start();
 
307
            }
 
308
 
 
309
            // **** BACKGROUND THREAD ****
 
310
            private void WorkerLoop()
 
311
            {
 
312
                long? elapsedMs = null;
 
313
                string fileNameOnly = Path.GetFileName(_fileName);
 
314
#if EDITOR_TRACING
 
315
                Stopwatch sw = new Stopwatch();
 
316
#endif
 
317
 
 
318
                try
 
319
                {
 
320
                    RazorEditorTrace.TraceLine(RazorResources.Trace_BackgroundThreadStart, fileNameOnly);
 
321
                    EnsureOnThread();
 
322
                    while (!_shutdownToken.IsCancellationRequested)
 
323
                    {
 
324
                        // Grab the parcel of work to do
 
325
                        WorkParcel parcel = _main.GetParcel();
 
326
                        if (parcel.Changes.Any())
 
327
                        {
 
328
                            RazorEditorTrace.TraceLine(RazorResources.Trace_ChangesArrived, fileNameOnly, parcel.Changes.Count);
 
329
                            try
 
330
                            {
 
331
                                DocumentParseCompleteEventArgs args = null;
 
332
                                using (var linkedCancel = CancellationTokenSource.CreateLinkedTokenSource(_shutdownToken, parcel.CancelToken))
 
333
                                {
 
334
                                    if (parcel != null && !linkedCancel.IsCancellationRequested)
 
335
                                    {
 
336
                                        // Collect ALL changes
 
337
#if EDITOR_TRACING
 
338
                                        if (_previouslyDiscarded != null && _previouslyDiscarded.Any())
 
339
                                        {
 
340
                                            RazorEditorTrace.TraceLine(RazorResources.Trace_CollectedDiscardedChanges, fileNameOnly, _previouslyDiscarded.Count);
 
341
                                        }
 
342
#endif
 
343
                                        var allChanges = Enumerable.Concat(
 
344
                                            _previouslyDiscarded ?? Enumerable.Empty<TextChange>(), parcel.Changes).ToList();
 
345
                                        var finalChange = allChanges.LastOrDefault();
 
346
                                        if (finalChange != default (TextChange))
 
347
                                        {
 
348
#if EDITOR_TRACING
 
349
                                            sw.Start();
 
350
#endif
 
351
                                            GeneratorResults results = ParseChange(finalChange.NewBuffer, linkedCancel.Token);
 
352
#if EDITOR_TRACING
 
353
                                            sw.Stop();
 
354
                                            elapsedMs = sw.ElapsedMilliseconds;
 
355
                                            sw.Reset();
 
356
#endif
 
357
                                            RazorEditorTrace.TraceLine(
 
358
                                                RazorResources.Trace_ParseComplete,
 
359
                                                fileNameOnly,
 
360
                                                elapsedMs.HasValue ? elapsedMs.Value.ToString() : "?");
 
361
 
 
362
                                            if (results != null && !linkedCancel.IsCancellationRequested)
 
363
                                            {
 
364
                                                // Clear discarded changes list
 
365
                                                _previouslyDiscarded = null;
 
366
 
 
367
                                                // Take the current tree and check for differences
 
368
#if EDITOR_TRACING
 
369
                                                sw.Start();
 
370
#endif
 
371
                                                bool treeStructureChanged = _currentParseTree == null || TreesAreDifferent(_currentParseTree, results.Document, allChanges, parcel.CancelToken);
 
372
#if EDITOR_TRACING
 
373
                                                sw.Stop();
 
374
                                                elapsedMs = sw.ElapsedMilliseconds;
 
375
                                                sw.Reset();
 
376
#endif
 
377
                                                _currentParseTree = results.Document;
 
378
                                                RazorEditorTrace.TraceLine(RazorResources.Trace_TreesCompared,
 
379
                                                    fileNameOnly,
 
380
                                                    elapsedMs.HasValue ? elapsedMs.Value.ToString() : "?",
 
381
                                                    treeStructureChanged);
 
382
 
 
383
                                                // Build Arguments
 
384
                                                args = new DocumentParseCompleteEventArgs()
 
385
                                                {
 
386
                                                    GeneratorResults = results,
 
387
                                                    SourceChange = finalChange,
 
388
                                                    TreeStructureChanged = treeStructureChanged
 
389
                                                };
 
390
                                            }
 
391
                                            else
 
392
                                            {
 
393
                                                // Parse completed but we were cancelled in the mean time. Add these to the discarded changes set
 
394
                                                RazorEditorTrace.TraceLine(RazorResources.Trace_ChangesDiscarded, fileNameOnly, allChanges.Count);
 
395
                                                _previouslyDiscarded = allChanges;
 
396
                                            }
 
397
 
 
398
#if CHECK_TREE
 
399
                                            if (args != null)
 
400
                                            {
 
401
                                                // Rewind the buffer and sanity check the line mappings
 
402
                                                finalChange.NewBuffer.Position = 0;
 
403
                                                int lineCount = finalChange.NewBuffer.ReadToEnd().Split(new string[] { Environment.NewLine, "\r", "\n" }, StringSplitOptions.None).Count();
 
404
                                                Debug.Assert(
 
405
                                                    !args.GeneratorResults.DesignTimeLineMappings.Any(pair => pair.Value.StartLine > lineCount),
 
406
                                                    "Found a design-time line mapping referring to a line outside the source file!");
 
407
                                                Debug.Assert(
 
408
                                                    !args.GeneratorResults.Document.Flatten().Any(span => span.Start.LineIndex > lineCount),
 
409
                                                    "Found a span with a line number outside the source file");
 
410
                                                Debug.Assert(
 
411
                                                    !args.GeneratorResults.Document.Flatten().Any(span => span.Start.AbsoluteIndex > parcel.NewBuffer.Length),
 
412
                                                    "Found a span with an absolute offset outside the source file");
 
413
                                            }
 
414
#endif
 
415
                                        }
 
416
                                    }
 
417
                                }
 
418
                                if (args != null)
 
419
                                {
 
420
                                    _main.ReturnParcel(args);
 
421
                                }
 
422
                            }
 
423
                            catch (OperationCanceledException)
 
424
                            {
 
425
                            }
 
426
                        }
 
427
                        else
 
428
                        {
 
429
                            RazorEditorTrace.TraceLine(RazorResources.Trace_NoChangesArrived, fileNameOnly, parcel.Changes.Count);
 
430
                            Thread.Yield();
 
431
                        }
 
432
                    }
 
433
                }
 
434
                catch (OperationCanceledException)
 
435
                {
 
436
                    // Do nothing. Just shut down.
 
437
                }
 
438
                catch (Exception ex)
 
439
                {
 
440
                    MonoDevelop.Core.LoggingService.LogError ("Internal error in Razor parser", ex);
 
441
                    MonoDevelop.Core.LogReporting.LogReportingService.ReportUnhandledException (ex, false);
 
442
                }
 
443
                finally
 
444
                {
 
445
                    RazorEditorTrace.TraceLine(RazorResources.Trace_BackgroundThreadShutdown, fileNameOnly);
 
446
 
 
447
                    // Clean up main thread resources
 
448
                    _main.Dispose();
 
449
                }
 
450
            }
 
451
 
 
452
            private GeneratorResults ParseChange(ITextBuffer buffer, CancellationToken token)
 
453
            {
 
454
                EnsureOnThread();
 
455
 
 
456
                // Create a template engine
 
457
                RazorTemplateEngine engine = new RazorTemplateEngine(_host);
 
458
 
 
459
                // Seek the buffer to the beginning
 
460
                buffer.Position = 0;
 
461
 
 
462
                try
 
463
                {
 
464
                    return engine.GenerateCode(
 
465
                        input: buffer,
 
466
                        className: null,
 
467
                        rootNamespace: null,
 
468
                        sourceFileName: _fileName,
 
469
                        cancelToken: token);
 
470
                }
 
471
                catch (OperationCanceledException)
 
472
                {
 
473
                    return null;
 
474
                }
 
475
            }
 
476
        }
 
477
 
 
478
        private class WorkParcel
 
479
        {
 
480
            public WorkParcel(IList<TextChange> changes, CancellationToken cancelToken)
 
481
            {
 
482
                Changes = changes;
 
483
                CancelToken = cancelToken;
 
484
            }
 
485
 
 
486
            public CancellationToken CancelToken { get; private set; }
 
487
            public IList<TextChange> Changes { get; private set; }
 
488
        }
 
489
    }
 
490
}