skills/oh-arkruntime-taskpool-dependency-analysis/SKILL.md
Analyze, diagnose, and fix taskpool task dependency problems in static ArkTS — including circular dependency (10200026), missing dependency (10200027), dependent tasks not waking after dependency completion, cancel propagation through dependency chains, and illegal addDependency/removeDependency on periodic/group/seqRunner/asyncRunner/executed tasks. Use this skill whenever the user mentions taskpool dependencies, addDependency, removeDependency, circular dependency, dependency graph, task not executing after dependencies complete, cancel propagation, or any taskpool error codes 10200026, 10200027, 10200070, 10200071, 10200073, 10200074, 10200079, 10200097, 10200101, 10200113 — even if they don't explicitly say 'dependency' or 'taskpool'.
npx skillsauth add openharmonyinsight/openharmony-skills taskpool-dependency-analysisInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
This skill helps you systematically diagnose and fix taskpool dependency problems in the static ArkTS runtime. It walks you through building a dependency graph from user code, detecting cycles, identifying illegal API call sequences, tracing notification/activation paths, and recommending fixes.
Taskpool dependencies are not "just deadlock detection" — they are a bespoke task dependency model with its own state machine, notification mechanism, task-type restrictions, and cancel propagation rules. A task that depends on another task blocks via waitForDependencies() until its taskDependenciesCount reaches zero; when a prerequisite task finishes, it calls notifyDependencies() which decrements counters and re-enqueues ready dependents via tryActivatePendingDependencyTask. If any step in this chain breaks, dependent tasks silently hang or never execute, and the root cause is often not where the symptom appears.
When using this skill, read these source files for the authoritative implementation:
Static stdlib: runtime_core/static_core/plugins/ets/stdlib/std/concurrency/taskpool.ets
Task.addDependency (line ~1238): validates task types, checks circular dependency via hasTaskDFS, builds edge in dependentTasks setTask.removeDependency (line ~1284): validates state, removes edge, adjusts taskDependenciesCountTask.hasTaskDFS (line ~1371): DFS cycle detection traversing dependentTasksInternalTask.waitForDependencies (line ~718): blocks on taskMutex/condVar until taskDependenciesCount == 0InternalTask.notifyDependencies (line ~731): decrements each dependent's count, signals condvar, activates ready taskstryActivatePendingDependencyTask (line ~3014): re-enqueues a task whose dependencies are all satisfiedmoveToPendingDependencyQueue (line ~2993): stores task+priority in pending map when dequeued but still blockedDynamic reference: commonlibrary/ets_utils/js_concurrent_module/taskpool/task.cpp and task_manager.cpp
Task::AddDependency, Task::RemoveDependency, Task::CheckAddDependencyTaskManager::StoreTaskDependency, TaskManager::CheckCircularDependency, TaskManager::NotifyDependencyTaskInfo, TaskManager::RemoveTaskDependency, TaskManager::ClearDependentTaskTest reference: runtime_core/static_core/plugins/ets/tests/ets-common-tests/taskpool/common_tasks.ets
dependentTasksTest, circularDependencyTest, dependencyExecutedTaskTest, dependencyCancelTaskTest, doubleCancelByDependencyTestFor detailed reference on data structures, error codes, and the full lifecycle, read references/dependency_reference.md in this skill's directory.
Follow these steps in order. Do not skip steps or merge them.
Read the user's description or the failing test case and classify into one of these categories:
| Category | Typical symptoms | Error codes |
|----------|-----------------|-------------|
| Circular dependency | addDependency throws immediately | 10200026 |
| Missing/nonexistent dependency | removeDependency throws | 10200027, 10200097 |
| Dependent task never wakes | Task submitted but never executes; hangs silently | None (no error thrown) |
| Cancel propagation failure | Canceling a prerequisite doesn't cancel dependents, or vice versa | None |
| Illegal API sequence | addDependency/removeDependency on wrong task type | 10200070, 10200071, 10200073, 10200074, 10200079, 10200113 |
| Re-execution after dependency | Re-executing a task that already ran with dependencies | 10200072 |
| Timeout + dependency conflict | execute(task, {timeout}) on task with dependency | Custom error |
If the user provides a test case (.ets file), read it first and extract: task creation order, addDependency calls, execute calls, and expected vs actual behavior.
From the user's code or test case, extract every Task creation, addDependency, removeDependency, and execute call in order. Build a directed graph:
Task instance (label with variable name + taskId if available)addDependency creates an edge dependent → prerequisite (the dependent waits for the prerequisite)removeDependency removes that edgeunexecuted, submitted, running, finished, canceled based on execute/cancel callsRepresent the graph as text, e.g.:
task1 ──depends on──> task2 ──depends on──> task3
task4 ──depends on──> task2
If the graph has been modified by removeDependency, show the graph at each significant state change.
Run these checks on the graph:
Use DFS from each node. If any path returns to the starting node, it's a cycle. Report the exact cycle path:
Cycle detected: task1 → task2 → task3 → task1
Introduced by: task3.addDependency(task1) at line X
The static implementation uses hasTaskDFS which traverses the dependentTasks (reverse direction: from prerequisite towards its dependents). Make sure your cycle detection matches this traversal direction.
Check each node against these rules (these are the exact checks in addDependency and removeDependency):
| Task type | Can call addDependency? | Can be a dependency target? | Can call removeDependency? | |-----------|------------------------|----------------------------|---------------------------| | Unexecuted (TASK) | Yes | Yes | Yes (if isDependent) | | Executed (COMMON_TASK, after execute) | No (10200070) | No (10200079) | No (10200071) | | Periodic task | No (10200113) | No (10200113) | No (10200113) | | Group task | No (10200073) | No (10200074) | N/A | | SeqRunner task | No (10200070) | No (10200079) | No (10200071) | | AsyncRunner task | No (10200070) | No (10200079) | No (10200071) | | Timeout task | No | No | N/A |
Report any violations with the specific error code.
For removeDependency errors: verify that the edge actually exists in the graph at the time of removal. If the user code calls removeDependency(taskX) but taskX is not a prerequisite of the caller, that's error 10200027.
This is the most important step for "dependent task never wakes" problems. Trace the full path from when a prerequisite task finishes to when the dependent task should execute:
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
Check each step for potential failure:
| Failure point | Symptom | Root cause pattern |
|---------------|---------|--------------------|
| notifyDependencies not called | Dependent never wakes | Task type is not COMMON_TASK (dynamic: TriggerTask only calls NotifyDependencyTaskInfo for common tasks); or task was canceled (isCancel=true skips notification) |
| dependentTasks set is empty | No dependents notified | Edge was removed before execution, or addDependency was called after execute |
| taskDependenciesCount doesn't reach 0 | Dependent wakes but re-waits | Some prerequisite finished without decrementing this dependent's count (race: count was incremented after the notification ran) |
| pendingDependencyTasks has no entry for the dependent | tryActivatePendingDependencyTask returns false | Task was never moved to pending queue (e.g., it was dequeued and started waitForDependencies before all prerequisites finished) |
| Task is CANCELED in tryActivatePendingDependencyTask | Promise rejected with cancel error | Another thread/task canceled the dependent between notification and activation |
| Global condvar not signaled | Worker doesn't pick up re-enqueued task | Race between enqueue and worker sleep |
When diagnosing "never wakes" issues, add logging at these points:
notifyDependencies: log each dependent task's taskId and the new taskDependenciesCounttryActivatePendingDependencyTask: log whether priority was found, whether task is CANCELED, whether re-enqueue succeededwaitForDependencies: log when entering wait and when exiting (with final taskDependenciesCount)moveToPendingDependencyQueue: log when a task is moved to pending vs when it's skipped (dependencies already cleared)When a task is canceled, its dependents should also be canceled. Trace this path:
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)
Check for:
dependentTasks set was empty or the edge was already removedRemoveDependTaskByTaskId in dynamic, tryCancelDependentTasks in static)cancel() has proper CAS on state transitionOutput a structured report with these sections:
Nodes: [task1 (unexecuted), task2 (finished), task3 (submitted, isDependent=true)]
Edges:
task3 → task2 (task3 depends on task2)
task1 → task3 (task1 depends on task3)
For each issue, provide:
For each issue, recommend one of:
If the user's code has structural issues (e.g., adding dependencies after execute, self-referencing cycles, group tasks in dependency chains), provide a sketch of the corrected code showing the proper order of operations.
When analyzing any taskpool dependency problem, verify these invariants hold. If any is violated, that's your root cause or contributing factor:
Edge direction invariant: addDependency(taskA, taskB) means taskA waits for taskB. The edge is stored in taskB.dependentTasks (reverse mapping). If you traverse dependentTasks, you're going from prerequisite towards dependents, not from dependents towards prerequisites.
taskDependenciesCount invariant: For any task T, T.taskDependenciesCount must equal the number of prerequisite edges pointing into T. If taskDependenciesCount > 0, T must be in pendingDependencyTasks (if dequeued from global queue) or in the global queue (if just submitted). If taskDependenciesCount == 0, T must NOT be in pendingDependencyTasks.
Task type restriction invariant: Only unexecuted TASK type and FUNCTION_TASK can participate in addDependency. Periodic, group, seqRunner, asyncRunner, and executed (COMMON_TASK) types are prohibited in both directions.
Notification invariant: When a COMMON_TASK finishes (not canceled), notifyDependencies() must be called, and it must traverse ALL entries in this.dependentTasks. If any entry is missing, some dependent will never wake.
Cancel propagation invariant: When a task is canceled, ALL tasks in its dependentTasks must also be canceled (recursively). If any dependent is not canceled, it will either hang forever (waiting for a canceled prerequisite) or execute with missing prerequisite results.
Single-instance invariant: waitForDependencies() checks isRunning after dependencies are satisfied. If another instance of the same task is running, the current instance waits. notifyDependencies() sets isRunning = false and signals condVar to unblock the next instance.
Pending-queue consistency: A task moves from global queue → pendingDependencyTasks (when dequeued but blocked) → global queue (when re-enqueued by tryActivatePendingDependencyTask). It must not be in both the global queue and pendingDependencyTasks simultaneously. The removePendingDependencyTask call at the start of execute() ensures cleanup.
These patterns in user code are almost always bugs. Flag them immediately:
task.addDependency(other) after taskpool.execute(task) — throws 10200070task.addDependency(task) — throws 10200026periodicTask.addDependency(x) or x.addDependency(periodicTask) — throws 10200113notifyDependencies, so dependents hang forevertaskpool.execute(task) after the task already ran with dependencies — throws 10200072| Code | Constant | Meaning | Typical fix | |------|----------|---------|-------------| | 10200026 | CIRCULAR_DEPENDENCY | Cycle in dependency graph | Remove the edge that closes the cycle | | 10200027 | DEPENDENCY_NOT_EXIST | removeDependency on non-edge | Only remove edges that were actually added | | 10200070 | EXECUTED_TASK_ADD_DEPENDENCY | addDependency on executed/runner task | Declare dependencies before execute | | 10200071 | EXECUTED_TASK_REMOVE_DEPENDENCY | removeDependency on executed task | Don't modify dependencies after execution | | 10200073 | GROUP_TASK_ADD_DEPENDENCY | Group task calling addDependency | Remove group task from dependency chain | | 10200074 | GROUP_TASK_RELIED_ON | Group task as dependency target | Use regular task instead of group task | | 10200079 | SEQ/ASYNC_RUNNER_TASK_RELIED_ON | SeqRunner/AsyncRunner as dependency target | Use regular task instead | | 10200097 | NO_DEPENDENCY | removeDependency on task with no deps | Ensure addDependency was called first | | 10200101 | NO_PARAMS | addDependency/removeDependency with no args | Provide task arguments | | 10200113 | PERIODIC_TASK_ADD_DEPENDENCY | Periodic task in dependency | Remove periodic task from dependency chain |
development
Run local code quality checks covering a subset of OpenHarmony gate CI (copyright, CodeArts C/C++) plus additional local checks (pylint/flake8, shellcheck/bashate, gn format). Use before committing to reduce gate failures. Triggers on: /oh-precommit-codecheck, "门禁检查", "门禁预检", "检查代码", "run codecheck", "check code quality", "lint my code", "代码检查", or after completing code implementation. WHEN to use: before git commit, before creating PR, after modifying C/C++/Python/Shell/GN files, when gate CI fails with codecheck defects, or when you want to preview what gate will flag.
development
OpenHarmony PR full lifecycle workflow. Five modes: - Commit: standardized commit with DCO sign-off and Issue linking - Create PR: commit + push to fork + create Issue + create PR on upstream - Fix Codecheck: fetch gate CI codecheck defects from a PR and auto-fix them - Review PR: fetch a PR's changes to local for code review - Fix Review: fetch unresolved review comments from a PR and auto-fix them Triggers on: /oh-pr-workflow, "提交代码", "创建PR", "提个PR", "commit", "修复告警", "修复门禁", "修复codecheck", "fix codecheck", "review pr", "review这个pr", "看下这个pr", "检视pr", "修复review", "修复检视意见", "fix review", or a GitCode PR URL with fix/review intent.
testing
分析 HM Desktop PRD 文档,提取需求信息、验证完整性、检查章节顺序(需求来源→需求背景→需求价值分析→竞品分析→需求描述)、检查 KEP 定义、检测需求冲突并生成结构化分析报告。适用于用户请求:(1) 分析或审查 PRD 文档, (2) 从需求中提取 KEP 列表, (3) 检查 PRD 完整性或一致性, (4) 将需求映射到模块架构, (5) 验证 PRD 格式合规性, (6) 验证竞品分析章节完整性。关键词:PRD分析, requirement extraction, KEP验证, completeness check, chapter order validation, 竞品分析检查, analyze PRD, 需求提取, 完整性检查, 章节顺序验证
development
基于 PRD 文档自动生成鸿蒙系统设计文档,包括架构设计文档和功能设计文档。生成前会分析 OpenHarmony 存量代码结构,确保与现有架构兼容。架构设计文档第2章必须为竞品方案分析,位于需求背景之后。适用于用户请求:(1) 生成架构设计文档, (2) 生成功能设计文档, (3) 从 PRD 生成设计文档, (4) 创建系统架构设计, (5) 编写功能规格说明, (6) 分析 OH 代码结构。关键词:architecture design, functional design, design doc, 竞品方案分析, OpenHarmony code analysis, 架构设计, 功能设计, 设计文档生成, OH代码分析, analyze codebase, competitor analysis