Powershell and stack depth
As a developer, I often develop deployment scripts. In one of the projects I had a task to automate the deployment of the project, which consisted of a few dozen jobs, with the possibility to adjust the composition is deployed to the stand components.
In the first place the work was carried out on the standardization of interfaces and tasks were allocated to the following methods:
Given that such steps became more and more to maintain as such, the scripts became more and more difficult. After examining possible solutions, it was decided to implement each task as a separate object:
As such, the deployment scripts worked a long time and there were no signs of trouble. At one point I faced a problem to deploy on a remote server. In powershell there is a very convenient mechanism for WinRM, which we previously very actively used, and, accordingly, to solve the problem the same.
The solution was unstable. Some of the tasks of the deployment occurred either error or Invoke-Command showed that the remote script was executed correctly, but in fact he was interrupted.
In EventViewer was able to find that process on the remote machine has failed with error 1726, but no intelligible information error could not detect. The run the same job on a remote machine was always successful.
In the course of numerous experiments caught the error The script failed due to call depth overflow which determined the further direction of research.
Since PowerShell v2, the maximum depth of the stack in powershell scripts is 1000 calls, in future versions this value was still significantly raised, and the errors stack overflow never occurred.
Decided to run some tests to determine the depth of the stack when calling locally and using WinRM. For this purpose we have prepared testing instruments.
The first test determined the possible depth of the recursion:
The result is locally the stack depth of more than 3000, remotely — a little more than 150.
150 is quite a large value. To achieve it in the real work of deployment scripts is unreal.
The second test determines the possible depth of recursion when using objects:
The results are a bit worse. Remotely depth of the stack 130-133. But it is also very important.
Further study of the original deployment scripts prompted the idea to see how try-catch blocks:
And here I was expecting a huge surprise. When using "objects" and generate exceptions possible stack depth locally was around 130, and remotely only 5.
But the rejection of the use of "objects", the problem disappeared. The values of the stack depth was at the level of the first test.
In powershell 5 there classes. Did a test with their use:
The special prize is not received. When invoked via WinRM stack depth was only 7 hops. What is so-is not enough for normal operation scripts.
Working with test scripts came up with the idea to implement objects with hash + script block.
The depth of the stack 55 of hops is already quite enough value.
Below brought to one table of test results available stack depth:
the
I hope that this information will be useful not only to me! :)
Article based on information from habrahabr.ru
In the first place the work was carried out on the standardization of interfaces and tasks were allocated to the following methods:
Interface deployment jobs
$Task1_Config = ...;
# check whether it is possible to perform the deployment step.
Task1_CheckRequirements function() {}
# check whether to perform the deployment step.
Task1_CanExecute function($project) {}
# run the deployment step.
Task1_Execute function($project, $context) {}
Given that such steps became more and more to maintain as such, the scripts became more and more difficult. After examining possible solutions, it was decided to implement each task as a separate object:
the Interface `object` for deployment tasks
function Task1()
{
$result = New-Object -Typename PSObject -Property `
@{
"name" = "Task1"
"config" = ...
}
Add-Member -InputObject $result -MemberType ScriptMethod -Name CheckRequirements -Value `
{ }
Add-Member -InputObject $result -MemberType ScriptMethod -Name CanExecute -Value `
{
Param($project)
}
Add-Member -InputObject $result -MemberType ScriptMethod -Name Execute -Value `
{
Param($project, $context)
}
return $result
}
As such, the deployment scripts worked a long time and there were no signs of trouble. At one point I faced a problem to deploy on a remote server. In powershell there is a very convenient mechanism for WinRM, which we previously very actively used, and, accordingly, to solve the problem the same.
The solution was unstable. Some of the tasks of the deployment occurred either error or Invoke-Command showed that the remote script was executed correctly, but in fact he was interrupted.
failed to process data of a remote team. Error message: host process the WSMan provider did not return the correct answer. Provider in the host process may behave incorrectly
Processing data for a remote command failed with the following error message: The WSMan provider host process did not return a proper response. A provider in the host process may have behaved improperly.
In EventViewer was able to find that process on the remote machine has failed with error 1726, but no intelligible information error could not detect. The run the same job on a remote machine was always successful.
In the course of numerous experiments caught the error The script failed due to call depth overflow which determined the further direction of research.
Since PowerShell v2, the maximum depth of the stack in powershell scripts is 1000 calls, in future versions this value was still significantly raised, and the errors stack overflow never occurred.
Decided to run some tests to determine the depth of the stack when calling locally and using WinRM. For this purpose we have prepared testing instruments.
testing Tools
$ErrorActionPreference = "Stop"
$cred = New-Object System.Management.Automation.PsCredential(...)
runLocal function($sb, $cnt)
{
Write-Host "Local $cnt"
Invoke-Command -ScriptBlock $sb-ArgumentList @($cnt)
}
function runRemote($sb, $cnt)
{
Write-Host "Remote $cnt"
$s = New-PSSession "." -credential $cred
try
{
Invoke-Command -Session $s -ScriptBlock $sb-ArgumentList @($cnt)
}
finally
{
Remove-PSSession -Session $s
}
}
The first test determined the possible depth of the recursion:
Definition of recursion depth
$scriptBlock1 =
{
Param($cnt)
function test($cnt)
{
if($cnt -ne 0)
{
test $($cnt - 1)
return
}
Write-Host " Call depth: $($(Get-PSCallStack).Count)"
}
test $cnt
}
runLocal $3000 scriptBlock1
runRemote $150 scriptBlock1
runRemote $160 scriptBlock1
----------
Local 3000
Call depth: 3004
Remote 150
Call depth: 152
Remote 160
The script failed due to call depth overflow.
The result is locally the stack depth of more than 3000, remotely — a little more than 150.
150 is quite a large value. To achieve it in the real work of deployment scripts is unreal.
The second test determines the possible depth of recursion when using objects:
determination of the depth of recursion when using objects
$scriptBlock2 =
{
Param($cnt)
function test()
{
$result = New-Object -Typename PSObject -Property @{ }
Add-Member -InputObject $result -MemberType ScriptMethod -Name Execute -Value `
{
Param($cnt)
if($cnt -ne 0)
{
$this.Execute($cnt - 1)
return
}
Write-Host " Call depth: $($(Get-PSCallStack).Count)"
}
return $result
}
$obj = test
$obj.Execute($cnt)
}
runLocal $3000 scriptBlock2
runRemote $130 scriptBlock2
runRemote $135 scriptBlock2
----------
Local 3000
Call depth: 3004
Remote 130
Call depth: 132
Remote 135
Processing data for a remote command failed with the following error message: The WSMan provider host process did not return a proper response.
The results are a bit worse. Remotely depth of the stack 130-133. But it is also very important.
Further study of the original deployment scripts prompted the idea to see how try-catch blocks:
determination of the depth of recursion when using objects and try-catch
$scriptBlock3 =
{
Param($cnt)
function test()
{
$result = New-Object -Typename PSObject -Property @{ }
Add-Member -InputObject $result -MemberType ScriptMethod -Name Execute -Value `
{
Param($cnt)
if($cnt -ne 0)
{
$this.Execute($cnt - 1)
return
}
Write-Host " Call depth: $($(Get-PSCallStack).Count)"
throw "error"
}
return $result
}
try
{
$obj = test
$obj.Execute($cnt)
}
catch
{
Write-Host "Exception catched"
}
}
runLocal $130 scriptBlock3
runRemote $scriptBlock3 5
runRemote $scriptBlock3 6
----------
Local 130
Call depth: 134
Exception catched
Remote 5
Call depth: 7
Exception catched
Remote 6
Call depth: 8
The script failed due to call depth overflow.
And here I was expecting a huge surprise. When using "objects" and generate exceptions possible stack depth locally was around 130, and remotely only 5.
determining the depth of the recursion by using try-catch with no objects
$scriptBlock4 =
{
Param($cnt)
function test($cnt)
{
if($cnt -ne 0)
{
test $($cnt - 1)
return
}
Write-Host " Call depth: $($(Get-PSCallStack).Count)"
throw "error"
}
try
{
test $cnt
}
catch
{
Write-Host "Exception catched"
}
}
runLocal $scriptBlock4 2000
runRemote $150 scriptBlock4
----------
Local 2000
Call depth: 2004
Exception catched
Remote 150
Call depth: 152
Exception catched
But the rejection of the use of "objects", the problem disappeared. The values of the stack depth was at the level of the first test.
In powershell 5 there classes. Did a test with their use:
determining the depth of the recursion by using try-catch with no objects
$scriptBlock5 =
{
Param($cnt)
Class test
{
Execute($cnt)
{
if($cnt -ne 0)
{
$this.Execute($cnt - 1)
return
}
Write-Host " Call depth: $($(Get-PSCallStack).Count)"
throw "error"
}
}
try
{
$t = [test]::new()
$t.Execute($cnt)
}
catch
{
Write-Host "Exception catched"
}
}
runLocal $130 scriptBlock5
runRemote $scriptBlock5 7
runRemote $scriptBlock5 8
----------
Local 130
Call depth: 134
Exception catched
Remote 7
Call depth: 9
Exception catched
Remote 8
Call depth: 10
The script failed due to call depth overflow.
The special prize is not received. When invoked via WinRM stack depth was only 7 hops. What is so-is not enough for normal operation scripts.
Working with test scripts came up with the idea to implement objects with hash + script block.
determining the depth of the recursion by using try-catch and hash + script block
$scriptBlock6 =
{
Param($cnt)
function Call($self, $scriptName, [parameter(ValueFromRemainingArguments = $true)] $args)
{
$args2 = @($self) + $args
Invoke-Command -ScriptBlock $self.$scriptName -ArgumentList $args2
}
function test()
{
$result = @{ }
$result.Execute =
{
Param($self, $cnt)
if($cnt -ne 0)
{
Call $self Execute $($cnt - 1)
return
}
Write-Host " Call depth: $($(Get-PSCallStack).Count)"
throw "error"
}
return $result
}
try
{
$obj = test
Call $obj Execute $cnt
}
catch
{
Write-Host "Exception catched"
}
}
runLocal $1000 scriptBlock6
runRemote $scriptBlock6 55
runRemote $60 scriptBlock6
----------
runLocal $1000 scriptBlock6
runRemote $scriptBlock6 55
runRemote $60 scriptBlock6
Local 1000
Call depth: 2005
Exception catched
Call depth: 113
Exception catched
Remote 60
Exception catched
The depth of the stack 55 of hops is already quite enough value.
Below brought to one table of test results available stack depth:
local | via winRM | |
>3000 | ~150 | |
object Method | >3000 | ~130 |
object Method with try-catch | ~130 | 5 |
a Function with try-catch | >2000 | ~150 |
a class Method (PS5) with try-catch | ~130 | 7 |
Hash + script block with try-catch | >1000 | ~55 |
I hope that this information will be useful not only to me! :)
Комментарии
Отправить комментарий