Skip to content

Commit b85e385

Browse files
committed
Return a struct containing encountered exceptions as suspension result
This allows the encountered exceptions to be rethrown using ExceptionDispatchInfo.Capture.
1 parent 1e11397 commit b85e385

File tree

10 files changed

+109
-64
lines changed

10 files changed

+109
-64
lines changed

Rubberduck.Core/UI/CodeExplorer/Commands/ImportCommand.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.IO;
44
using System.Linq;
5+
using System.Runtime.ExceptionServices;
56
using System.Windows.Forms;
67
using Rubberduck.Interaction;
78
using Rubberduck.JunkDrawer.Extensions;
@@ -186,9 +187,16 @@ private void NotifyUserAboutAbortDueToUnsupportedFileExtensions(IEnumerable<stri
186187

187188
private void ImportFilesWithSuspension(ICollection<string> filesToImport, IVBProject targetProject)
188189
{
189-
var suspensionResult = _parseManager.OnSuspendParser(this, new[] {ParserState.Ready}, () => ImportFiles(filesToImport, targetProject));
190-
if (suspensionResult != SuspensionResult.Completed)
190+
var suspendResult = _parseManager.OnSuspendParser(this, new[] {ParserState.Ready}, () => ImportFiles(filesToImport, targetProject));
191+
var suspendOutcome = suspendResult.Outcome;
192+
if (suspendOutcome != SuspensionOutcome.Completed)
191193
{
194+
if (suspendOutcome == SuspensionOutcome.UnexpectedError || suspendOutcome == SuspensionOutcome.Canceled)
195+
{
196+
ExceptionDispatchInfo.Capture(suspendResult.EncounteredException).Throw();
197+
return;
198+
}
199+
192200
Logger.Warn("File import failed due to suspension failure.");
193201
}
194202
}

Rubberduck.Parsing/Rewriter/AttributesRewriteSession.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,13 @@ protected override bool TryRewriteInternal()
3232
PrimeOpenStateRecovery();
3333

3434
var result = _parseManager.OnSuspendParser(this, new[] {ParserState.Ready, ParserState.ResolvedDeclarations}, ExecuteAllRewriters);
35-
if(result != SuspensionResult.Completed)
35+
if(result.Outcome != SuspensionOutcome.Completed)
3636
{
3737
Logger.Warn($"Rewriting attribute modules did not succeed. suspension result = {result}");
38+
if (result.EncounteredException != null)
39+
{
40+
Logger.Warn(result.EncounteredException);
41+
}
3842
return false;
3943
}
4044

Rubberduck.Parsing/VBA/IParseManager.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,55 @@ public interface IParseManager : IParserStatusProvider
3131
SuspensionResult OnSuspendParser(object requestor, IEnumerable<ParserState> allowedRunStates, Action busyAction, int millisecondsTimeout = -1);
3232
void MarkAsModified(QualifiedModuleName module);
3333
}
34+
35+
36+
37+
public enum SuspensionOutcome
38+
{
39+
/// <summary>
40+
/// The busy action has been queued but has not run yet.
41+
/// </summary>
42+
Pending,
43+
/// <summary>
44+
/// The busy action was completed successfully.
45+
/// </summary>
46+
Completed,
47+
/// <summary>
48+
/// The busy action could not executed because it timed out when
49+
/// attempting to obtain a suspension lock. The timeout is
50+
/// governed by the MillisecondsTimeout argument.
51+
/// </summary>
52+
TimedOut,
53+
/// <summary>
54+
/// The parser arrived to one of states that wasn't listed in the
55+
/// AllowedRunStates specified by the requestor (e.g. an error state)
56+
/// and thus the busy action was not executed.
57+
/// </summary>
58+
IncompatibleState,
59+
/// <summary>
60+
/// Indicates that the suspension request cannot be made because there
61+
/// is no handler for it. This points to a bug in the code.
62+
/// </summary>
63+
NotEnabled,
64+
/// <summary>
65+
/// The suspend action has thrown an OperationCanceledException.
66+
/// </summary>
67+
Canceled,
68+
/// <summary>
69+
/// An unexpected error; usually indicates a bug in code.
70+
/// </summary>
71+
UnexpectedError
72+
}
73+
74+
public readonly struct SuspensionResult
75+
{
76+
public SuspensionResult(SuspensionOutcome outcome, Exception encounteredException = null)
77+
{
78+
Outcome = outcome;
79+
EncounteredException = encounteredException;
80+
}
81+
82+
public SuspensionOutcome Outcome { get; }
83+
public Exception EncounteredException { get; }
84+
}
3485
}

Rubberduck.Parsing/VBA/ParseCoordinator.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ public void SuspendRequested(object sender, RubberduckStatusSuspendParserEventAr
116116
{
117117
if (ParsingSuspendLock.IsReadLockHeld)
118118
{
119-
e.Result = SuspensionResult.UnexpectedError;
119+
e.Result = SuspensionOutcome.UnexpectedError;
120120
const string errorMessage =
121121
"A suspension action was attempted while a read lock was held. This indicates a bug in the code logic as suspension should not be requested from same thread that has a read lock.";
122122
Logger.Error(errorMessage);
@@ -131,7 +131,7 @@ public void SuspendRequested(object sender, RubberduckStatusSuspendParserEventAr
131131
{
132132
if (!ParsingSuspendLock.TryEnterWriteLock(e.MillisecondsTimeout))
133133
{
134-
e.Result = SuspensionResult.TimedOut;
134+
e.Result = SuspensionOutcome.TimedOut;
135135
return;
136136
}
137137

@@ -143,7 +143,7 @@ public void SuspendRequested(object sender, RubberduckStatusSuspendParserEventAr
143143
var originalStatus = State.Status;
144144
if (!e.AllowedRunStates.Contains(originalStatus))
145145
{
146-
e.Result = SuspensionResult.IncompatibleState;
146+
e.Result = SuspensionOutcome.IncompatibleState;
147147
return;
148148
}
149149
_parserStateManager.SetStatusAndFireStateChanged(e.Requestor, ParserState.Busy,
@@ -152,13 +152,13 @@ public void SuspendRequested(object sender, RubberduckStatusSuspendParserEventAr
152152
}
153153
catch (OperationCanceledException ex)
154154
{
155-
e.Result = SuspensionResult.Canceled;
155+
e.Result = SuspensionOutcome.Canceled;
156156
e.EncounteredException = ex;
157157
throw;
158158
}
159159
catch (Exception ex)
160160
{
161-
e.Result = SuspensionResult.UnexpectedError;
161+
e.Result = SuspensionOutcome.UnexpectedError;
162162
e.EncounteredException = ex;
163163
throw;
164164
}
@@ -190,9 +190,9 @@ public void SuspendRequested(object sender, RubberduckStatusSuspendParserEventAr
190190
ParsingSuspendLock.ExitWriteLock();
191191
}
192192

193-
if (e.Result == SuspensionResult.Pending)
193+
if (e.Result == SuspensionOutcome.Pending)
194194
{
195-
e.Result = SuspensionResult.Completed;
195+
e.Result = SuspensionOutcome.Completed;
196196
}
197197
}
198198

Rubberduck.Parsing/VBA/RubberduckParserState.cs

Lines changed: 4 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -60,43 +60,6 @@ public ParserStateEventArgs(ParserState state, ParserState oldState, Cancellatio
6060
State == ParserState.UnexpectedError);
6161
}
6262

63-
public enum SuspensionResult
64-
{
65-
/// <summary>
66-
/// The busy action has been queued but has not run yet.
67-
/// </summary>
68-
Pending,
69-
/// <summary>
70-
/// The busy action was completed successfully.
71-
/// </summary>
72-
Completed,
73-
/// <summary>
74-
/// The busy action could not executed because it timed out when
75-
/// attempting to obtain a suspension lock. The timeout is
76-
/// governed by the MillisecondsTimeout argument.
77-
/// </summary>
78-
TimedOut,
79-
/// <summary>
80-
/// The parser arrived to one of states that wasn't listed in the
81-
/// AllowedRunStates specified by the requestor (e.g. an error state)
82-
/// and thus the busy action was not executed.
83-
/// </summary>
84-
IncompatibleState,
85-
/// <summary>
86-
/// Indicates that the suspension request cannot be made because there
87-
/// is no handler for it. This points to a bug in the code.
88-
/// </summary>
89-
NotEnabled,
90-
/// <summary>
91-
/// The suspend action has thrown an OperationCanceledException.
92-
/// </summary>
93-
Canceled,
94-
/// <summary>
95-
/// An unexpected error; usually indicates a bug in code.
96-
/// </summary>
97-
UnexpectedError
98-
}
99-
10063
public class RubberduckStatusSuspendParserEventArgs : EventArgs
10164
{
10265
public RubberduckStatusSuspendParserEventArgs(object requestor, IEnumerable<ParserState> allowedRunStates, Action busyAction, int millisecondsTimeout)
@@ -105,14 +68,14 @@ public RubberduckStatusSuspendParserEventArgs(object requestor, IEnumerable<Pars
10568
AllowedRunStates = allowedRunStates;
10669
BusyAction = busyAction;
10770
MillisecondsTimeout = millisecondsTimeout;
108-
Result = SuspensionResult.Pending;
71+
Result = SuspensionOutcome.Pending;
10972
}
11073

11174
public object Requestor { get; }
11275
public IEnumerable<ParserState> AllowedRunStates { get; }
11376
public Action BusyAction { get; }
11477
public int MillisecondsTimeout { get; }
115-
public SuspensionResult Result { get; set; }
78+
public SuspensionOutcome Result { get; set; }
11679
public Exception EncounteredException { get; set; }
11780
}
11881

@@ -1015,10 +978,10 @@ public SuspensionResult OnSuspendParser(object requestor, IEnumerable<ParserStat
1015978
{
1016979
var args = new RubberduckStatusSuspendParserEventArgs(requestor, allowedRunStates, busyAction, millisecondsTimeout);
1017980
handler.Invoke(requestor, args);
1018-
return args.Result;
981+
return new SuspensionResult(args.Result, args.EncounteredException);
1019982
}
1020983

1021-
return SuspensionResult.NotEnabled;
984+
return new SuspensionResult(SuspensionOutcome.NotEnabled);
1022985
}
1023986

1024987
public bool IsNewOrModified(IVBComponent component)

Rubberduck.Refactorings/ExtractInterface/ExtractInterfaceRefactoring.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Linq;
3+
using System.Runtime.ExceptionServices;
34
using NLog;
45
using Rubberduck.Parsing;
56
using Rubberduck.Parsing.Grammar;
@@ -71,8 +72,16 @@ private void AddInterfaceWithSuspendedParser(ExtractInterfaceModel model)
7172
{
7273
//We need to suspend here since adding the interface and rewriting will both trigger a reparse.
7374
var suspendResult = _parseManager.OnSuspendParser(this, new[] {ParserState.Ready}, () => AddInterface(model));
74-
if (suspendResult != SuspensionResult.Completed)
75+
var suspendOutcome = suspendResult.Outcome;
76+
if (suspendOutcome != SuspensionOutcome.Completed)
7577
{
78+
if ((suspendOutcome == SuspensionOutcome.UnexpectedError || suspendOutcome == SuspensionOutcome.Canceled)
79+
&& suspendResult.EncounteredException != null)
80+
{
81+
ExceptionDispatchInfo.Capture(suspendResult.EncounteredException).Throw();
82+
return;
83+
}
84+
7685
_logger.Warn($"{nameof(AddInterface)} failed because a parser suspension request could not be fulfilled. The request's result was '{suspendResult.ToString()}'.");
7786
throw new SuspendParserFailureException();
7887
}

Rubberduck.Refactorings/Rename/RenameRefactoring.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System;
77
using System.Diagnostics;
88
using System.Collections.Generic;
9+
using System.Runtime.ExceptionServices;
910
using Rubberduck.Parsing.Grammar;
1011
using Rubberduck.Parsing.Rewriter;
1112
using Rubberduck.Refactorings.Exceptions;
@@ -91,8 +92,16 @@ protected override void RefactorImpl(RenameModel model)
9192
private void RenameRefactorWithSuspendedParser(RenameModel model)
9293
{
9394
var suspendResult = _parseManager.OnSuspendParser(this, new[] { ParserState.Ready }, () => RenameRefactor(model));
94-
if (suspendResult != SuspensionResult.Completed)
95+
var suspendOutcome = suspendResult.Outcome;
96+
if (suspendOutcome != SuspensionOutcome.Completed)
9597
{
98+
if ((suspendOutcome == SuspensionOutcome.UnexpectedError || suspendOutcome == SuspensionOutcome.Canceled)
99+
&& suspendResult.EncounteredException != null)
100+
{
101+
ExceptionDispatchInfo.Capture(suspendResult.EncounteredException).Throw();
102+
return;
103+
}
104+
96105
_logger.Warn($"{nameof(RenameRefactor)} failed because a parser suspension request could not be fulfilled. The request's result was '{suspendResult.ToString()}'.");
97106
throw new SuspendParserFailureException();
98107
}

RubberduckTests/ParserState/ParserStateTests.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ public void Test_RPS_SuspendParser_IsQueued()
9494
[Category("ParserState")]
9595
public void Test_RPS_SuspendParser_Exception_Suspending_Inside_Parse()
9696
{
97-
var result = SuspensionResult.Pending;
97+
var result = SuspensionOutcome.Pending;
9898
var wasRun = false;
9999
var wasSuspended = false;
100100

@@ -110,13 +110,13 @@ public void Test_RPS_SuspendParser_Exception_Suspending_Inside_Parse()
110110
{
111111
// Cheap hack to run in same thread. Should not be done in production
112112
wasSuspended = true;
113-
});
113+
}).Outcome;
114114
}
115115
};
116116
state.OnParseRequested(this);
117117
}
118118
Assert.IsFalse(wasSuspended);
119-
Assert.AreEqual(SuspensionResult.UnexpectedError, result);
119+
Assert.AreEqual(SuspensionOutcome.UnexpectedError, result);
120120
}
121121

122122
[Test]
@@ -204,7 +204,7 @@ public void Test_RPS_SuspendParser_Interrupted_IsQueued()
204204
public void Test_RPS_SuspendParser_Interrupted_Deadlock()
205205
{
206206
var wasSuspended = false;
207-
var result = SuspensionResult.Pending;
207+
var result = SuspensionOutcome.Pending;
208208

209209
var vbe = MockVbeBuilder.BuildFromSingleModule("", ComponentType.StandardModule, out var _);
210210
using (var state = MockParser.CreateAndParse(vbe.Object))
@@ -225,7 +225,8 @@ public void Test_RPS_SuspendParser_Interrupted_Deadlock()
225225
result =
226226
state.OnSuspendParser(this, AllowedRunStates,
227227
() => { wasSuspended = state.Status == ParserState.Busy; },
228-
20);
228+
20)
229+
.Outcome;
229230
}, token);
230231
result2.Wait(token);
231232
}
@@ -242,7 +243,7 @@ public void Test_RPS_SuspendParser_Interrupted_Deadlock()
242243
result2.Wait(token);
243244
}
244245
Assert.IsFalse(wasSuspended, "wasSuspended was set to true");
245-
Assert.AreEqual(SuspensionResult.TimedOut, result);
246+
Assert.AreEqual(SuspensionOutcome.TimedOut, result);
246247
}
247248

248249
[Test]

RubberduckTests/Rewriter/AttributesRewriteSessionTests.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public void UsesASuspendActionToRewrite()
2020
var mockParseManager = new Mock<IParseManager>();
2121
mockParseManager.Setup(m => m.OnSuspendParser(It.IsAny<object>(), It.IsAny<IEnumerable<ParserState>>(), It.IsAny<Action>(), It.IsAny<int>()))
2222
.Callback((object requestor, IEnumerable<ParserState> allowedStates, Action suspendAction, int timeout) => suspendAction())
23-
.Returns((object requestor, IEnumerable<ParserState> allowedStates, Action suspendAction, int timeout) => SuspensionResult.Completed);
23+
.Returns((object requestor, IEnumerable<ParserState> allowedStates, Action suspendAction, int timeout) => new SuspensionResult(SuspensionOutcome.Completed));
2424

2525
var rewriteSession = RewriteSession(mockParseManager.Object, session => true, out _);
2626
var module = new QualifiedModuleName("TestProject", string.Empty, "TestModule");
@@ -37,7 +37,7 @@ public void DoesNotCallRewriteOutsideTheSuspendAction()
3737
{
3838
var mockParseManager = new Mock<IParseManager>();
3939
mockParseManager.Setup(m => m.OnSuspendParser(It.IsAny<object>(), It.IsAny<IEnumerable<ParserState>>(), It.IsAny<Action>(), It.IsAny<int>()))
40-
.Returns((object requestor, IEnumerable<ParserState> allowedStates, Action suspendAction, int timeout) => SuspensionResult.Completed);
40+
.Returns((object requestor, IEnumerable<ParserState> allowedStates, Action suspendAction, int timeout) => new SuspensionResult(SuspensionOutcome.Completed));
4141

4242
var rewriteSession = RewriteSession(mockParseManager.Object, session => true, out var mockRewriterProvider);
4343
var module = new QualifiedModuleName("TestProject", string.Empty, "TestModule");
@@ -56,7 +56,7 @@ public void TryRewriteReturnsFalseIfNotInvalidatedAndParsingAllowedAndSuspension
5656
var parseManager = new Mock<IParseManager>();
5757
parseManager.Setup(m => m.OnSuspendParser(It.IsAny<object>(), It.IsAny<IEnumerable<ParserState>>(), It.IsAny<Action>(), It.IsAny<int>()))
5858
.Callback((object requestor, IEnumerable<ParserState> allowedStates, Action suspendAction, int timeout) => suspendAction())
59-
.Returns((object requestor, IEnumerable<ParserState> allowedStates, Action suspendAction, int timeout) => SuspensionResult.UnexpectedError);
59+
.Returns((object requestor, IEnumerable<ParserState> allowedStates, Action suspendAction, int timeout) => new SuspensionResult(SuspensionOutcome.UnexpectedError));
6060

6161
var rewriteSession = RewriteSession(parseManager.Object, session => true, out _);
6262
var module = new QualifiedModuleName("TestProject", string.Empty, "TestModule");
@@ -72,7 +72,7 @@ public void TryRewriteReturnsTrueIfNotInvalidatedAndParsingAllowedAndSuspensionC
7272
var parseManager = new Mock<IParseManager>();
7373
parseManager.Setup(m => m.OnSuspendParser(It.IsAny<object>(), It.IsAny<IEnumerable<ParserState>>(), It.IsAny<Action>(), It.IsAny<int>()))
7474
.Callback((object requestor, IEnumerable<ParserState> allowedStates, Action suspendAction, int timeout) => suspendAction())
75-
.Returns((object requestor, IEnumerable<ParserState> allowedStates, Action suspendAction, int timeout) => SuspensionResult.Completed);
75+
.Returns((object requestor, IEnumerable<ParserState> allowedStates, Action suspendAction, int timeout) => new SuspensionResult(SuspensionOutcome.Completed));
7676

7777
var rewriteSession = RewriteSession(parseManager.Object, session => true, out _);
7878
var module = new QualifiedModuleName("TestProject", string.Empty, "TestModule");

RubberduckTests/Rewriter/RewriteSessionTestBase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@ protected IExecutableRewriteSession RewriteSession(Func<IRewriteSession, bool> r
357357
var parseManager = new Mock<IParseManager>();
358358
parseManager.Setup(m => m.OnSuspendParser(It.IsAny<object>(), It.IsAny<IEnumerable<ParserState>>(), It.IsAny<Action>(), It.IsAny<int>()))
359359
.Callback((object requestor, IEnumerable<ParserState> allowedStates, Action suspendAction, int timeout) => suspendAction())
360-
.Returns((object requestor, IEnumerable<ParserState> allowedStates, Action suspendAction, int timeout) => SuspensionResult.Completed);
360+
.Returns((object requestor, IEnumerable<ParserState> allowedStates, Action suspendAction, int timeout) => new SuspensionResult(SuspensionOutcome.Completed));
361361
return RewriteSession(parseManager.Object, rewritingAllowed, out mockProvider, rewritersAreDirty, selectionRecoverer);
362362
}
363363

0 commit comments

Comments
 (0)