使用WinRM for ScriptMethods增加堆栈大小

Pav*_*rov 7 powershell winrm

我们目前正在重构我们的管理脚本.刚刚出现的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
    }
}
Run Code Online (Sandbox Code Playgroud)

只有六个嵌套调用足以溢出调用堆栈!实际上,超过200个本地嵌套调用工作正常,没有try-catch,可用深度加倍.常规函数也不限于递归.

注意:我只使用递归来重现问题,实际代码在不同模块中的不同对象上包含许多不同的函数.因此,"使用函数而不是ScriptMethod"这些微不足道的优化需要进行体系结构更改

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

Jer*_*ert 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
     )
 }
Run Code Online (Sandbox Code Playgroud)

这会产生大量堆栈跟踪,最终溢出所有合理的边界。当您使用远程处理时,问题会因为以下事实而加剧:即使脚本执行并产生这个巨大的异常,它(以及函数确实产生的任何结果)也无法成功远程处理 - 在我的机器上,使用 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
}
Run Code Online (Sandbox Code Playgroud)

虽然这会产生更令人愉快的异常,但当您进行远程处理时,即使这样也可能很快耗尽堆栈。在我的机器上,这可以达到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)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

显然foo这是微不足道的尾递归,这是矫枉过正的,但它说明了这个想法。迭代可以将多个项目压入堆栈。

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