Skip to content

fix(control): arm streamed path before task is active#3159

Open
shahtvisha wants to merge 1 commit into
dimensionalOS:mainfrom
shahtvisha:fix-holonomic-follower-arming-deadlock
Open

fix(control): arm streamed path before task is active#3159
shahtvisha wants to merge 1 commit into
dimensionalOS:mainfrom
shahtvisha:fix-holonomic-follower-arming-deadlock

Conversation

@shahtvisha

@shahtvisha shahtvisha commented Jul 24, 2026

Copy link
Copy Markdown

Summary

HolonomicPoseFollowerTask never starts driving a path that arrives via the path stream card (on_path). The robot receives the path and logs it, but doesn't move.

Root cause

compute() is the only thing that arms a pending path: on_path() just latches it (self._pending_path = msg), and compute() arms it on the next tick that has a pose, by calling start_path().

But the tick loop only calls compute() on tasks where is_active() is already True:

# tick_loop.py, _compute_all_tasks
for task in self._tasks.values():
    if not task.is_active():
        continue
    ...
    output = task.compute(state)

And is_active() only checked states that start_path() itself sets:

def is_active(self) -> bool:
    return self._state in ("tracking", "settling", "stopping")

The task starts in "idle", so is_active() is False until compute() runs, but compute() never runs until is_active() is True. It's a permanent deadlock with no error, since nothing throws; the task just never starts.

Fix

is_active() also returns True while a path is pending arm, so the tick loop lets compute() run once to arm it. After that, the normal state-based check takes over.

Test plan

  • Ran 50 runs on the Go2 (10 paths x 5 speeds) with this fix applied — path arms and the robot tracks correctly.

@greptile-apps

greptile-apps Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR allows streamed paths to activate an idle holonomic pose follower.

  • Extends is_active to include a latched pending path.
  • Lets the tick loop call compute so that the pending path can be armed once pose state is available.

Confidence Score: 3/5

The PR should not merge until cancellation and preemption reliably discard pending paths instead of allowing them to start later.

A pending path now independently keeps the task schedulable, while cancellation and preemption only change the state; compute can therefore arm and execute work that was already cancelled, or leave the task active indefinitely without pose data.

dimos/control/tasks/holonomic_pose_follower_task/holonomic_pose_follower_task.py

Important Files Changed

Filename Overview
dimos/control/tasks/holonomic_pose_follower_task/holonomic_pose_follower_task.py The activation fix resolves initial streamed-path scheduling but leaves pending paths live across cancellation and preemption.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  P[Path stream] --> L[Latch pending path]
  L --> A[is_active returns true]
  A --> C[Tick loop calls compute]
  C --> Q{Pose available?}
  Q -->|Yes| S[start_path]
  Q -->|No| W[Keep path pending]
  W --> A
  X[Cancel or preempt] --> B[Set state aborted]
  B --> A
Loading

Reviews (1): Last reviewed commit: "fix(control): arm streamed path before t..." | Re-trigger Greptile

@@ -150,7 +150,7 @@ def claim(self) -> ResourceClaim:
)

def is_active(self) -> bool:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Cancellation leaves pending path active

When a path is cancelled or preempted before pose data is available, _pending_path remains set, so this condition keeps scheduling the task and later arms the cancelled path once pose data arrives, causing the robot to execute work that was cancelled. Without pose data, the task remains active indefinitely and can block removal of hardware whose joints it claims.

Knowledge Base Used: Control Coordinator and Control Tasks

@codecov

codecov Bot commented Jul 24, 2026

Copy link
Copy Markdown

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
2948 1 2947 175
View the top 1 failed test(s) by shortest run time
dimos.control.tasks.holonomic_pose_follower_task.test_holonomic_pose_follower_task::test_streamed_path_arms_on_the_first_tick_with_a_pose
Stack Traces | 0.025s run time
def test_streamed_path_arms_on_the_first_tick_with_a_pose():
        """The card handler carries no odom, so a streamed path is latched and armed
        on the first tick that has a pose — never dropped for arriving early."""
        task = _task()
        task.on_path(straight_rotate(), t_now=0.0)
>       assert not task.is_active()  # latched, not armed
E       assert not True
E        +  where True = is_active()
E        +    where is_active = <dimos.control.tasks.holonomic_pose_follower_task.holonomic_pose_follower_task.HolonomicPoseFollowerTask object at 0xff39206157c0>.is_active

task       = <dimos.control.tasks.holonomic_pose_follower_task.holonomic_pose_follower_task.HolonomicPoseFollowerTask object at 0xff39206157c0>

.../tasks/holonomic_pose_follower_task/test_holonomic_pose_follower_task.py:145: AssertionError

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

@TomCC7 TomCC7 self-assigned this Jul 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants