From a2f6792ed0813c6ef76bc42f2a10226da8fef4af Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Thu, 18 Apr 2019 09:56:33 -0700 Subject: [PATCH 1/9] Fix null reference when module is null in hosting scenarios --- .../commands/utility/ConvertFromMarkdownCommand.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFromMarkdownCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFromMarkdownCommand.cs index 6a1daa24737..52c04b98320 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFromMarkdownCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFromMarkdownCommand.cs @@ -66,12 +66,8 @@ public class ConvertFromMarkdownCommand : PSCmdlet /// protected override void BeginProcessing() { - _mdOption = this.CommandInfo.Module.SessionState.PSVariable.GetValue("PSMarkdownOptionInfo", new PSMarkdownOptionInfo()) as PSMarkdownOptionInfo; - - if (_mdOption == null) - { - throw new InvalidOperationException(); - } + _mdOption = this.CommandInfo.Module?.SessionState.PSVariable.GetValue("PSMarkdownOptionInfo", new PSMarkdownOptionInfo()) as PSMarkdownOptionInfo; + _mdOption = _mdOption ?? new PSMarkdownOptionInfo(); bool? supportsVT100 = this.Host?.UI.SupportsVirtualTerminal; From a96b4b4b6fd9aa386e2ecb0acc606258c43c297b Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Thu, 18 Apr 2019 10:06:48 -0700 Subject: [PATCH 2/9] Add test for hosting scenario --- .../MarkdownCmdlets.Tests.ps1 | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/MarkdownCmdlets.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/MarkdownCmdlets.Tests.ps1 index bbaf412d3fb..e76cdcf5ea8 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/MarkdownCmdlets.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/MarkdownCmdlets.Tests.ps1 @@ -546,4 +546,25 @@ bool function()`n{`n} } } } + + Context "Hosted PowerShell scenario" { + It 'ConvertFrom-Markdown gets expected out when run in hosted powershell' { + $pool = [runspacefactory]::CreateRunspacePool(1, 2, $Host) + $pool.Open() + + $ps = [powershell]::Create() + $ps.RunspacePool = $pool + $ps.AddScript( { + try { + $output = '# test' | ConvertFrom-Markdown + $output.Html | Should -BeExactly '

test

' + } catch { + $_ | Out-Default + } + }) | Out-Null + + $ps.Invoke() + $ps.Dispose() + } + } } From a7b4297b3cc4d2cb02400f709b65ddd910e8b072 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Thu, 18 Apr 2019 12:29:04 -0700 Subject: [PATCH 3/9] Address Ilya's comments --- .../MarkdownCmdlets.Tests.ps1 | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/MarkdownCmdlets.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/MarkdownCmdlets.Tests.ps1 index e76cdcf5ea8..68ac29ce07c 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/MarkdownCmdlets.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/MarkdownCmdlets.Tests.ps1 @@ -549,22 +549,26 @@ bool function()`n{`n} Context "Hosted PowerShell scenario" { It 'ConvertFrom-Markdown gets expected out when run in hosted powershell' { - $pool = [runspacefactory]::CreateRunspacePool(1, 2, $Host) - $pool.Open() - - $ps = [powershell]::Create() - $ps.RunspacePool = $pool - $ps.AddScript( { - try { - $output = '# test' | ConvertFrom-Markdown - $output.Html | Should -BeExactly '

test

' - } catch { - $_ | Out-Default - } - }) | Out-Null - $ps.Invoke() - $ps.Dispose() + try { + $pool = [runspacefactory]::CreateRunspacePool(1, 2, $Host) + $pool.Open() + + $ps = [powershell]::Create() + $ps.RunspacePool = $pool + $ps.AddScript( { + try { + $output = '# test' | ConvertFrom-Markdown + $output.Html.trim() | Should -BeExactly '

test

' + } catch { + throw $_ + } + }) + + $ps.Invoke() + } finally { + $ps.Dispose() + } } } } From b5be0db75ee2a4293c16f573c15b6fb67cb102b9 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Tue, 23 Apr 2019 15:18:41 -0700 Subject: [PATCH 4/9] Update the cmdlets to store the options per runspace * Instead of using the module scope variable, we now use a dictionary with the runspace as the key to store the options. * Update tests accordingly. --- .../utility/ConvertFromMarkdownCommand.cs | 4 +- .../utility/MarkdownOptionCommands.cs | 41 ++++++- .../MarkdownCmdlets.Tests.ps1 | 105 ++++++++++++------ 3 files changed, 112 insertions(+), 38 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFromMarkdownCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFromMarkdownCommand.cs index 52c04b98320..7989b025530 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFromMarkdownCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFromMarkdownCommand.cs @@ -66,8 +66,8 @@ public class ConvertFromMarkdownCommand : PSCmdlet /// protected override void BeginProcessing() { - _mdOption = this.CommandInfo.Module?.SessionState.PSVariable.GetValue("PSMarkdownOptionInfo", new PSMarkdownOptionInfo()) as PSMarkdownOptionInfo; - _mdOption = _mdOption ?? new PSMarkdownOptionInfo(); + string currentRunspaceId = this.CommandInfo.Context.CurrentRunspace.InstanceId.ToString(); + _mdOption = PSMarkdownOptionInfoCache.Get(currentRunspaceId); bool? supportsVT100 = this.Host?.UI.SupportsVirtualTerminal; diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MarkdownOptionCommands.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MarkdownOptionCommands.cs index 53238ecfa06..e4c7c861fbb 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MarkdownOptionCommands.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MarkdownOptionCommands.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; @@ -173,11 +174,14 @@ protected override void EndProcessing() break; } - this.CommandInfo.Module.SessionState.PSVariable.Set(MarkdownOptionInfoVariableName, mdOptionInfo); + string currentRunspaceId = this.CommandInfo.Context.CurrentRunspace.InstanceId.ToString(); + + // If the option is already set once for this runspace, then update it or set it. + var setOption = PSMarkdownOptionInfoCache.Set(currentRunspaceId, mdOptionInfo); if (PassThru.IsPresent) { - WriteObject(mdOptionInfo); + WriteObject(setOption); } } @@ -256,7 +260,38 @@ public class GetMarkdownOptionCommand : PSCmdlet /// protected override void EndProcessing() { - WriteObject(this.CommandInfo.Module.SessionState.PSVariable.GetValue(MarkdownOptionInfoVariableName, new PSMarkdownOptionInfo())); + string currentRunspaceId = this.CommandInfo.Context.CurrentRunspace.InstanceId.ToString(); + WriteObject(PSMarkdownOptionInfoCache.Get(currentRunspaceId)); + } + } + + internal static class PSMarkdownOptionInfoCache + { + private static ConcurrentDictionary markdownOptionInfoCache; + + static PSMarkdownOptionInfoCache() + { + markdownOptionInfoCache = new ConcurrentDictionary(); + } + + internal static PSMarkdownOptionInfo Get(string runspaceId) + { + if (markdownOptionInfoCache.TryGetValue(runspaceId, out PSMarkdownOptionInfo cachedOption)) + { + // return the cached options for the runspaceId + return cachedOption; + } + else + { + // no option cache so cache and return the default PSMarkdownOptionInfo + var newOptionInfo = new PSMarkdownOptionInfo(); + return markdownOptionInfoCache.GetOrAdd(runspaceId, (key) => newOptionInfo); + } + } + + internal static PSMarkdownOptionInfo Set(string runspaceId, PSMarkdownOptionInfo optionInfo) + { + return markdownOptionInfoCache.AddOrUpdate(runspaceId, optionInfo, (key, oldvalue) => optionInfo); } } } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/MarkdownCmdlets.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/MarkdownCmdlets.Tests.ps1 index 68ac29ce07c..55f5e5743b1 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/MarkdownCmdlets.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/MarkdownCmdlets.Tests.ps1 @@ -429,36 +429,6 @@ bool function()`n{`n} } - $options.Link | Should -BeExactly "[4;38;5;117m" - $options.Image | Should -BeExactly "[33m" - $options.EmphasisBold | Should -BeExactly "[1m" - $options.EmphasisItalics | Should -BeExactly "[36m" - } - - It "Verify PSMarkdownOptionInfo is defined in module scope" { - - $PSMarkdownOptionInfo | Should -BeNullOrEmpty - - $mod = Get-Module Microsoft.PowerShell.Utility - $options = & $mod { $PSMarkdownOptionInfo } - - $options.Header1 | Should -BeExactly "[7m" - $options.Header2 | Should -BeExactly "[4;93m" - $options.Header3 | Should -BeExactly "[4;94m" - $options.Header4 | Should -BeExactly "[4;95m" - $options.Header5 | Should -BeExactly "[4;96m" - $options.Header6 | Should -BeExactly "[4;97m" - - if($IsMacOS) - { - $options.Code | Should -BeExactly "[107;95m" - } - else - { - $options.Code | Should -BeExactly "[48;2;155;155;155;38;2;30;30;30m" - } - - $options.Link | Should -BeExactly "[4;38;5;117m" $options.Image | Should -BeExactly "[33m" $options.EmphasisBold | Should -BeExactly "[1m" @@ -548,7 +518,8 @@ bool function()`n{`n} } Context "Hosted PowerShell scenario" { - It 'ConvertFrom-Markdown gets expected out when run in hosted powershell' { + + It 'ConvertFrom-Markdown gets expected output when run in hosted powershell' { try { $pool = [runspacefactory]::CreateRunspacePool(1, 2, $Host) @@ -559,16 +530,84 @@ bool function()`n{`n} $ps.AddScript( { try { $output = '# test' | ConvertFrom-Markdown - $output.Html.trim() | Should -BeExactly '

test

' + $output.Html.trim() } catch { throw $_ } }) - $ps.Invoke() + $output = $ps.Invoke() + + $output | Should -BeExactly '

test

' } finally { $ps.Dispose() } } + + It 'Get-MarkdownOption gets default values when run in hosted powershell' { + + try { + $ps = [powershell]::Create() + $ps.AddScript( { + Get-MarkdownOption -ErrorAction Stop + }) + + $options = $ps.Invoke() + + $options.Header1 | Should -BeExactly "[7m" + $options.Header2 | Should -BeExactly "[4;93m" + $options.Header3 | Should -BeExactly "[4;94m" + $options.Header4 | Should -BeExactly "[4;95m" + $options.Header5 | Should -BeExactly "[4;96m" + $options.Header6 | Should -BeExactly "[4;97m" + + if ($IsMacOS) { + $options.Code | Should -BeExactly "[107;95m" + } else { + $options.Code | Should -BeExactly "[48;2;155;155;155;38;2;30;30;30m" + } + + $options.Link | Should -BeExactly "[4;38;5;117m" + $options.Image | Should -BeExactly "[33m" + $options.EmphasisBold | Should -BeExactly "[1m" + $options.EmphasisItalics | Should -BeExactly "[36m" + } + finally { + $ps.Dispose() + } + } + + It 'Set-MarkdownOption sets values when run in hosted powershell' { + + try { + $ps = [powershell]::Create() + $ps.AddScript( { + Set-MarkdownOption -Header1Color '[93m' -ErrorAction Stop -PassThru + }) + + $options = $ps.Invoke() + + $options.Header1 | Should -BeExactly "[93m" + $options.Header2 | Should -BeExactly "[4;93m" + $options.Header3 | Should -BeExactly "[4;94m" + $options.Header4 | Should -BeExactly "[4;95m" + $options.Header5 | Should -BeExactly "[4;96m" + $options.Header6 | Should -BeExactly "[4;97m" + + if ($IsMacOS) { + $options.Code | Should -BeExactly "[107;95m" + } else { + $options.Code | Should -BeExactly "[48;2;155;155;155;38;2;30;30;30m" + } + + $options.Link | Should -BeExactly "[4;38;5;117m" + $options.Image | Should -BeExactly "[33m" + $options.EmphasisBold | Should -BeExactly "[1m" + $options.EmphasisItalics | Should -BeExactly "[36m" + } + finally { + $ps.Dispose() + } + } } } From c5a55f64f9833e2cb0ba39cb7940e5a9b9000b68 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Thu, 25 Apr 2019 14:06:23 -0700 Subject: [PATCH 5/9] Address Ilya's comments --- .../commands/utility/ConvertFromMarkdownCommand.cs | 2 +- .../commands/utility/MarkdownOptionCommands.cs | 12 ++++++------ .../MarkdownCmdlets.Tests.ps1 | 12 +++++------- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFromMarkdownCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFromMarkdownCommand.cs index 7989b025530..48d6ddb1689 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFromMarkdownCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFromMarkdownCommand.cs @@ -66,7 +66,7 @@ public class ConvertFromMarkdownCommand : PSCmdlet /// protected override void BeginProcessing() { - string currentRunspaceId = this.CommandInfo.Context.CurrentRunspace.InstanceId.ToString(); + Guid currentRunspaceId = this.CommandInfo.Context.CurrentRunspace.InstanceId; _mdOption = PSMarkdownOptionInfoCache.Get(currentRunspaceId); bool? supportsVT100 = this.Host?.UI.SupportsVirtualTerminal; diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MarkdownOptionCommands.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MarkdownOptionCommands.cs index e4c7c861fbb..8432d4b39f0 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MarkdownOptionCommands.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MarkdownOptionCommands.cs @@ -174,7 +174,7 @@ protected override void EndProcessing() break; } - string currentRunspaceId = this.CommandInfo.Context.CurrentRunspace.InstanceId.ToString(); + Guid currentRunspaceId = this.CommandInfo.Context.CurrentRunspace.InstanceId; // If the option is already set once for this runspace, then update it or set it. var setOption = PSMarkdownOptionInfoCache.Set(currentRunspaceId, mdOptionInfo); @@ -260,21 +260,21 @@ public class GetMarkdownOptionCommand : PSCmdlet /// protected override void EndProcessing() { - string currentRunspaceId = this.CommandInfo.Context.CurrentRunspace.InstanceId.ToString(); + Guid currentRunspaceId = this.CommandInfo.Context.CurrentRunspace.InstanceId; WriteObject(PSMarkdownOptionInfoCache.Get(currentRunspaceId)); } } internal static class PSMarkdownOptionInfoCache { - private static ConcurrentDictionary markdownOptionInfoCache; + private static ConcurrentDictionary markdownOptionInfoCache; static PSMarkdownOptionInfoCache() { - markdownOptionInfoCache = new ConcurrentDictionary(); + markdownOptionInfoCache = new ConcurrentDictionary(); } - internal static PSMarkdownOptionInfo Get(string runspaceId) + internal static PSMarkdownOptionInfo Get(Guid runspaceId) { if (markdownOptionInfoCache.TryGetValue(runspaceId, out PSMarkdownOptionInfo cachedOption)) { @@ -289,7 +289,7 @@ internal static PSMarkdownOptionInfo Get(string runspaceId) } } - internal static PSMarkdownOptionInfo Set(string runspaceId, PSMarkdownOptionInfo optionInfo) + internal static PSMarkdownOptionInfo Set(Guid runspaceId, PSMarkdownOptionInfo optionInfo) { return markdownOptionInfoCache.AddOrUpdate(runspaceId, optionInfo, (key, oldvalue) => optionInfo); } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/MarkdownCmdlets.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/MarkdownCmdlets.Tests.ps1 index 55f5e5743b1..431bc492961 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/MarkdownCmdlets.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/MarkdownCmdlets.Tests.ps1 @@ -527,13 +527,9 @@ bool function()`n{`n} $ps = [powershell]::Create() $ps.RunspacePool = $pool - $ps.AddScript( { - try { - $output = '# test' | ConvertFrom-Markdown - $output.Html.trim() - } catch { - throw $_ - } + $ps.AddScript({ + $output = '# test' | ConvertFrom-Markdown + $output.Html.trim() }) $output = $ps.Invoke() @@ -554,6 +550,7 @@ bool function()`n{`n} $options = $ps.Invoke() + $options | Should -Not -BeNullOrEmpty $options.Header1 | Should -BeExactly "[7m" $options.Header2 | Should -BeExactly "[4;93m" $options.Header3 | Should -BeExactly "[4;94m" @@ -587,6 +584,7 @@ bool function()`n{`n} $options = $ps.Invoke() + $options | Should -Not -BeNullOrEmpty $options.Header1 | Should -BeExactly "[93m" $options.Header2 | Should -BeExactly "[4;93m" $options.Header3 | Should -BeExactly "[4;94m" From afbd87ad5822879e32cc63ab2117cf8b7882adcd Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Thu, 25 Apr 2019 16:29:52 -0700 Subject: [PATCH 6/9] Use module scope variable when moduleInfo is available When moduleInfo is available we use the module scope variable. But, when it is not available, we use a concurrent dictionary with runspace instance ID as the key to store the options. --- .../utility/ConvertFromMarkdownCommand.cs | 13 ++++++++-- .../utility/MarkdownOptionCommands.cs | 25 +++++++++++++++---- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFromMarkdownCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFromMarkdownCommand.cs index 48d6ddb1689..a7827b865f4 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFromMarkdownCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFromMarkdownCommand.cs @@ -66,8 +66,17 @@ public class ConvertFromMarkdownCommand : PSCmdlet /// protected override void BeginProcessing() { - Guid currentRunspaceId = this.CommandInfo.Context.CurrentRunspace.InstanceId; - _mdOption = PSMarkdownOptionInfoCache.Get(currentRunspaceId); + // If we have the moduleInfo then store are module scope variable + if (this.CommandInfo.Module != null) + { + _mdOption = this.CommandInfo.Module?.SessionState.PSVariable.GetValue("PSMarkdownOptionInfo", new PSMarkdownOptionInfo()) as PSMarkdownOptionInfo; + _mdOption = _mdOption ?? new PSMarkdownOptionInfo(); + } + else // If we don't have a moduleInfo, like in PowerShell hosting scenarios, use a concurrent dictionary. + { + Guid currentRunspaceId = this.CommandInfo.Context.CurrentRunspace.InstanceId; + _mdOption = PSMarkdownOptionInfoCache.Get(currentRunspaceId); + } bool? supportsVT100 = this.Host?.UI.SupportsVirtualTerminal; diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MarkdownOptionCommands.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MarkdownOptionCommands.cs index 8432d4b39f0..88b737fa48a 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MarkdownOptionCommands.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MarkdownOptionCommands.cs @@ -8,6 +8,7 @@ using System.IO; using System.Management.Automation; using System.Management.Automation.Internal; +using System.Management.Automation.Runspaces; using System.Threading.Tasks; using Microsoft.PowerShell.MarkdownRender; @@ -174,10 +175,19 @@ protected override void EndProcessing() break; } - Guid currentRunspaceId = this.CommandInfo.Context.CurrentRunspace.InstanceId; + PSMarkdownOptionInfo setOption = null; - // If the option is already set once for this runspace, then update it or set it. - var setOption = PSMarkdownOptionInfoCache.Set(currentRunspaceId, mdOptionInfo); + // If we have the moduleInfo then store are module scope variable + if (this.CommandInfo.Module != null) + { + this.CommandInfo.Module.SessionState.PSVariable.Set(MarkdownOptionInfoVariableName, mdOptionInfo); + setOption = mdOptionInfo; + } + else // If we don't have a moduleInfo, like in PowerShell hosting scenarios, use a concurrent dictionary. + { + // If the option is already set once for this runspace, then update it or set it. + setOption = PSMarkdownOptionInfoCache.Set(Runspace.DefaultRunspace.InstanceId, mdOptionInfo); + } if (PassThru.IsPresent) { @@ -260,8 +270,13 @@ public class GetMarkdownOptionCommand : PSCmdlet /// protected override void EndProcessing() { - Guid currentRunspaceId = this.CommandInfo.Context.CurrentRunspace.InstanceId; - WriteObject(PSMarkdownOptionInfoCache.Get(currentRunspaceId)); + var option = this.CommandInfo.Module == null + // If we don't have a moduleInfo, like in PowerShell hosting scenarios, use a concurrent dictionary. + ? PSMarkdownOptionInfoCache.Get(Runspace.DefaultRunspace.InstanceId) + // If we have the moduleInfo then store are module scope variable + : this.CommandInfo.Module.SessionState.PSVariable.GetValue(MarkdownOptionInfoVariableName, new PSMarkdownOptionInfo()); + + WriteObject(option); } } From 24ad16266a3267a24e4f1a6f430a74de982a76e5 Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Fri, 26 Apr 2019 15:50:25 -0700 Subject: [PATCH 7/9] Move get set code for options to internal class --- .../utility/ConvertFromMarkdownCommand.cs | 12 +---- .../utility/MarkdownOptionCommands.cs | 49 +++++++++---------- 2 files changed, 24 insertions(+), 37 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFromMarkdownCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFromMarkdownCommand.cs index a7827b865f4..5df1b594391 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFromMarkdownCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFromMarkdownCommand.cs @@ -66,17 +66,7 @@ public class ConvertFromMarkdownCommand : PSCmdlet /// protected override void BeginProcessing() { - // If we have the moduleInfo then store are module scope variable - if (this.CommandInfo.Module != null) - { - _mdOption = this.CommandInfo.Module?.SessionState.PSVariable.GetValue("PSMarkdownOptionInfo", new PSMarkdownOptionInfo()) as PSMarkdownOptionInfo; - _mdOption = _mdOption ?? new PSMarkdownOptionInfo(); - } - else // If we don't have a moduleInfo, like in PowerShell hosting scenarios, use a concurrent dictionary. - { - Guid currentRunspaceId = this.CommandInfo.Context.CurrentRunspace.InstanceId; - _mdOption = PSMarkdownOptionInfoCache.Get(currentRunspaceId); - } + _mdOption = PSMarkdownOptionInfoCache.Get(this.CommandInfo); bool? supportsVT100 = this.Host?.UI.SupportsVirtualTerminal; diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MarkdownOptionCommands.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MarkdownOptionCommands.cs index 88b737fa48a..1c8618a3218 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MarkdownOptionCommands.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MarkdownOptionCommands.cs @@ -126,7 +126,6 @@ public class SetMarkdownOptionCommand : PSCmdlet private const string IndividualSetting = "IndividualSetting"; private const string InputObjectParamSet = "InputObject"; private const string ThemeParamSet = "Theme"; - private const string MarkdownOptionInfoVariableName = "PSMarkdownOptionInfo"; private const string LightThemeName = "Light"; private const string DarkThemeName = "Dark"; @@ -175,19 +174,7 @@ protected override void EndProcessing() break; } - PSMarkdownOptionInfo setOption = null; - - // If we have the moduleInfo then store are module scope variable - if (this.CommandInfo.Module != null) - { - this.CommandInfo.Module.SessionState.PSVariable.Set(MarkdownOptionInfoVariableName, mdOptionInfo); - setOption = mdOptionInfo; - } - else // If we don't have a moduleInfo, like in PowerShell hosting scenarios, use a concurrent dictionary. - { - // If the option is already set once for this runspace, then update it or set it. - setOption = PSMarkdownOptionInfoCache.Set(Runspace.DefaultRunspace.InstanceId, mdOptionInfo); - } + var setOption = PSMarkdownOptionInfoCache.Set(this.CommandInfo, mdOptionInfo); if (PassThru.IsPresent) { @@ -270,28 +257,30 @@ public class GetMarkdownOptionCommand : PSCmdlet /// protected override void EndProcessing() { - var option = this.CommandInfo.Module == null - // If we don't have a moduleInfo, like in PowerShell hosting scenarios, use a concurrent dictionary. - ? PSMarkdownOptionInfoCache.Get(Runspace.DefaultRunspace.InstanceId) - // If we have the moduleInfo then store are module scope variable - : this.CommandInfo.Module.SessionState.PSVariable.GetValue(MarkdownOptionInfoVariableName, new PSMarkdownOptionInfo()); - - WriteObject(option); + WriteObject(PSMarkdownOptionInfoCache.Get(this.CommandInfo)); } } internal static class PSMarkdownOptionInfoCache { private static ConcurrentDictionary markdownOptionInfoCache; + private const string MarkdownOptionInfoVariableName = "PSMarkdownOptionInfo"; static PSMarkdownOptionInfoCache() { markdownOptionInfoCache = new ConcurrentDictionary(); } - internal static PSMarkdownOptionInfo Get(Guid runspaceId) + internal static PSMarkdownOptionInfo Get(CommandInfo command) { - if (markdownOptionInfoCache.TryGetValue(runspaceId, out PSMarkdownOptionInfo cachedOption)) + // If we have the moduleInfo then store are module scope variable + if (command.Module != null) + { + return command.Module.SessionState.PSVariable.GetValue(MarkdownOptionInfoVariableName, new PSMarkdownOptionInfo()) as PSMarkdownOptionInfo; + } + + // If we don't have a moduleInfo, like in PowerShell hosting scenarios, use a concurrent dictionary. + if (markdownOptionInfoCache.TryGetValue(Runspace.DefaultRunspace.InstanceId, out PSMarkdownOptionInfo cachedOption)) { // return the cached options for the runspaceId return cachedOption; @@ -300,13 +289,21 @@ internal static PSMarkdownOptionInfo Get(Guid runspaceId) { // no option cache so cache and return the default PSMarkdownOptionInfo var newOptionInfo = new PSMarkdownOptionInfo(); - return markdownOptionInfoCache.GetOrAdd(runspaceId, (key) => newOptionInfo); + return markdownOptionInfoCache.GetOrAdd(Runspace.DefaultRunspace.InstanceId, (key) => newOptionInfo); } } - internal static PSMarkdownOptionInfo Set(Guid runspaceId, PSMarkdownOptionInfo optionInfo) + internal static PSMarkdownOptionInfo Set(CommandInfo command, PSMarkdownOptionInfo optionInfo) { - return markdownOptionInfoCache.AddOrUpdate(runspaceId, optionInfo, (key, oldvalue) => optionInfo); + // If we have the moduleInfo then store are module scope variable + if (command.Module != null) + { + command.Module.SessionState.PSVariable.Set(MarkdownOptionInfoVariableName, optionInfo); + return optionInfo; + } + + // If we don't have a moduleInfo, like in PowerShell hosting scenarios, use a concurrent dictionary. + return markdownOptionInfoCache.AddOrUpdate(Runspace.DefaultRunspace.InstanceId, optionInfo, (key, oldvalue) => optionInfo); } } } From d8302fde68ca52639a2fc3fff7814515a945045f Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Fri, 26 Apr 2019 16:01:09 -0700 Subject: [PATCH 8/9] Update comments to give more detail --- .../commands/utility/MarkdownOptionCommands.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MarkdownOptionCommands.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MarkdownOptionCommands.cs index 1c8618a3218..e852dc7c534 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MarkdownOptionCommands.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MarkdownOptionCommands.cs @@ -261,6 +261,14 @@ protected override void EndProcessing() } } + /// + /// The class manages whether we should use a module scope variable or concurrent dictionary for storing the set PSMarkdownOptions. + /// When we have a moduleInfo available we use the module scope variable. + /// In case of built-in modules, they are loaded as snapins when we are hosting PowerShell. + /// We use runspace Id as the key for the concurrent dictionary to have the functionality of separate settings per runspace. + /// Force loading the module does not unload the nested modules and hence we cannot use IModuleAssemblyCleanup to remove items from the dictionary. + /// Because of these reason, we continue using module scope variable when moduleInfo is available. + /// internal static class PSMarkdownOptionInfoCache { private static ConcurrentDictionary markdownOptionInfoCache; @@ -302,7 +310,7 @@ internal static PSMarkdownOptionInfo Set(CommandInfo command, PSMarkdownOptionIn return optionInfo; } - // If we don't have a moduleInfo, like in PowerShell hosting scenarios, use a concurrent dictionary. + // If we don't have a moduleInfo, like in PowerShell hosting scenarios with modules loaded as snapins, use a concurrent dictionary. return markdownOptionInfoCache.AddOrUpdate(Runspace.DefaultRunspace.InstanceId, optionInfo, (key, oldvalue) => optionInfo); } } From e53d5a19ba51634f18c7c31bb137f8d6865ab69f Mon Sep 17 00:00:00 2001 From: Aditya Patwardhan Date: Fri, 3 May 2019 13:41:30 -0700 Subject: [PATCH 9/9] Address Dongbo's comments --- .../commands/utility/MarkdownOptionCommands.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MarkdownOptionCommands.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MarkdownOptionCommands.cs index e852dc7c534..61953205445 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MarkdownOptionCommands.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MarkdownOptionCommands.cs @@ -297,7 +297,7 @@ internal static PSMarkdownOptionInfo Get(CommandInfo command) { // no option cache so cache and return the default PSMarkdownOptionInfo var newOptionInfo = new PSMarkdownOptionInfo(); - return markdownOptionInfoCache.GetOrAdd(Runspace.DefaultRunspace.InstanceId, (key) => newOptionInfo); + return markdownOptionInfoCache.GetOrAdd(Runspace.DefaultRunspace.InstanceId, newOptionInfo); } }