使用 WinRM 增加 ScriptMethods 的堆栈大小
Posted
技术标签:
【中文标题】使用 WinRM 增加 ScriptMethods 的堆栈大小【英文标题】:Increasing stack size with WinRM for ScriptMethods 【发布时间】:2017-06-08 22:18:30 【问题描述】:我们目前正在重构我们的管理脚本。 刚刚看来,WinRM、错误处理和 ScriptMethod 的组合显着降低了可用的递归深度。
请看下面的例子:
Invoke-Command -ComputerName . -ScriptBlock
$object = New-Object psobject
$object | Add-Member ScriptMethod foo
param($depth)
if ($depth -eq 0)
throw "error"
else
$this.foo($depth - 1)
try
$object.foo(5) # Works fine, the error gets caught
catch
Write-Host $_.Exception
try
$object.foo(6) # Failure due to call stack overflow
catch
Write-Host $_.Exception
仅仅六个嵌套调用就足以溢出调用堆栈! 事实上,超过 200 个本地嵌套调用工作正常,并且没有 try-catch 可用深度加倍。常规函数在递归方面也不受限制。
注意:我使用递归只是为了重现问题,真正的代码在不同模块的不同对象上包含许多不同的函数。因此,像“使用函数而不是 ScriptMethod”这样的微不足道的优化需要架构更改
有没有办法增加可用堆栈大小? (我有一个管理帐户。)
【问题讨论】:
throw error
- 什么是“错误”?
如果我使用throw "MY ERROR"
之类的东西,那么我无法重现问题(至少不像描述的那样)。
@RomanKuzmin 此代码可以按原样执行(如果本地计算机上的 WinRM 已启用)
@RomanKuzmin 我修复了这个语法错误...并且递归限制增加了 1。这还不够 :)
如果我使用 . ...
而不是 Invoke-Command
那么深度 134 很好(135 溢出)。我认为这不仅仅是ScriptMethod
,而是它和Invoke-Command
的组合,大概添加了自己的嵌套调用。
【参考方案1】:
您有两个问题共同使这变得困难。如果可能的话,通过增加堆栈大小来最有效地解决这两个问题(我不知道是不是)。
首先,正如您所经历的那样,远程处理会增加调用的开销,从而减少可用堆栈。我不知道为什么,但很容易证明它确实如此。这可能是由于运行空间的配置方式,解释器的调用方式,或者是由于增加了簿记——我不知道最终原因。
其次,更糟糕的是,您的方法会产生一堆嵌套异常,而不仅仅是一个。发生这种情况是因为脚本方法实际上是包装在另一个异常处理程序中的脚本块,该处理程序将异常重新抛出为MethodInvocationException
。因此,当您调用 foo(N)
时,会设置一个嵌套异常处理程序块(换句话说,执行此操作的实际上不是 PowerShell 代码):
try
try
...
try
throw "error"
catch
throw [System.Management.Automation.MethodInvocationException]::new(
"Exception calling ""foo"" with ""1"" argument(s): ""$($_.Exception.Message)""",
$_.Exception
)
...
catch
throw [System.Management.Automation.MethodInvocationException]::new(
"Exception calling ""foo"" with ""1"" argument(s): ""$($_.Exception.Message)""",
$_.Exception
)
catch
throw [System.Management.Automation.MethodInvocationException]::new(
"Exception calling ""foo"" with ""1"" argument(s): ""$($_.Exception.Message)""",
$_.Exception
)
这会产生大量堆栈跟踪,最终会溢出所有合理的边界。当您使用远程处理时,即使脚本执行并产生了这个巨大的异常,它(以及该函数确实产生的任何结果)也无法成功地远程处理——在我的机器上,使用 PowerShell 5,当我调用foo(10)
时,我没有收到堆栈溢出错误,而是出现远程错误。
这里的解决方案是避免递归脚本方法和异常的这种特殊的致命组合。假设您不想摆脱递归或异常,这最容易通过包装常规函数来完成:
$object = New-Object PSObject
$object | Add-Member ScriptMethod foo
param($depth)
function foo($depth)
if ($depth -eq 0)
throw "error"
else
foo ($depth - 1)
foo $depth
虽然这会产生更令人愉快的异常,但当您进行远程处理时,即使这样也会很快耗尽堆栈。在我的机器上,这适用于foo(200)
;除此之外,我得到一个呼叫深度溢出。在本地,限制要高得多,尽管 PowerShell 会因大参数而变得异常缓慢。
作为一种脚本语言,PowerShell 的设计初衷并不是有效地处理递归。如果您需要的不仅仅是foo(200)
,我的建议是咬紧牙关重写函数,使其不递归。像Stack<T>
这样的类可以在这里提供帮助:
$object = New-Object PSObject
$object | Add-Member ScriptMethod foo
param($depth)
$stack = New-Object System.Collections.Generic.Stack[int]
$stack.Push($depth)
while ($stack.Count -gt 0)
$item = $stack.Pop()
if ($item -eq 0)
throw "error"
else
$stack.Push($item - 1)
显然foo
是微不足道的尾递归,这有点矫枉过正,但它说明了这个想法。迭代可以将多个项目压入堆栈。
这不仅消除了堆栈深度有限的任何问题,而且速度也快了很多。
【讨论】:
我只将递归用于演示 - 在实际代码中,不同对象上有 5 个不同的函数。 @PavelMayorov:你的意思是,不同脚本方法之间的相互递归?在那种情况下,是的,我的方法对你没有帮助。在这种情况下,我告诉你,如果重写不是一个选项,你就是 Boned (tm),除非有人想出一些聪明的方法来调整堆栈大小。请注意,嵌套异常处理程序的问题仍然是此类脚本方法的实际功能问题。听起来您的代码库正在使用脚本方法作为在 PowerShell 中执行 O-O 的一种方式,这在理论上是个好主意,但在实践中却不是。 @PavelMayorov:异常处理呢?如果您在深度嵌套的方法中既没有递归也没有异常,那么您应该能够将调用进行得很远。如果问题是深层嵌套的异常,那就另当别论了。 真正的异常是 FileNotFoundException。无法避免此异常(尝试测试文件是否存在会导致竞争条件)。 无论如何,总是会发生意外的异常。根异常处理是定位仅在十三号星期五发生的错误的唯一方法,而满月正是在 13:13 和老板正在寻找的时候。【参考方案2】:如果您超出远程会话中的可用内存,可能值得检查一下:Running Java remotely using PowerShell
我知道它是用于运行 Java 应用程序,但该解决方案会更新远程 WinRM 会话可用的最大内存。
【讨论】:
我有足够的内存用于这个简单的脚本 (1Gb)。问题的根源在于堆栈大小。以上是关于使用 WinRM 增加 ScriptMethods 的堆栈大小的主要内容,如果未能解决你的问题,请参考以下文章
使用 Vagrant 配置 Windows VM 无法执行远程 WinRM 命令