Skip to content

Commit 6fa555a

Browse files
committed
Improve error handling when invoking IFeatureProvider implementations
This change introduces the FeatureComponentBase class which provides some helpful behavior for dealing with errors when invoking feature providers. The default behavior is to catch and log all exceptions, but this may be changed later.
1 parent 196d1c2 commit 6fa555a

File tree

3 files changed

+121
-16
lines changed

3 files changed

+121
-16
lines changed

src/PowerShellEditorServices.Host/CodeLens/CodeLensFeature.cs

+7-7
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,21 @@
1717

1818
namespace Microsoft.PowerShell.EditorServices.CodeLenses
1919
{
20-
internal class CodeLensFeature : ICodeLenses
20+
internal class CodeLensFeature :
21+
FeatureComponentBase<ICodeLensProvider>,
22+
ICodeLenses
2123
{
2224
private EditorSession editorSession;
2325

2426
private JsonSerializer jsonSerializer =
2527
JsonSerializer.Create(
2628
Constants.JsonSerializerSettings);
2729

28-
public IFeatureProviderCollection<ICodeLensProvider> Providers { get; } =
29-
new FeatureProviderCollection<ICodeLensProvider>();
30-
3130
public CodeLensFeature(
3231
EditorSession editorSession,
3332
IMessageHandlers messageHandlers,
3433
ILogger logger)
34+
: base(logger)
3535
{
3636
this.editorSession = editorSession;
3737

@@ -70,8 +70,8 @@ public static CodeLensFeature Create(
7070
public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile)
7171
{
7272
return
73-
this.Providers
74-
.SelectMany(p => p.ProvideCodeLenses(scriptFile))
73+
this.InvokeProviders(p => p.ProvideCodeLenses(scriptFile))
74+
.SelectMany(r => r)
7575
.ToArray();
7676
}
7777

@@ -88,7 +88,7 @@ private async Task HandleCodeLensRequest(
8888
codeLensParams.TextDocument.Uri);
8989

9090
var codeLenses =
91-
this.ProvideCodeLenses(scriptFile)
91+
this.ProvideCodeLenses(scriptFile)
9292
.Select(
9393
codeLens =>
9494
codeLens.ToProtocolCodeLens(

src/PowerShellEditorServices.Host/Symbols/DocumentSymbolFeature.cs

+10-9
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,17 @@
1717

1818
namespace Microsoft.PowerShell.EditorServices.Symbols
1919
{
20-
internal class DocumentSymbolFeature : IDocumentSymbols
20+
internal class DocumentSymbolFeature :
21+
FeatureComponentBase<IDocumentSymbolProvider>,
22+
IDocumentSymbols
2123
{
2224
private EditorSession editorSession;
2325

24-
public IFeatureProviderCollection<IDocumentSymbolProvider> Providers { get; } =
25-
new FeatureProviderCollection<IDocumentSymbolProvider>();
26-
2726
public DocumentSymbolFeature(
2827
EditorSession editorSession,
2928
IMessageHandlers messageHandlers,
3029
ILogger logger)
30+
: base(logger)
3131
{
3232
this.editorSession = editorSession;
3333

@@ -61,9 +61,12 @@ public static DocumentSymbolFeature Create(
6161
return documentSymbols;
6262
}
6363

64-
public IEnumerable<SymbolReference> ProvideDocumentSymbols(ScriptFile scriptFile)
64+
public IEnumerable<SymbolReference> ProvideDocumentSymbols(
65+
ScriptFile scriptFile)
6566
{
66-
throw new NotImplementedException();
67+
return
68+
this.InvokeProviders(p => p.ProvideDocumentSymbols(scriptFile))
69+
.SelectMany(r => r);
6770
}
6871

6972
protected async Task HandleDocumentSymbolRequest(
@@ -75,9 +78,7 @@ protected async Task HandleDocumentSymbolRequest(
7578
documentSymbolParams.TextDocument.Uri);
7679

7780
IEnumerable<SymbolReference> foundSymbols =
78-
this.Providers
79-
.SelectMany(
80-
provider => provider.ProvideDocumentSymbols(scriptFile));
81+
this.ProvideDocumentSymbols(scriptFile);
8182

8283
SymbolInformation[] symbols = null;
8384

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
//
2+
// Copyright (c) Microsoft. All rights reserved.
3+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
4+
//
5+
6+
using System;
7+
using System.Collections.Generic;
8+
using System.Diagnostics;
9+
using System.Linq;
10+
using System.Threading.Tasks;
11+
using Microsoft.PowerShell.EditorServices.Utility;
12+
13+
namespace Microsoft.PowerShell.EditorServices.Components
14+
{
15+
public abstract class FeatureComponentBase<TProvider>
16+
where TProvider : IFeatureProvider
17+
{
18+
/// <summary>
19+
/// Gets the collection of IFeatureProviders registered with
20+
/// this feature component.
21+
/// </summary>
22+
public IFeatureProviderCollection<TProvider> Providers { get; private set; }
23+
24+
/// <summary>
25+
/// Gets the ILogger implementation to use for writing log
26+
/// messages.
27+
/// </summary>
28+
protected ILogger Logger { get; private set; }
29+
30+
/// <summary>
31+
/// Creates an instance of the FeatureComponentBase class with
32+
/// the specified ILoggger.
33+
/// </summary>
34+
/// <param name="logger">The ILogger to use for this instance.</param>
35+
public FeatureComponentBase(ILogger logger)
36+
{
37+
this.Providers = new FeatureProviderCollection<TProvider>();
38+
this.Logger = logger;
39+
}
40+
41+
protected IEnumerable<TResult> InvokeProviders<TResult>(
42+
Func<TProvider, TResult> invokeFunc)
43+
{
44+
Stopwatch invokeTimer = new Stopwatch();
45+
List<TResult> providerResults = new List<TResult>();
46+
47+
foreach (var provider in this.Providers)
48+
{
49+
try
50+
{
51+
invokeTimer.Restart();
52+
53+
providerResults.Add(invokeFunc(provider));
54+
55+
invokeTimer.Stop();
56+
57+
this.Logger.Write(
58+
LogLevel.Verbose,
59+
$"Invocation of provider '{provider.ProviderId}' completed in {invokeTimer.ElapsedMilliseconds}ms.");
60+
}
61+
catch (Exception e)
62+
{
63+
this.Logger.WriteException(
64+
$"Exception caught while invoking provider {provider.ProviderId}:",
65+
e);
66+
}
67+
}
68+
69+
return providerResults;
70+
}
71+
72+
protected async Task<IEnumerable<TResult>> InvokeProvidersAsync<TResult>(
73+
Func<TProvider, Task<TResult>> invokeFunc)
74+
{
75+
Stopwatch invokeTimer = new Stopwatch();
76+
List<TResult> providerResults = new List<TResult>();
77+
78+
foreach (var provider in this.Providers)
79+
{
80+
try
81+
{
82+
invokeTimer.Restart();
83+
84+
providerResults.Add(
85+
await invokeFunc(provider));
86+
87+
invokeTimer.Stop();
88+
89+
this.Logger.Write(
90+
LogLevel.Verbose,
91+
$"Invocation of provider '{provider.ProviderId}' completed in {invokeTimer.ElapsedMilliseconds}ms.");
92+
}
93+
catch (Exception e)
94+
{
95+
this.Logger.WriteException(
96+
$"Exception caught while invoking provider {provider.ProviderId}:",
97+
e);
98+
}
99+
}
100+
101+
return providerResults;
102+
}
103+
}
104+
}

0 commit comments

Comments
 (0)