Skip to content

Commit 91eafaf

Browse files
JamesWTruherbergmeister
authored andcommitted
Change CommandInfoCache to implement IDisposable and clean up the runspace pool (#1335)
* Change helper to implement IDisposable and clean up the runspace pool The Helper is recreated with every invocation of Invoke-ScriptAnalyzer which essentially resulted in a runspace which was not cleaned up for every invocation. I saw 100s of runspaces (ia get-runspace) in my session. I also gave the CommandInfoCache it's own runspace pool and implemented IDisposable in it. Lastly, I've added a test to catch this in the future. * Remove runspace from helper It no longer needs to be IDisposable Implement dispose in cache along suggested guidelines * Update Tests/Engine/Helper.tests.ps1 Co-Authored-By: Christoph Bergmeister [MVP] <[email protected]> * Update Tests/Engine/Helper.tests.ps1 Co-Authored-By: Christoph Bergmeister [MVP] <[email protected]> * Update Tests/Engine/Helper.tests.ps1 Co-Authored-By: Christoph Bergmeister [MVP] <[email protected]> * update test language * Update Tests/Engine/Helper.tests.ps1 Co-Authored-By: Christoph Bergmeister [MVP] <[email protected]> * Fix version comparison to just use major number
1 parent b9fd7b3 commit 91eafaf

File tree

3 files changed

+35
-10
lines changed

3 files changed

+35
-10
lines changed

Engine/CommandInfoCache.cs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,44 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer
1212
/// <summary>
1313
/// Provides threadsafe caching around CommandInfo lookups with `Get-Command -Name ...`.
1414
/// </summary>
15-
internal class CommandInfoCache
15+
internal class CommandInfoCache : IDisposable
1616
{
1717
private readonly ConcurrentDictionary<CommandLookupKey, Lazy<CommandInfo>> _commandInfoCache;
1818
private readonly Helper _helperInstance;
1919
private readonly RunspacePool _runspacePool;
20+
private bool disposed = false;
2021

2122
/// <summary>
2223
/// Create a fresh command info cache instance.
2324
/// </summary>
24-
public CommandInfoCache(Helper pssaHelperInstance, RunspacePool runspacePool)
25+
public CommandInfoCache(Helper pssaHelperInstance)
2526
{
2627
_commandInfoCache = new ConcurrentDictionary<CommandLookupKey, Lazy<CommandInfo>>();
2728
_helperInstance = pssaHelperInstance;
28-
_runspacePool = runspacePool;
29+
_runspacePool = RunspaceFactory.CreateRunspacePool(1, 10);
30+
_runspacePool.Open();
31+
}
32+
33+
/// <summary>Dispose the runspace pool</summary>
34+
public void Dispose()
35+
{
36+
Dispose(true);
37+
GC.SuppressFinalize(this);
38+
}
39+
40+
protected virtual void Dispose(bool disposing)
41+
{
42+
if ( disposed )
43+
{
44+
return;
45+
}
46+
47+
if ( disposing )
48+
{
49+
_runspacePool.Dispose();
50+
}
51+
52+
disposed = true;
2953
}
3054

3155
/// <summary>

Engine/Helper.cs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ public class Helper
3030
private PSVersionTable psVersionTable;
3131

3232
private readonly Lazy<CommandInfoCache> _commandInfoCacheLazy;
33-
private readonly RunspacePool _runSpacePool;
3433
private readonly object _testModuleManifestLock = new object();
3534

3635
#endregion
@@ -116,11 +115,7 @@ internal set
116115
/// </summary>
117116
private Helper()
118117
{
119-
// There are 5 rules that use the CommandInfo cache but one rule (AvoidAlias) makes parallel queries.
120-
// Therefore 10 runspaces was a heuristic measure where no more speed improvement was seen.
121-
_runSpacePool = RunspaceFactory.CreateRunspacePool(1, 10);
122-
_runSpacePool.Open();
123-
_commandInfoCacheLazy = new Lazy<CommandInfoCache>(() => new CommandInfoCache(pssaHelperInstance: this, runspacePool: _runSpacePool));
118+
_commandInfoCacheLazy = new Lazy<CommandInfoCache>(() => new CommandInfoCache(pssaHelperInstance: this));
124119
}
125120

126121
/// <summary>
@@ -309,7 +304,6 @@ public PSModuleInfo GetModuleManifest(string filePath, out IEnumerable<ErrorReco
309304
{
310305
using (var ps = System.Management.Automation.PowerShell.Create())
311306
{
312-
ps.RunspacePool = _runSpacePool;
313307
ps.AddCommand("Test-ModuleManifest")
314308
.AddParameter("Path", filePath)
315309
.AddParameter("WarningAction", ActionPreference.SilentlyContinue);

Tests/Engine/Helper.tests.ps1

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,11 @@ Describe "Test Directed Graph" {
2626
$digraph.IsConnected('v1', 'v4') | Should -BeTrue
2727
}
2828
}
29+
30+
Context "Runspaces should be disposed" {
31+
It "Running analyzer 100 times should only create a limited number of runspaces" -Skip:$($PSVersionTable.PSVersion.Major -le 4) {
32+
$null = 1..100 | ForEach-Object { Invoke-ScriptAnalyzer -ScriptDefinition 'gci' }
33+
(Get-Runspace).Count | Should -BeLessOrEqual 10 -Because 'Number of Runspaces should not exceed size of runspace pool cache in CommandInfoCache'
34+
}
35+
}
2936
}

0 commit comments

Comments
 (0)