エンタープライズ・サンドボックス・リソース制限
GraalVM Enterpriseによって、ゲスト・アプリケーションで使用されるリソースを制限できるようにする試験段階のサンドボックス・リソース制限機能が導入されました。これらのリソース制限は、GraalVMのCommunity Editionでは使用できません。このドキュメントでは、GraalVMポリグロットAPIのオプションを使用してサンドボックス・リソース制限を構成する方法について説明します。
通常、すべてのリソース制限オプションにはsandbox
オプション・グループの接頭辞が付いており、GraalVMで提供される言語ランチャ(js --help:tools
など)を使用してリストできます。ポリグロットのオプションは、言語ランチャを介して、Graal SDKのポリグロット埋込みAPIを使用して、またはJVM上でシステム・プロパティを使用して指定できます。例をより理解するには、リファレンス・マニュアルのポリグロット埋込みガイドを最初に読むことをお薦めします。
現在、すべてのサンドボックス・オプションは試験段階であるため、これらの例では試験段階のオプションが有効であると想定されています(たとえば--experimental-options
)。このオプションは、ゲスト・アプリケーションのリソース使用量を制限するためのベスト・エフォート・アプローチです。
リソース制限は、次のオプションを使用して構成できます:
--sandbox.AllocatedBytesCheckEnabled=true|false
: 実行コンテキストに割り当てられたバイトのチェックが有効かどうかを指定します。デフォルトでは、trueに設定されています。--sandbox.AllocatedBytesCheckFactor=[0.0, inf)
: 保持ヒープ・メモリーの計算をトリガーする割当てであるMaxHeapMemoryの係数を指定します。実行コンテキストに割り当てられたバイトが指定された係数に到達すると、コンテキストによってヒープに保持されるバイトの計算が開始されます。デフォルトでは1.0に設定されています。--sandbox.AllocatedBytesCheckInterval=[1, inf)ms|s|m|h|d
: 実行コンテキストに割り当てられたバイトをチェックする時間間隔。特定の割当てバイト数を超えると、コンテキストによってヒープに保持されるバイトの計算がトリガーされます。デフォルトでは10ミリ秒に設定されています。--sandbox.MaxASTDepth=[1, inf)
: 関数の最大AST深度(デフォルト: 制限なし)。--sandbox.MaxCPUTime=[1, inf)ms|s|m|h|d
: アプリケーションの実行に費やされた合計最大CPU時間を制限します。デフォルトでは制限は設定されていません。値の例: 100ミリ秒。--sandbox.MaxCPUTimeCheckInterval=[1, inf)ms|s|m|h|d
: 実行コンテキストのアクティブなCPU時間を確認する時間間隔。デフォルトでは10ミリ秒に設定されています。--sandbox.MaxHeapMemory=[1, inf)B|KB|MB|GB
: アプリケーションが実行中に保持できる最大ヒープ・メモリーを指定します。デフォルトでは制限は設定されず、関連するエキスパート・オプションを設定しても効果はありません。値の例: 100MB。--sandbox.MaxStackFrames=[1, inf)
: ゲスト・スタック・フレームの最大数を制限します(デフォルト: 制限なし)。--sandbox.MaxStatements=[1, inf)
: 実行されるゲスト言語文の最大数を制限します。超過すると、リソースが使い果たされたエラーのため、実行が取り消されます。--sandbox.MaxStatementsIncludeInternal
: 文の最大数の計算に内部ソースを含めるかどうかを構成します。--sandbox.MaxThreads=[1, inf)
: コンテキストで同時に入力できるスレッドの数を制限します(デフォルト: 制限なし)。--sandbox.RetainedBytesCheckFactor=[0.0, inf)
: 超えた場合にすべての処理を停止するホストVMの合計ヒープ・メモリーの係数を指定します。ホストVM全体のヒープに割り当てられた合計バイト数が係数を超えると、次のプロセスが開始されます。1つ以上のメモリー制限実行コンテキストを持つすべてのエンジンの実行が一時停止されます。メモリー制限されたコンテキストごとに、ヒープ内の保持バイトが計算されます。制限を超えるコンテキストは取り消されます。実行が再開されます。デフォルトでは0.7に設定されています。--sandbox.RetainedBytesCheckInterval=[1, inf)ms|s|m|h|d
: 単一の実行コンテキストに対して、ヒープ内の保持バイトの2つの計算間の最小時間間隔を指定します。デフォルトでは10ミリ秒に設定されています。--sandbox.UseLowMemoryTrigger=true|false
: すべての処理の停止を有効にするかどうかを指定します。有効にすると、ホストVM全体のヒープに割り当てられた合計バイト数がホストVMの指定された係数の合計ヒープ・メモリーを超えると、メモリー制限された実行コンテキストを少なくとも1つ持つエンジンが一時停止されます。デフォルトでは、trueに設定されています。
ポリグロット埋込みContext
インスタンスごとに異なる構成を指定できます。その他に、実行中の任意の時点で制限をリセットできます。リセットできるのは、sandbox.MaxStatements
およびsandbox.MaxCPUTime
のみです。
ゲスト言語は、外部実行コンテキスト内で内部コンテキストを作成することを選択する場合があります。制限は、外部コンテキストおよびそこから生成されるすべての内部コンテキストに適用されます。内部コンテキストに対して個別の制限を指定することはできず、内部コンテキストを作成して制限の対象外にすることもできません。
アクティブなCPU時間の制限
sandbox.MaxCPUTime
オプションを使用すると、アプリケーションの実行に費やされる最大CPU時間を指定できます。コンテキストがアクティブなまま最大CPU時間を経過すると、自動的に取り消されてクローズされます。デフォルトでは、時間制限は10ミリ秒ごとにチェックされます。これは、sandbox.MaxCPUTimeCheckInterval
オプションを使用してカスタマイズできます。最大CPU時間制限とチェック間隔は、どちらも正の値である必要があります。デフォルトでは、CPU時間制限は実施されません。時間制限を超えた場合、ポリグロット・コンテキストは取り消され、実行はisResourceExhausted()
に対してtrue
を返すPolyglotException
をスローして停止します。時間制限がトリガーされるとすぐに、アプリケーション・コードはこのコンテキストでそれ以上実行できなくなります。起動されるポリグロット・コンテキストのメソッドに対してPolyglotException
をスローし続けます。
コンテキストの使用されたCPU時間には、ホスト・コードへのコールバックに費やされた時間が含まれます。これは、ポリグロット分離で実行する場合にも当てはまります。
コンテキストで使用されたCPU時間には、通常、同期またはIOの待機に費やされた時間は含まれません。すべてのスレッドのCPU時間が合算され、CPU時間制限と照合されます。これは、2つのスレッドで同じコンテキストが実行された場合、時間制限を2倍速く超えることを意味します。
時間制限は、定期的に呼び出される別の優先度の高いスレッドによって実施されます。指定した精度でコンテキストが取り消される保証はありません。ホストVMで完全なガベージ・コレクションが発生した場合など、精度を大幅に外れる場合があります。時間制限を超えない場合、ゲスト・コンテキストのスループットは影響を受けません。あるコンテキストで時間制限を超えた場合、同じ明示的なエンジンを使用する他のコンテキストのスループットが一時的に低下する可能性があります。
時間を指定するために使用できる単位は、ミリ秒の場合はms
、秒の場合はs
、分の場合はm
、時間の場合はh
、日の場合はd
です。CPU時間制限オプションで負の値を指定したり、時間単位を指定することは許可されていません。
使用例
try (Context context = Context.newBuilder("js")
.allowExperimentalOptions(true)
.option("sandbox.MaxCPUTime", "500ms")
.option("sandbox.MaxCPUTimeCheckInterval", "5ms")
.build();) {
context.eval("js", "while(true);");
assert false;
} catch (PolyglotException e) {
// triggered after 500ms;
// context is closed and can no longer be used
// error message: Maximum CPU time limit of 500ms exceeded.
assert e.isCancelled();
assert e.isResourceExhausted();
}
実行される文の数の制限
コンテキストが取り消されるまでコンテキストが実行できる文の最大数を指定します。文の制限がトリガーされたコンテキストは使用できなくなり、そのコンテキストの使用のたびにPolyglotException.isCancelled()
に対してtrue
を返すPolyglotException
がスローされます。文の制限は、実行中のスレッド数には関係なく、コンテキストごとに適用されます。また、ポリグロット埋込みAPIの[ResourceLimits]()
APIを使用して、この制限を指定することもできます。
デフォルトでは、文の制限は適用されません。この制限を負の数に設定して無効にできます。この制限が適用されるかどうかに関係なく、内部ソースはsandbox.MaxStatementsIncludeInternal
を使用してのみ構成できます。デフォルトでは、この制限には内部とマークされたソースの文は含まれません。共有エンジンを使用する場合は、1つのエンジンのすべてのコンテキストで同じ内部構成を使用する必要があります。文の最大制限は、エンジンのコンテキストごとに個別に構成できます。
文の制限をコンテキストにアタッチすると、同じエンジンを使用するすべてのゲスト・アプリケーションのスループットが低下します。実行されるすべての文によって文カウンタが更新される必要があります。本番で使用する前に、文の制限の使用をベンチマークすることをお薦めします。
ゲスト言語によっては、単一の文の複雑度が一定時間ではない場合があります。たとえば、JavaScriptの組込み機能(Array.sort
など)を実行する文は単一の文となる場合がありますが、その実行時間は配列のサイズによって異なります。したがって、文の数の制限はタイム・ボクシングの実行には適しておらず、CPU時間制限など、より信頼性の高い他の対策と組み合せる必要があります。
try (Context context = Context.newBuilder("js")
.allowExperimentalOptions(true)
.option("sandbox.MaxStatements", "2")
.option("sandbox.MaxStatementsIncludeInternal", "false")
.build();) {
context.eval("js", "purpose = 41");
context.eval("js", "purpose++");
context.eval("js", "purpose++"); // triggers max statements
assert false;
} catch (PolyglotException e) {
// context is closed and can no longer be used
// error message: Maximum statements limit of 2 exceeded.
assert e.isCancelled();
assert e.isResourceExhausted();
}
関数のAST深度の制限
ゲスト言語関数の最大式の深度の制限。インストゥルメント可能なノードのみが制限に対してカウントされます。制限を超えた場合、コードの評価は失敗し、コンテキストは取り消されます。
AST深度により、関数の複雑度およびそのスタック・フレーム・サイズを見積もることができます。AST深度の制限は、単一の関数による恣意的なスタック領域使用に対する保護手段として機能します。
スタック・フレーム数の制限
コンテキストがスタック上でプッシュできるフレームの最大数を指定します。この制限を超えると、コンテキストは取り消されます。スレッドローカルなスタック・フレーム・カウンタが関数の開始時に増分され、関数の終了時に減分されます。リソース制限をリセットしても、スタック・フレーム・カウンタには影響しません。
スタック・フレーム制限はそれ自体で、無限再帰に対する保護手段として機能します。AST深度制限とともに使用すると、スタック領域使用の合計を見積もるために使用できます。
アクティブ・スレッド数の制限
コンテキストで同時に使用できるスレッドの数を制限します。デフォルトでは、任意の数のスレッドを使用できます。設定された制限を超えた場合、コンテキストに入るとPolyglotException
で失敗し、ポリグロット・コンテキストは取り消されます。リソース制限をリセットしても、スレッド制限には影響しません。
最大ヒープ・メモリーの制限
sandbox.MaxHeapMemory
オプションを使用すると、アプリケーションが実行中に保持できる最大ヒープ・メモリーを指定できます。sandbox.MaxHeapMemory
は正である必要があります。このオプションは、HotSpotベースのVMでのみサポートされます。ネイティブ実行可能ファイルでこのオプションを有効にすると、PolyglotException
が発生します。このオプションは、ポリグロット分離でもサポートされず、メモリー消費を制御する方法が異なります。制限の超過が検出されると、対応するコンテキストが自動的に取り消され、閉じられます。
ゲスト・アプリケーションに存在するオブジェクトのみが制限に対してカウントされます。ホスト・コードへのコールバック中に割り当てられたメモリーはカウントされません。このオプションの有効性は(も)、使用しているガベージ・コレクタによって異なります。
使用例
try (Context context = Context.newBuilder("js")
.allowExperimentalOptions(true)
.option("sandbox.MaxHeapMemory", "100MB")
.build()) {
context.eval("js", "var r = {}; var o = r; while(true) { o.o = {}; o = o.o; };");
assert false;
} catch (PolyglotException e) {
// triggered after the retained size is greater than 100MB;
// context is closed and can no longer be used
// error message: Maximum heap memory limit of 104857600 bytes exceeded. Current memory at least...
assert e.isCancelled();
assert e.isResourceExhausted();
}
実装の詳細およびエキスパート・オプション
この制限は、割当て済みバイトまたは低メモリー通知に基づいてトリガーされた保持サイズ計算によってチェックされます。
割り当てられたバイトは、定期的に呼び出される別の優先度の高いスレッドによってチェックされます。メモリー制限されたコンテキストごとにこのようなスレッドが1つ(sandbox.MaxHeapMemory
が設定されたスレッド)あります。保持バイトの計算は、必要に応じて、割り当てられたバイト・チェック・スレッドから起動されるもう1つの優先度の高いスレッドによってさらに行われます。ヒープ・メモリー制限を超えると、保持バイトの計算スレッドもコンテキストを取り消します。さらに、低メモリー・トリガーが呼び出されると、メモリー制限されたコンテキストが少なくとも1つあるエンジン上のすべてのコンテキストが、割り当てチェッカとともに一時停止されます。すべての個々の保持サイズ計算は取り消されます。メモリー制限されたコンテキストごとのヒープ内の保持バイトは、単一の優先順位の高いスレッドによって計算されます。制限を超えるコンテキストは取り消され、実行は再開されます。
ヒープ・メモリー制限の主な目的は、ほとんどの場合ヒープ・メモリー不足関連のエラーを防止し、不適切なコンテキストが存在する場合でもホストVMをスムーズに実行できるようにすることです。実装はベスト・エフォートです。これは、ヒープ・メモリー制限の正確性に対する保証がないことを意味します。また、ヒープ・メモリー制限の設定は、コンテキストでOutOfMemory
エラーが発生しないことを保証するものでもありません。多数のオブジェクトをすばやく連続して割り当てるゲスト・アプリケーションは、オブジェクトを割り当てることがほとんどないアプリケーションよりも正確性が低くなります。ゲスト・コードの実行は、ホスト・ヒープ・メモリーが不足し、ホストVMの低メモリー・トリガーが呼び出された場合にのみ一時停止します。一時停止の範囲はエンジンであるため、sandbox.MaxHeapMemory
オプションが設定されていないコンテキストも、メモリーが制限されている他のコンテキストとエンジンを共有している場合、一時停止します。また、1つのコンテキストが取り消されると、同じ明示的なエンジンを持つ他のコンテキストの速度が低下する可能性があります。コンテキストによって保持されるサイズの計算方法は、次に示すエキスパート・オプションsandbox.AllocatedBytesCheckInterval
、sandbox.AllocatedBytesCheckEnabled
、sandbox.AllocatedBytesCheckFactor
、sandbox.RetainedBytesCheckInterval
、sandbox.RetainedBytesCheckFactor
およびsandbox.UseLowMemoryTrigger
を使用してカスタマイズできます。
コンテキストの保持サイズ計算は、保持されたバイトの推定が、指定されたsandbox.MaxHeapMemory
の特定の係数を超えるとトリガーされます。推定は、コンテキストがアクティブなスレッドによって割り当てられたヒープ・メモリーに基づきます。より正確には、推定とは、前の保持バイトの計算結果(使用可能な場合)と、前の計算の開始以降に割り当てられたバイト数です。デフォルトでは、sandbox.MaxHeapMemory
の係数は1.0で、sandbox.AllocatedBytesCheckFactor
オプションでカスタマイズできます。係数には正の値を指定する必要があります。たとえば、sandbox.MaxHeapMemory
を100MB、sandbox.AllocatedBytesCheckFactor
を0.5とします。保持サイズの計算は、割り当てられたバイトが50MBに達したときに最初にトリガーされます。計算された保持サイズが25MBとすると、追加の25MBが割り当てられたときに次の保持サイズの計算がトリガーされます。
デフォルトでは、割り当てられたバイトは10ミリ秒ごとにチェックされます。これは、sandbox.AllocatedBytesCheckInterval
で構成できます。指定可能な最小間隔は1ミリ秒です。より小さい値は1ミリ秒と解釈されます。
同じコンテキストの2つの保持サイズ計算の開始は、デフォルトでは少なくとも10ミリ秒離れている必要があります。これは、sandbox.RetainedBytesCheckInterval
オプションで構成できます。間隔は正数である必要があります。
コンテキストに割り当てられたバイトのチェックは、sandbox.AllocatedBytesCheckEnabled
オプションによって無効にできます。デフォルトでは、有効になっています(true)。無効にすると(false)、コンテキストの保持サイズ・チェックは、低メモリー・トリガーによってのみトリガーできます。
ホストVM全体のヒープに割り当てられたバイトの合計数がVMのヒープ・メモリーの合計の特定の係数を超えると、低メモリー通知が起動され、次のプロセスが開始されます。sandbox.MaxHeapMemory
オプションが設定されている実行コンテキストが1つ以上あるすべてのエンジンの実行は一時停止され、メモリー制限されたコンテキストごとにヒープ内の保持バイトが計算され、その制限を超えるコンテキストは取り消されて、実行が再開されます。デフォルトの係数は0.7です。これは、sandbox.RetainedBytesCheckFactor
オプションで構成できます。係数は0.0から1.0の間である必要があります。sandbox.MaxHeapMemory
オプションを使用するすべてのコンテキストは、sandbox.RetainedBytesCheckFactor
に同じ値を使用する必要があります。
説明されている低メモリー・トリガーは、sandbox.UseLowMemoryTrigger
オプションによって無効にできます。デフォルトでは、有効になっています(true)。無効(false)の場合、実行コンテキストの保持サイズ・チェックは、割り当てられたバイト・チェッカによってのみトリガーできます。sandbox.MaxHeapMemory
オプションを使用するすべてのコンテキストは、sandbox.UseLowMemoryTrigger
に同じ値を使用する必要があります。
ヒープ・メモリー制限の超過が検出されると、ポリグロット・コンテキストは取り消され、実行はisResourceExhausted()
に対してtrue
を返すPolyglotException
をスローして停止します。メモリー制限がトリガーされるとすぐに、アプリケーション・コードはこのコンテキストでそれ以上実行できなくなります。起動されるポリグロット・コンテキストのメソッドに対してPolyglotException
をスローし続けます。
時間を指定するために使用できる単位は、ミリ秒の場合はms
、秒の場合はs
、分の場合はm
、時間の場合はh
、日の場合はd
です。最大ヒープ・メモリー・オプションで負の値を指定したり、時間単位を指定することは許可されていません。
サイズを指定できる単位は、バイトの場合はB
、キロバイトの場合はKB
、メガバイトの場合はMB
、ギガバイトの場合はGB
です。最大ヒープ・メモリー・オプションで負の値を指定したり、サイズ単位を指定することは許可されていません。
Context.resetLimits
を使用してリソース制限をリセットしても、ヒープ・メモリー制限には影響しません。
リソース制限のリセット
ポリグロット埋込みAPIでは、Context.resetLimits
メソッドを使用して任意の時点で制限をリセットできます。これは、既知の信頼できる初期化スクリプトを制限から除外する場合に役立ちます。制限のリセットは、すべての制限には適用されません。
使用例
try (Context context = Context.newBuilder("js")
.allowExperimentalOptions(true)
.option("sandbox.MaxCPUTime", "500ms")
.build();) {
context.eval("js", /*... initialization script ...*/);
context.resetLimits();
context.eval("js", /*... user script ...*/);
assert false;
} catch (PolyglotException e) {
assert e.isCancelled();
assert e.isResourceExhausted();
}