@@ -10,20 +10,16 @@ namespace ICSharpCode.CodeConverter.Shared
1010 /// </summary>
1111 public class OptionalOperations
1212 {
13+ private readonly TimeSpan _abandonTasksIfNoActivityFor ;
1314 private readonly IProgress < ConversionProgress > _progress ;
1415 private readonly CancellationToken _wholeTaskCancellationToken ;
15- private readonly ActivityMonitor _activityMonitor ;
16- private readonly CancellationTokenSource _optionalTaskCts ;
1716
1817 public OptionalOperations ( TimeSpan abandonTasksIfNoActivityFor , IProgress < ConversionProgress > progress ,
1918 CancellationToken wholeTaskCancellationToken )
2019 {
20+ _abandonTasksIfNoActivityFor = abandonTasksIfNoActivityFor ;
2121 _progress = progress ;
2222 _wholeTaskCancellationToken = wholeTaskCancellationToken ;
23- _optionalTaskCts = CancellationTokenSource . CreateLinkedTokenSource ( wholeTaskCancellationToken ) ;
24- _activityMonitor = new ActivityMonitor ( abandonTasksIfNoActivityFor , _optionalTaskCts , ( ) =>
25- _progress . Report ( new ConversionProgress ( "WARNING: Skipping all further formatting, you can increase the timeout for this in Tools -> Options -> Code Converter." ) )
26- ) ;
2723 }
2824
2925 public SyntaxNode MapSourceTriviaToTargetHandled < TSource , TTarget > ( TSource root ,
@@ -44,85 +40,15 @@ public SyntaxNode MapSourceTriviaToTargetHandled<TSource, TTarget>(TSource root,
4440
4541 public SyntaxNode Format ( SyntaxNode node , Document document )
4642 {
47- if ( ! _optionalTaskCts . IsCancellationRequested ) {
48- try {
49- _optionalTaskCts . Token . ThrowIfCancellationRequested ( ) ;
50- _activityMonitor . ActivityStarted ( ) ;
51- // This call is very expensive for large documents. Should look for a more performant version, e.g. Is NormalizeWhitespace good enough?
52- return Formatter . Format ( node , document . Project . Solution . Workspace ,
53- cancellationToken : _optionalTaskCts . Token ) ;
54-
55- } catch ( OperationCanceledException ) {
56- } finally {
57- _activityMonitor . ActivityFinished ( ) ;
58- }
59- }
60- _progress . Report ( new ConversionProgress ( "Skipped formatting" , 1 ) ) ;
61- return node . NormalizeWhitespace ( ) ;
62- }
63-
64- /// <summary>
65- /// Reasonably lightweight check that there's been some activity within the given timeout.
66- /// </summary>
67- private class ActivityMonitor
68- {
69- private readonly TimeSpan _timeout ;
70- private readonly CancellationTokenSource _cts ;
71- private readonly Action _afterInactivity ;
72- private volatile int _activeOperations ;
73- /// <summary>
74- /// Must check <see cref="_activeOperations"/> within the lock before changed timer.Enabled
75- /// This avoids race conditions between the last task of a set finishing and the first of a new set starting
76- /// </summary>
77- private readonly object _timerEnabledWriteLock = new object ( ) ;
78- private static Timer _timer ;
79-
80-
81- private void OnTimedEvent ( object state )
82- {
83- if ( ! _cts . IsCancellationRequested && _activeOperations > 0 ) {
84- _cts . Cancel ( ) ;
85- _afterInactivity ( ) ;
86- } else if ( _activeOperations > 0 ) {
87- ActivityObserved ( ) ;
88- }
89- }
90-
91- public ActivityMonitor ( TimeSpan timeout , CancellationTokenSource cts , Action afterInactivity )
92- {
93- _timeout = timeout ;
94- _cts = cts ;
95- _afterInactivity = afterInactivity ;
96- _timer = new Timer ( OnTimedEvent , null , Timeout . InfiniteTimeSpan , Timeout . InfiniteTimeSpan ) ;
97- }
98-
99- public void ActivityStarted ( )
100- {
101- if ( Interlocked . Increment ( ref _activeOperations ) == 1 ) {
102- lock ( _timerEnabledWriteLock ) {
103- if ( _activeOperations > 0 ) {
104- ActivityObserved ( ) ;
105- }
106- }
107- }
108- ActivityObserved ( ) ;
109- }
110-
111- public void ActivityFinished ( )
112- {
113- ActivityObserved ( ) ;
114- if ( Interlocked . Decrement ( ref _activeOperations ) == 0 ) {
115- lock ( _timerEnabledWriteLock ) {
116- if ( _activeOperations == 0 ) {
117- _timer . Change ( Timeout . InfiniteTimeSpan , Timeout . InfiniteTimeSpan ) ;
118- }
119- }
120- }
121- }
122-
123- private void ActivityObserved ( )
124- {
125- _timer . Change ( _timeout , Timeout . InfiniteTimeSpan ) ;
43+ using var cts = CancellationTokenSource . CreateLinkedTokenSource ( _wholeTaskCancellationToken ) ;
44+ var token = cts . Token ;
45+ cts . CancelAfter ( _abandonTasksIfNoActivityFor ) ;
46+ try {
47+ // This call is very expensive for large documents. Should look for a more performant version, e.g. Is NormalizeWhitespace good enough?
48+ return Formatter . Format ( node , document . Project . Solution . Workspace , cancellationToken : token ) ;
49+ } catch ( OperationCanceledException ) {
50+ _progress . Report ( new ConversionProgress ( "Timeout expired - falling back to basic formatting. If within Visual Studio you can adjust the timeout in Tools -> Options -> Code Converter." , 1 ) ) ;
51+ return node . NormalizeWhitespace ( ) ;
12652 }
12753 }
12854 }
0 commit comments