Skip to content

Commit fe54e27

Browse files
Use simpler per-operation timeout
1 parent 05c3fb1 commit fe54e27

File tree

1 file changed

+11
-85
lines changed

1 file changed

+11
-85
lines changed

CodeConverter/Shared/OptionalOperations.cs

Lines changed: 11 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)