mcpField
T
taskpool-dependency-analysis

taskpool-dependency-analysis

github.com/openharmonyinsight開発3541 回インストール11 週間前に更新
リポジトリ

taskpool-dependency-analysis について

taskpool-dependency-analysisは、静的ArkTSランタイムのタスク依存関係を診断するスキルです。依存グラフの作成、循環やAPI呼び出し順の確認、通知とキャンセル伝播の追跡、修正案の提示を支援します。

主な機能

  1. 01コードからタスク依存グラフを作成
  2. 02循環と不正なタスク種別の辺を検出
  3. 03通知とタスク起動の経路を追跡
  4. 04キャンセル伝播を分析
  5. 05構造化された診断と修正案を提示

活用シーン

  1. 01実行されないtaskpoolタスクの調査
  2. 02循環または欠落した依存関係の診断
  3. 03不正な依存API呼び出し順の発見
  4. 04タスク間のキャンセル伝播障害の追跡

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/openharmonyinsight/openharmony-skills --skill taskpool-dependency-analysis

インストール後、Claude Code で/taskpool-dependency-analysisを入力して読み込まれたか確認します。

同梱ファイルevals/evals.jsonreferences/dependency_reference.md

詳細

Taskpool の依存関係分析スキル

このスキルは、静的 ArkTS ランタイムにおける Taskpool の依存関係の問題を体系的に診断し、修正するのに役立ちます。ユーザーコードから依存関係グラフを構築し、循環を検出し、不正な API 呼び出し順序を特定し、通知/アクティベーション経路を追跡して、修正を提案する手順を説明します。

このスキルが必要な理由

Taskpool の依存関係は、「単なるデッドロック検出」ではありません。独自の状態マシン、通知メカニズム、タスク種別の制約、キャンセル伝播ルールを備えた、独自のタスク依存関係モデルです。あるタスクが別のタスクに依存している場合、そのタスクは taskDependenciesCount がゼロになるまで waitForDependencies() を介してブロックします。前提タスクが終了すると、notifyDependencies() を呼び出してカウンターを減らし、tryActivatePendingDependencyTask を介して、実行可能になった依存先タスクを再度キューに登録します。この一連の処理のどこかで問題が起きると、依存先タスクは気づかれないまま停止するか、実行されなくなります。根本原因は、多くの場合、症状が現れる場所とは異なるところにあります。

ソースコードの参照先

このスキルを使用する際は、正確な実装を確認するため、以下のソースファイルを参照してください。

  • 静的標準ライブラリ:runtime_core/static_core/plugins/ets/stdlib/std/concurrency/taskpool.ets

    • Task.addDependency(1238 行目付近):タスク種別を検証し、hasTaskDFS で循環依存を確認して、dependentTasks セットにエッジを追加します
    • Task.removeDependency(1284 行目付近):状態を検証し、エッジを削除して、taskDependenciesCount を調整します
    • Task.hasTaskDFS(1371 行目付近):dependentTasks をたどる DFS で循環を検出します
    • InternalTask.waitForDependencies(718 行目付近):taskDependenciesCount == 0 になるまで taskMutex/condVar でブロックします
    • InternalTask.notifyDependencies(731 行目付近):各依存先のカウンターを減らし、条件変数にシグナルを送り、実行可能なタスクをアクティベートします
    • tryActivatePendingDependencyTask(3014 行目付近):すべての依存関係が満たされたタスクを再度キューに登録します
    • moveToPendingDependencyQueue(2993 行目付近):タスクが取り出されたものの、まだブロックされている場合に、タスクと優先度を保留中のマップに保存します
  • 動的実装の参照先:commonlibrary/ets_utils/js_concurrent_module/taskpool/task.cpp および task_manager.cpp

    • Task::AddDependency、Task::RemoveDependency、Task::CheckAddDependency
    • TaskManager::StoreTaskDependency、TaskManager::CheckCircularDependency、TaskManager::NotifyDependencyTaskInfo、TaskManager::RemoveTaskDependency、TaskManager::ClearDependentTask
  • テストの参照先:runtime_core/static_core/plugins/ets/tests/ets-common-tests/taskpool/common_tasks.ets

    • dependentTasksTest、circularDependencyTest、dependencyExecutedTaskTest、dependencyCancelTaskTest、doubleCancelByDependencyTest

データ構造、エラーコード、ライフサイクル全体の詳細については、このスキルのディレクトリにある references/dependency_reference.md を参照してください。

分析ワークフロー

以下の手順を順番に実行してください。手順を省略したり、まとめたりしないでください。

ステップ 1:問題を分類する

ユーザーの説明または失敗したテストケースを読み、次のいずれかのカテゴリに分類します。

カテゴリ典型的な症状エラーコード
循環依存addDependency が即座にスローされる10200026
依存関係の欠落/存在しない依存関係removeDependency がスローされる10200027, 10200097
依存先タスクが起動しないタスクが送信されたが実行されず、応答のないまま停止するなし(エラーはスローされない)
キャンセル伝播の失敗前提タスクをキャンセルしても依存タスクがキャンセルされない、またはその逆なし
不正な API 呼び出し順序誤ったタスク種別に対して addDependency/removeDependency を呼び出している10200070, 10200071, 10200073, 10200074, 10200079, 10200113
依存関係設定後の再実行依存関係のあるタスクを、すでに実行した後に再実行する10200072
タイムアウトと依存関係の競合依存関係のあるタスクに対して execute(task, {timeout}) を呼び出すカスタムエラー

ユーザーがテストケース(.ets ファイル)を提示した場合は、まずそれを読み、タスクの作成順序、addDependency の呼び出し、execute の呼び出し、および予期される動作と実際の動作を抽出します。

ステップ 2:ユーザーコードから依存関係グラフを構築する

ユーザーのコードまたはテストケースから、すべての Task の作成、addDependency、removeDependency、および execute の呼び出しを順に抽出します。有向グラフを構築します。

  • ノード:各 Task インスタンス(変数名と、取得可能な場合は taskId でラベル付けします)
  • 辺:各 addDependency が dependent → prerequisite の辺を作成します(dependent は prerequisite の実行を待ちます)
  • 辺の削除:各 removeDependency がその辺を削除します
  • 実行状態:execute/cancel の呼び出しに基づき、ノードを unexecuted、submitted、running、finished、canceled のいずれかとしてマークします

次のように、グラフをテキストで表します。

task1 ──depends on──> task2 ──depends on──> task3
task4 ──depends on──> task2

removeDependency によってグラフが変更されている場合は、重要な状態変化ごとにグラフを示します。

ステップ 3:循環と不正な辺を検出する

グラフに対して次のチェックを実施します。

3a:循環の検出

各ノードから DFS を実行します。いずれかの経路が開始ノードに戻る場合、それは循環です。正確な循環経路を報告します。

Cycle detected: task1 → task2 → task3 → task1
Introduced by: task3.addDependency(task1) at line X

静的実装では hasTaskDFS を使用して dependentTasks をたどります(逆方向、つまり前提タスクからその依存タスクへたどります)。循環の検出がこの走査方向と一致していることを確認してください。

3b:不正なタスク種別の辺

各ノードについて、次のルールを確認します(これらは addDependency と removeDependency における正確なチェックです)。

タスク種別addDependency を呼び出せるか依存先の対象にできるかremoveDependency を呼び出せるか
未実行(TASK)はいはいはい(isDependent の場合)
実行済み(COMMON_TASK、execute 後)いいえ(10200070)いいえ(10200079)いいえ(10200071)
周期タスクいいえ(10200113)いいえ(10200113)いいえ(10200113)
グループタスクいいえ(10200073)いいえ(10200074)該当なし
SeqRunner タスクいいえ(10200070)いいえ(10200079)いいえ(10200071)
AsyncRunner タスクいいえ(10200070)いいえ(10200079)いいえ(10200071)
タイムアウトタスクいいえいいえ該当なし

違反があれば、該当するエラーコードとともに報告します。

3c:依存関係の辺の欠落

removeDependency のエラーについては、削除時点でその辺が実際にグラフ内に存在するか確認します。ユーザーコードで removeDependency(taskX) を呼び出していても、taskX が呼び出し元の前提タスクでない場合は、エラー 10200027 です。

Step 4: 通知/アクティベーションの経路を追跡する

これは、「依存タスクが起動しない」問題において最も重要なステップです。前提タスクの完了から、依存タスクが実行されるはずのタイミングまで、経路全体を追跡します。

Prerequisite finishes
  → InternalTask.execute() finally block
    → notifyDependencies() called
      → Iterates over prerequisite.dependentTasks
        → For each dependent:
          - Locks dependent.taskMutex
          - Decrements dependent.taskDependenciesCount
          - Signals dependent.condVar
          - If taskDependenciesCount == 0:
            → tryActivatePendingDependencyTask(dependent)
              - Takes priority from pendingDependencyTasks map
              - Re-enqueues dependent into globalTaskQueue
              - Signals global condvar to wake a worker

各ステップで、次の潜在的な障害を確認します。

障害箇所症状根本原因のパターン
notifyDependencies が呼び出されない依存タスクが起動しないタスクタイプが COMMON_TASK ではない(動的:TriggerTask は共通タスクに対してのみ NotifyDependencyTaskInfo を呼び出す)、またはタスクがキャンセルされた(isCancel=true の場合、通知はスキップされる)
dependentTasks が空依存タスクに通知されない実行前にエッジが削除された、または execute の後に addDependency が呼び出された
taskDependenciesCount が 0 にならない依存タスクが起動するが、再び待機する前提タスクの一部が、この依存タスクのカウントをデクリメントせずに完了した(競合:通知の実行後にカウントがインクリメントされた)
pendingDependencyTasks に依存タスクのエントリーがないtryActivatePendingDependencyTask が false を返すタスクが保留キューに移されなかった(例:すべての前提タスクが完了する前にキューから取り出され、waitForDependencies の待機を開始した)
tryActivatePendingDependencyTask でタスクが CANCELEDPromise がキャンセルエラーで拒否される通知からアクティベーションまでの間に、別のスレッド/タスクが依存タスクをキャンセルした
グローバル condvar がシグナルされないワーカーが再エンキューされたタスクを取得しないエンキューとワーカーのスリープの間で競合が発生した

「起動しない」問題を診断する場合は、次の箇所にログを追加します。

  1. notifyDependencies:各依存タスクの taskId と、新しい taskDependenciesCount を記録する
  2. tryActivatePendingDependencyTask:優先度が見つかったか、タスクが CANCELED か、再エンキューに成功したかを記録する
  3. waitForDependencies:待機開始時と終了時のログを記録する(最後の taskDependenciesCount も記録する)
  4. moveToPendingDependencyQueue:タスクを保留キューに移したときと、スキップしたとき(依存関係がすでに解消されている場合)を記録する
Step 5: キャンセルの伝播を追跡する

タスクがキャンセルされた場合、その依存タスクもキャンセルされる必要があります。この経路を追跡します。

cancel(task) called
  → task state -> CANCELED (CAS)
  → tryCancelDependentTasks(task)
    → Iterates over InternalTask.of(task).dependentTasks
      → For each dependent:
        - calls cancel(dependent) (silently catches errors)
  → cleanupCanceledPendingDependencyTask(task)
    → Takes priority from pendingDependencyTasks
    → If found: finishCanceledPendingDependencyTask (rejects promises)

次の点を確認します。

  • 依存タスクがキャンセルされない:dependentTasks セットが空、またはエッジがすでに削除されている
  • 依存タスクが WAITING 状態のまま停止する:依存タスクがグローバルキューにあり、キャンセル時に削除されなかった(動的では RemoveDependTaskByTaskId、静的では tryCancelDependentTasks で処理されるはずです)
  • 二重キャンセルの競合:同じ依存関係チェーン内の 2 つのタスクが同時にキャンセルされる。状態遷移に対して cancel() が適切な CAS を行っているか確認する
Step 6: 診断レポートを作成する

次のセクションを含む構造化レポートを出力します。

依存関係グラフ
Nodes: [task1 (unexecuted), task2 (finished), task3 (submitted, isDependent=true)]
Edges:
  task3 → task2  (task3 depends on task2)
  task1 → task3  (task1 depends on task3)
見つかった問題

各問題について、次の情報を記載します。

  • 問題の種類:循環/不正なエッジ/エッジの欠落/通知の失敗/キャンセルの失敗
  • エラーコード(該当する場合)
  • 正確なソース位置(ファイル:行)
  • 影響を受けるエッジまたは経路
  • 根本原因の説明
推奨される修正

各問題について、次のいずれかを推奨します。

  • addDependency の呼び出し順を変更する:すべての依存関係を execute の前に宣言する
  • 問題のあるエッジを削除する:タスクタイプが変更される前に removeDependency を使用する
  • タスクを分割する:実行済みのタスクを再利用せず、新しい未実行のタスクを作成する
  • タスクタイプを置き換える:依存関係が必要な場合は、periodic/group/seqRunner/asyncRunner を使用しない
  • 同期を追加する:通知/アクティベーションで競合が発生している場合は、具体的な競合期間と修正方法を特定する(sleep/retry/timeout は提案しない)
  • 通知経路を修正する:notifyDependencies が呼び出されていない場合は、その理由を特定し、条件を修正する
リファクタリング後のコード例

ユーザーのコードに構造上の問題(例:execute の後に依存関係を追加する、自己参照サイクル、依存関係チェーン内でグループタスクを使用する)がある場合は、正しい処理順序を示す修正後のコード例を提示します。

主要な不変条件のチェックリスト

タスクプールの依存関係に関する問題を分析する際は、次の不変条件がすべて満たされていることを確認してください。違反がある場合は、それが根本原因または要因です。

  1. エッジの方向に関する不変条件:addDependency(taskA, taskB) は、taskA が taskB を待つことを意味します。エッジは taskB.dependentTasks に保存されます(逆マッピング)。dependentTasks をたどる場合、依存先から前提タスクへではなく、前提タスクから依存先へ進みます。

  2. taskDependenciesCount に関する不変条件:任意のタスク T について、T.taskDependenciesCount は T に入る前提タスクのエッジ数と一致している必要があります。taskDependenciesCount > 0 の場合、T は(グローバルキューから取り出されているなら)pendingDependencyTasks に、(送信されたばかりなら)グローバルキューに存在している必要があります。taskDependenciesCount == 0 の場合、T は pendingDependencyTasks に存在してはなりません。

  3. タスク型の制限に関する不変条件:addDependency に参加できるのは、未実行の TASK 型と FUNCTION_TASK のみです。Periodic、group、seqRunner、asyncRunner、および実行済み(COMMON_TASK)型は、どちらの方向でも禁止されています。

  4. 通知に関する不変条件:COMMON_TASK が(キャンセルされずに)終了した場合、notifyDependencies() を呼び出す必要があり、this.dependentTasks のすべてのエントリーを走査する必要があります。エントリーが欠けていると、依存先タスクの一部が起動しないままになります。

  5. キャンセル伝播に関する不変条件:タスクがキャンセルされた場合、その dependentTasks 内のすべてのタスクも再帰的にキャンセルする必要があります。依存先タスクがキャンセルされないと、キャンセルされた前提タスクを待ち続けて永久に停止するか、前提タスクの結果が欠けた状態で実行されます。

  6. 単一インスタンスに関する不変条件:waitForDependencies() は、依存関係が満たされた後に isRunning を確認します。同じタスクの別のインスタンスが実行中の場合、現在のインスタンスは待機します。notifyDependencies() は isRunning = false を設定し、condVar にシグナルを送って次のインスタンスのブロックを解除します。

  7. 保留キューの整合性:タスクは、グローバルキュー → pendingDependencyTasks(キューから取り出されたもののブロックされている場合)→ グローバルキュー(tryActivatePendingDependencyTask によって再キュー登録された場合)と移動します。グローバルキューと pendingDependencyTasks の両方に同時に存在してはなりません。execute() の開始時に呼び出される removePendingDependencyTask によって、クリーンアップが行われます。

注意すべきアンチパターン

これらのユーザーコードのパターンは、ほぼ確実にバグです。すぐに指摘してください。

  1. execute 後の addDependency:taskpool.execute(task) の後に task.addDependency(other) を呼び出すと、10200070 がスローされます
  2. 自己依存:task.addDependency(task) は 10200026 をスローします
  3. 依存関係チェーン内の周期タスク:periodicTask.addDependency(x) または x.addDependency(periodicTask) は 10200113 をスローします
  4. 依存関係チェーン内のグループタスク:グループタスクは依存関係を追加できず、依存先にもなれません。10200073/10200074 がスローされます
  5. 前提タスクからの通知漏れ:前提タスクが seqRunner/asyncRunner/group 経由で実行される場合、notifyDependencies が呼び出されないため、依存先タスクは永久に停止します
  6. 依存関係完了後の再実行:依存関係を持つタスクがすでに実行された後に taskpool.execute(task) を呼び出すと、10200072 がスローされます
  7. 誤ったタスクに対する removeDependency:存在しない依存関係を削除しようとすると、10200027 がスローされます
  8. 伝播を伴わないキャンセル:前提タスクをキャンセルしても依存先タスクは実行されると期待すること。依存先タスクもキャンセルされます

エラーコードのクイックリファレンス

コード定数意味一般的な修正方法
10200026CIRCULAR_DEPENDENCY依存関係グラフに循環があります循環を閉じるエッジを削除します
10200027DEPENDENCY_NOT_EXISTエッジが存在しない状態でのremoveDependency実際に追加されたエッジのみを削除します
10200070EXECUTED_TASK_ADD_DEPENDENCY実行済みタスクまたはランナータスクに対するaddDependency実行前に依存関係を宣言します
10200071EXECUTED_TASK_REMOVE_DEPENDENCY実行済みタスクに対するremoveDependency実行後に依存関係を変更しないでください
10200073GROUP_TASK_ADD_DEPENDENCYグループタスクによるaddDependencyの呼び出し依存関係チェーンからグループタスクを削除します
10200074GROUP_TASK_RELIED_ONグループタスクが依存先になっていますグループタスクではなく通常のタスクを使用します
10200079SEQ/ASYNC_RUNNER_TASK_RELIED_ONSeqRunner/AsyncRunnerが依存先になっています代わりに通常のタスクを使用します
10200097NO_DEPENDENCY依存関係のないタスクに対するremoveDependency先にaddDependencyが呼び出されたことを確認します
10200101NO_PARAMS引数なしでのaddDependency/removeDependencyタスクの引数を指定します
10200113PERIODIC_TASK_ADD_DEPENDENCY依存関係に周期タスクが含まれています依存関係チェーンから周期タスクを削除します

コメント