У вас есть две проблемы, которые сговариваются, чтобы сделать это трудным. Ни один из них не будет эффективно решен путем увеличения размера стека, если это возможно (я не знаю, если это так).
Во-первых, как вы уже пережили, удаленное добавление накладных расходов вызывает вызовы, которые уменьшают доступный стек. Я не знаю почему, но это легко продемонстрировать, что это так. Это может быть связано с тем, как настраиваются рабочие пространства или как интерпретатор вызывается, или из-за увеличения объема бухгалтерии - я не знаю конечной причины (причин).
Во-вторых, гораздо более уныло, ваш метод производит кучу вложенные исключения, а не только один. Это происходит потому, что скриптовый метод, по сути, представляет собой блок сценария, завернутый в другой обработчик исключений, который вызывает это исключение как 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
тривиальным хвостовая рекурсия, и это излишеством, но она иллюстрирует идею. Итерации могут вызывать более одного элемента в стеке.
Это не только устраняет проблемы с ограниченной глубиной стека, но и намного быстрее.
'throw error' - что такое« ошибка »? –
Если я использую что-то вроде 'throw 'MY ERROR", то я не могу воспроизвести проблему (по крайней мере, не так, как описано). –
@RomanKuzmin этот код может быть выполнен как есть (если включен WinRM на локальном компьютере) –