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'.
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'.
Taskpool Dependency Analysis Skill
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.
Why this skill exists
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.
Source code bindings
When using this skill, read these source files for the authoritative implementation:
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.
Step 2: Build the dependency graph from user code
From the user's code or test case, extract every Task creation, addDependency, removeDependency, and execute call in order. Build a directed graph:
Nodes: each Task instance (label with variable name + taskId if available)
Edges: each addDependency creates an edge dependent → prerequisite (the dependent waits for the prerequisite)
Edge deletions: each removeDependency removes that edge
Execution state: mark nodes as unexecuted, submitted, running, finished, canceled based on execute/cancel calls
If the graph has been modified by removeDependency, show the graph at each significant state change.
Step 3: Detect cycles and illegal edges
Run these checks on the graph:
3a: Cycle detection
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.
3b: Illegal task-type edges
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.
3c: Missing dependency edges
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.
Step 4: Trace the notification/activation path
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 taskDependenciesCount
tryActivatePendingDependencyTask: log whether priority was found, whether task is CANCELED, whether re-enqueue succeeded
waitForDependencies: 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)
Step 5: Trace cancel propagation
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:
Dependents not canceled: dependentTasks set was empty or the edge was already removed
Dependents stuck in WAITING state: They were in the global queue and cancel didn't remove them (this should be handled by RemoveDependTaskByTaskId in dynamic, tryCancelDependentTasks in static)
Double cancel race: Two tasks in the same dependency chain are canceled concurrently — check whether cancel() has proper CAS on state transition
Reorder addDependency calls: declare all dependencies before execute
Remove the problematic edge: use removeDependency before the task type changes
Split the task: create a new unexecuted task instead of reusing an executed one
Replace task type: don't use periodic/group/seqRunner/asyncRunner where dependencies are needed
Add synchronization: if the issue is a race in notification/activation, identify the specific race window and the fix (do not suggest sleep/retry/timeout)
Fix the notification path: if notifyDependencies is not being called, identify why and fix the condition
Refactored Code Sketch
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.
Key invariant checklist
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.
Anti-patterns to watch for
These patterns in user code are almost always bugs. Flag them immediately:
addDependency after execute: task.addDependency(other) after taskpool.execute(task) — throws 10200070
Periodic task in dependency chain: periodicTask.addDependency(x) or x.addDependency(periodicTask) — throws 10200113
Group task in dependency chain: group tasks cannot add or receive dependencies — throws 10200073/10200074
Missing prerequisite notification: if a prerequisite is executed via seqRunner/asyncRunner/group, it will never call notifyDependencies, so dependents hang forever
Re-execution after dependency completion: taskpool.execute(task) after the task already ran with dependencies — throws 10200072
removeDependency on wrong task: removing a dependency that doesn't exist — throws 10200027
Cancel without propagation: canceling a prerequisite but expecting dependents to still run — they will be canceled too