Powershellとスタックの深さ

開発者として、私はしばしば展開スクリプトを開発します。 プロジェクトの1つで、プロジェクトの展開を自動化するタスクがありました。これは、数十個のタスクで構成され、スタンドに展開されるコンポーネントの構成をカスタマイズできます。



まず、タスクインターフェイスを統合するための作業が行われ、次の方法が特定されました。



展開ジョブインターフェイス
$Task1_Config = ...; # ,     . function Task1_CheckRequirements() {} # ,     . function Task1_CanExecute($project) {} #   . function Task1_Execute($project, $context) {}
      
      







そのような手順がますます増えていることを考えると、この形式でのスクリプトの保守はますます困難になりました。 考えられる解決策を検討した後、各タスクを個別のオブジェクトとして実装することが決定されました。



展開タスクのオブジェクトインターフェイス
 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 }
      
      







この形式では、展開スクリプトが長時間にわたって機能し、トラブルの前兆はありませんでした。 ある時点で、リモートサーバーに展開するという課題に直面しました。 Powershellには非常に便利なWinRMメカニズムがあり、以前に非常に積極的に使用していたため、同じものを使用してタスクを解決することにしました。



ソリューションは不安定になりました。 一部の展開タスクで、エラーが発生したか、Invoke-Commandがリモートスクリプトが正しく実行されたことを示しましたが、実際には中断されました。

リモートコマンドデータの処理に失敗しました。 エラーメッセージ:WSManプロバイダーのホストプロセスが正しい答えを返しませんでした。 リードプロセスのサプライヤが正しく動作しない可能性があります


リモートコマンドのデータの処理は、次のエラーメッセージで失敗しました。WSManプロバイダーホストプロセスは適切な応答を返しませんでした。 ホストプロセスのプロバイダーが正しく動作していない可能性があります。


EventViewerで、リモートマシン上のプロセスがエラー1726で終了したことがわかりましたが、わかりやすいエラー情報は見つかりませんでした。 この場合、リモートマシンでの同じタスクの起動は常に正常に完了しました。



多数の実験の過程で、呼び出しの深さのオーバーフローによりスクリプトが失敗し、それが研究のさらなる方向性を決定したことを発見しました。



PowerShell v2以降、powershellスクリプトの最大スタック深度は1,000コールでしたが、以降のバージョンではこの値が大幅に引き上げられ、スタックオーバーフローエラーは発生しませんでした。



ローカルおよびWinRMから呼び出されたときにスタックの深さを判断するために、いくつかのテストを実施することにしました。 このために、テストツールを準備しました。



テストツールキット
 $ErrorActionPreference = "Stop" $cred = New-Object System.Management.Automation.PsCredential(...) function runLocal($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 } }
      
      







最初のテストでは、考えられる再帰の深さを決定しました。



再帰の深さの決定
 $scriptBlock1 = { Param($cnt) function test($cnt) { if($cnt -ne 0) { test $($cnt - 1) return } Write-Host " Call depth: $($(Get-PSCallStack).Count)" } test $cnt } runLocal $scriptBlock1 3000 runRemote $scriptBlock1 150 runRemote $scriptBlock1 160 ---------- Local 3000 Call depth: 3004 Remote 150 Call depth: 152 Remote 160 The script failed due to call depth overflow.
      
      







その結果、ローカルでのスタックの深さは3000を超え、リモートで-150を少し超えます。



150は非常に重要です。 展開スクリプトの実際の作業でそれを実現することは非現実的です。



2番目のテストは、オブジェクトを使用するときに再帰の可能な深さを決定します。



オブジェクト使用時の再帰の深さの決定
 $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 $scriptBlock2 3000 runRemote $scriptBlock2 130 runRemote $scriptBlock2 135 ---------- 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.
      
      







結果は少し悪くなります。 深度130-133をリモートでスタックします。 しかし、仕事にとっても非常に重要です。



ソース展開スクリプトのさらなる研究では、try-catchブロックがどのように機能するかを調べることを提案しました。



オブジェクトと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 $scriptBlock3 130 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.
      
      







そして、ここで大きな驚きが待っていました。 「オブジェクト」を使用して例外的な状況を生成した場合、可能なスタック深度はローカルで約130で、リモートではわずか5でした。



オブジェクトなしでtry-catchを使用する場合の再帰の深さの決定
 $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 $scriptBlock4 150 ---------- Local 2000 Call depth: 2004 Exception catched Remote 150 Call depth: 152 Exception catched
      
      







しかし、「オブジェクト」の使用が拒否されると、問題はなくなりました。 スタックの深さは、最初のテストのレベルでした。



クラスはpowershell 5で登場しました。 それらを使用してテストを実施しました:



オブジェクトなしでtry-catchを使用する場合の再帰の深さの決定
 $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 $scriptBlock5 130 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.
      
      







彼らはあまり勝ちませんでした WinRMを介して呼び出されたとき、スタックの深さは7つの希望でした。 また、スクリプトの通常の操作には不十分です。



テストスクリプトを使用して、ハッシュ+スクリプトブロックを使用してオブジェクトを実装するという考えが生まれました。



try-catchおよびhash + scriptブロックを使用して再帰の深さを決定する
 $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 $scriptBlock6 1000 runRemote $scriptBlock6 55 runRemote $scriptBlock6 60 ---------- runLocal $scriptBlock6 1000 runRemote $scriptBlock6 55 runRemote $scriptBlock6 60 Local 1000 Call depth: 2005 Exception catched Remote 55 Call depth: 113 Exception catched Remote 60 Exception catched
      
      







55ホップのスタックの深さ-これは十分な値です。



以下に、利用可能なスタック深度のテスト結果を1つの表にまとめました。

局所的に winRMを介して
機能 > 3000 〜150
オブジェクトメソッド > 3000 〜130
try-catchを使用したオブジェクトメソッド 〜130 5
try-catchを使用した関数 > 2000 〜150
try-catchを使用したクラスメソッド(PS5) 〜130 7
try-catchを使用したハッシュ+スクリプトブロック > 1000 〜55


この情報が私だけでなく役に立つことを願っています! :)



All Articles