gpt4 book ai didi

powershell - 使用 WinRM for ScriptMethods 增加堆栈大小

转载 作者:行者123 更新时间:2023-12-02 12:39:23 25 4
gpt4 key购买 nike

我们目前正在重构我们的管理脚本。刚刚看来,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”这样的琐碎优化需要架构更改

有没有办法增加可用堆栈大小? (我有一个管理帐户。)

最佳答案

有两个问题共同导致这件事变得困难。如果可能的话(我不知道是否可能),通过增加堆栈大小来最有效地解决这两个问题。

首先,正如您所经历的,远程处理会增加调用的开销,从而减少可用堆栈。我不知道为什么,但很容易证明它确实如此。这可能是由于运行空间的配置方式,或者解释器的调用方式,或者由于增加的簿记造成的——我不知道最终的原因。

第二,更糟糕的是,你的方法会产生一堆嵌套异常,而不仅仅是一个。发生这种情况是因为脚本方法实际上是包装在另一个异常处理程序中的脚本 block ,该异常处理程序将异常重新抛出为 MethodInvocationException 。结果,当您调用 foo(N) ,设置了一个嵌套异常处理程序 block (解释一下,实际上并不是 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是微不足道的尾递归,这有点矫枉过正,但它说明了这个想法。迭代可以将多个项目压入堆栈。

这不仅消除了堆栈深度有限的任何问题,而且速度也快得多。

关于powershell - 使用 WinRM for ScriptMethods 增加堆栈大小,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41822726/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com