Skip to content

Commit 19c4f49

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 19c4f49

File tree

3 files changed

+144
-16
lines changed

3 files changed

+144
-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,127 @@
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+
/// <summary>
16+
/// Provides common functionality needed to implement a feature
17+
/// component which uses IFeatureProviders to provide further
18+
/// extensibility.
19+
/// </summary>
20+
public abstract class FeatureComponentBase<TProvider>
21+
where TProvider : IFeatureProvider
22+
{
23+
/// <summary>
24+
/// Gets the collection of IFeatureProviders registered with
25+
/// this feature component.
26+
/// </summary>
27+
public IFeatureProviderCollection<TProvider> Providers { get; private set; }
28+
29+
/// <summary>
30+
/// Gets the ILogger implementation to use for writing log
31+
/// messages.
32+
/// </summary>
33+
protected ILogger Logger { get; private set; }
34+
35+
/// <summary>
36+
/// Creates an instance of the FeatureComponentBase class with
37+
/// the specified ILoggger.
38+
/// </summary>
39+
/// <param name="logger">The ILogger to use for this instance.</param>
40+
public FeatureComponentBase(ILogger logger)
41+
{
42+
this.Providers = new FeatureProviderCollection<TProvider>();
43+
this.Logger = logger;
44+
}
45+
46+
/// <summary>
47+
/// Invokes the given function synchronously against all
48+
/// registered providers.
49+
/// </summary>
50+
/// <param name="invokeFunc">The function to be invoked.</param>
51+
/// <returns>
52+
/// An IEnumerable containing the results of all providers
53+
/// that were invoked successfully.
54+
/// </returns>
55+
protected IEnumerable<TResult> InvokeProviders<TResult>(
56+
Func<TProvider, TResult> invokeFunc)
57+
{
58+
Stopwatch invokeTimer = new Stopwatch();
59+
List<TResult> providerResults = new List<TResult>();
60+
61+
foreach (var provider in this.Providers)
62+
{
63+
try
64+
{
65+
invokeTimer.Restart();
66+
67+
providerResults.Add(invokeFunc(provider));
68+
69+
invokeTimer.Stop();
70+
71+
this.Logger.Write(
72+
LogLevel.Verbose,
73+
$"Invocation of provider '{provider.ProviderId}' completed in {invokeTimer.ElapsedMilliseconds}ms.");
74+
}
75+
catch (Exception e)
76+
{
77+
this.Logger.WriteException(
78+
$"Exception caught while invoking provider {provider.ProviderId}:",
79+
e);
80+
}
81+
}
82+
83+
return providerResults;
84+
}
85+
86+
/// <summary>
87+
/// Invokes the given function asynchronously against all
88+
/// registered providers.
89+
/// </summary>
90+
/// <param name="invokeFunc">The function to be invoked.</param>
91+
/// <returns>
92+
/// A Task that, when completed, returns an IEnumerable containing
93+
/// the results of all providers that were invoked successfully.
94+
/// </returns>
95+
protected async Task<IEnumerable<TResult>> InvokeProvidersAsync<TResult>(
96+
Func<TProvider, Task<TResult>> invokeFunc)
97+
{
98+
Stopwatch invokeTimer = new Stopwatch();
99+
List<TResult> providerResults = new List<TResult>();
100+
101+
foreach (var provider in this.Providers)
102+
{
103+
try
104+
{
105+
invokeTimer.Restart();
106+
107+
providerResults.Add(
108+
await invokeFunc(provider));
109+
110+
invokeTimer.Stop();
111+
112+
this.Logger.Write(
113+
LogLevel.Verbose,
114+
$"Invocation of provider '{provider.ProviderId}' completed in {invokeTimer.ElapsedMilliseconds}ms.");
115+
}
116+
catch (Exception e)
117+
{
118+
this.Logger.WriteException(
119+
$"Exception caught while invoking provider {provider.ProviderId}:",
120+
e);
121+
}
122+
}
123+
124+
return providerResults;
125+
}
126+
}
127+
}

0 commit comments

Comments
 (0)