Skip to content

fix(task): prevent hang when skipped task has dependents#8937

Merged
jdx merged 2 commits intomainfrom
fix/task-sources-outputs-hang
Apr 9, 2026
Merged

fix(task): prevent hang when skipped task has dependents#8937
jdx merged 2 commits intomainfrom
fix/task-sources-outputs-hang

Conversation

@jdx
Copy link
Copy Markdown
Owner

@jdx jdx commented Apr 6, 2026

Summary

  • Fix race condition in emit_leaves() where a failed tx.send() on a dead channel leaves the task marked in self.sent, preventing it from being re-emitted on the next live channel
  • When a task with sources/outputs is skipped (up-to-date), its downstream dependents could hang indefinitely due to this race between subscribe() calls in pump_deps
  • Remove the key from self.sent when send() fails so the task can be re-emitted

Test plan

  • Existing e2e test test_task_run_sources passes
  • Manual loop test (50 iterations locally, 500 on Linux) passes
  • Reporter (@patrickhoefler) to confirm fix resolves their CI hang

Fixes #8932

🤖 Generated with Claude Code


Note

Medium Risk
Touches task scheduling/dedup logic in Deps::emit_leaves, which can affect execution order and potentially re-run tasks if misapplied. Change is small and guarded (only on tx.send failure), but it targets a hang/race scenario.

Overview
Fixes a dead-channel edge case in Deps::emit_leaves() by rolling back the sent marker when tx.send(Some(task)) fails.

This allows the same leaf task to be re-emitted on a subsequent subscribe()/new channel instead of remaining permanently “scheduled”, preventing dependent tasks from stalling when a prior receiver dropped.

Reviewed by Cursor Bugbot for commit bf1bd6f. Bugbot is set up for automated code reviews on this repo. Configure here.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 6, 2026

Greptile Summary

Fixes a race condition in Deps::emit_leaves() where a failed tx.send() left the task key in self.sent, preventing the task from being re-emitted after a new channel was created by the next subscribe() call. The minimal two-line patch (add .clone(), roll back self.sent on send failure) correctly addresses the hang in the skipped-task-with-dependents scenario described in #8932.

Confidence Score: 5/5

Safe to merge — minimal targeted fix for a confirmed race condition with no behavioral regressions.

The change is two lines: a .clone() to keep the key usable after HashSet::insert(), and a self.sent.remove(&key) rollback on send failure. The Deps object is mutex-protected so there are no new concurrency hazards. The rollback is only triggered when the channel is disconnected (receiver dropped), which is the exact scenario that caused the hang. All other code paths (successful send, is_empty completion signal, infinite-loop panic guard) are unaffected. Remaining findings are P2 at most.

No files require special attention.

Vulnerabilities

No security concerns identified.

Important Files Changed

Filename Overview
src/task/deps.rs emit_leaves() now rolls back self.sent on a failed send so tasks are re-emitted on the next subscribe(); .clone() added to keep key available for the removal.

Sequence Diagram

sequenceDiagram
    participant PD as pump_deps
    participant D as Deps
    participant CH as Channel

    PD->>D: subscribe() [call 1]
    D->>CH: create channel A (tx_A, rx_A)
    D->>D: emit_leaves() → send(task) via tx_A ✓
    PD->>CH: try_recv() drains initial leaves
    note over PD,CH: rx_A dropped at end of block

    note over CH: tx_A now disconnected

    D->>D: remove(skipped_task) → emit_leaves()
    D--xCH: send(dependent_task) via tx_A ✗ FAILS
    note over D: BEFORE FIX: key stays in self.sent<br/>AFTER FIX: key removed from self.sent

    PD->>D: subscribe() [call 2, in tokio::spawn]
    D->>CH: create channel B (tx_B, rx_B)
    D->>D: emit_leaves()
    note over D: BEFORE FIX: dependent_task in self.sent → skipped → HANG<br/>AFTER FIX: dependent_task not in self.sent → re-emitted ✓
    D->>CH: send(dependent_task) via tx_B ✓
    PD->>CH: recv() gets dependent_task → runs
Loading

Reviews (3): Last reviewed commit: "Merge branch 'main' into fix/task-source..." | Re-trigger Greptile

Comment thread docs/backend-plugin-development.md Outdated
Comment thread docs/url-replacements.md Outdated
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request fixes a task scheduling bug in src/task/deps.rs by ensuring task keys are removed from the tracking set if sending fails. It also includes documentation updates where GitHub alert blocks were incorrectly collapsed into single lines; feedback was provided to restore the original multi-line formatting for proper rendering.

Comment thread docs/backend-plugin-development.md Outdated

> [!WARNING]
> **Version sorting**: The versions returned by `BackendListVersions` should be in ascending order (oldest to newest), sorted semantically (version `3.10.0` should not come before `3.2.0`). Mise does not apply any additional sorting to the versions returned by this method.
> [!WARNING] > **Version sorting**: The versions returned by `BackendListVersions` should be in ascending order (oldest to newest), sorted semantically (version `3.10.0` should not come before `3.2.0`). Mise does not apply any additional sorting to the versions returned by this method.
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.

medium

The formatting of this warning block has been mangled, likely by an automated tool. This breaks the standard GitHub alert syntax and reduces readability. It should be restored to the multi-line format.

Suggested change
> [!WARNING] > **Version sorting**: The versions returned by `BackendListVersions` should be in ascending order (oldest to newest), sorted semantically (version `3.10.0` should not come before `3.2.0`). Mise does not apply any additional sorting to the versions returned by this method.
> [!WARNING]
> **Version sorting**: The versions returned by `BackendListVersions` should be in ascending order (oldest to newest), sorted semantically (version `3.10.0` should not come before `3.2.0`). Mise does not apply any additional sorting to the versions returned by this method.

Comment thread docs/url-replacements.md Outdated

> [!WARNING]
> **Credential Leaking**: When using `url_replacements`, any authentication headers (like `Authorization: Bearer <TOKEN>`) generated for the original URL (e.g., `api.github.com`) are **preserved** and sent to the replaced URL.
> [!WARNING] > **Credential Leaking**: When using `url_replacements`, any authentication headers (like `Authorization: Bearer <TOKEN>`) generated for the original URL (e.g., `api.github.com`) are **preserved** and sent to the replaced URL.
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.

medium

The formatting of this warning block has been mangled. Reverting to the multi-line format ensures correct rendering of the GitHub alert and improves readability.

Suggested change
> [!WARNING] > **Credential Leaking**: When using `url_replacements`, any authentication headers (like `Authorization: Bearer <TOKEN>`) generated for the original URL (e.g., `api.github.com`) are **preserved** and sent to the replaced URL.
> [!WARNING]
> **Credential Leaking**: When using `url_replacements`, any authentication headers (like `Authorization: Bearer <TOKEN>`) generated for the original URL (e.g., `api.github.com`) are **preserved** and sent to the replaced URL.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 6, 2026

Hyperfine Performance

mise x -- echo

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.7 x -- echo 26.8 ± 0.5 25.5 28.2 1.00
mise x -- echo 27.3 ± 0.5 25.5 28.8 1.02 ± 0.03

mise env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.7 env 26.2 ± 1.4 23.9 41.8 1.00
mise env 26.6 ± 0.9 25.3 37.2 1.02 ± 0.06

mise hook-env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.7 hook-env 26.9 ± 0.6 25.1 28.5 1.00
mise hook-env 27.3 ± 0.5 25.9 28.7 1.01 ± 0.03

mise ls

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.4.7 ls 24.2 ± 1.1 22.7 38.3 1.00
mise ls 24.5 ± 0.5 22.9 27.0 1.01 ± 0.05

xtasks/test/perf

Command mise-2026.4.7 mise Variance
install (cached) 159ms 159ms +0%
ls (cached) 84ms 84ms +0%
bin-paths (cached) 89ms 90ms -1%
task-ls (cached) 819ms 809ms +1%

@jdx jdx force-pushed the fix/task-sources-outputs-hang branch from 33d77ca to 6df0b8b Compare April 9, 2026 14:18
When emit_leaves() sends a task on a dead channel (from a previous
subscribe() call), the send fails but the task stays in self.sent,
preventing it from being re-emitted on the next live channel. This
causes downstream dependent tasks to never get scheduled, hanging
indefinitely.

Remove the key from self.sent when send() fails so the task can be
re-emitted on the next subscribe().

Fixes #8932

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@jdx jdx force-pushed the fix/task-sources-outputs-hang branch from 6df0b8b to 4079f1c Compare April 9, 2026 14:20
@jdx jdx enabled auto-merge (squash) April 9, 2026 14:20
@jdx jdx merged commit 6f3bdb3 into main Apr 9, 2026
35 checks passed
@jdx jdx deleted the fix/task-sources-outputs-hang branch April 9, 2026 14:37
mise-en-dev added a commit that referenced this pull request Apr 10, 2026
### 🚀 Features

- **(config)** add lockfile_platforms setting to restrict lockfile
platforms by @cameronbrill in
[#8966](#8966)
- **(sandbox)** support wildcard patterns in allow_env by @jdx in
[#8974](#8974)
- bump usage-lib v2 → v3 to render examples in task --help by @baby-joel
in [#8890](#8890)

### 🐛 Bug Fixes

- **(activate)** handle empty __MISE_FLAGS array with set -u on bash 3.2
by @jdx in [#8988](#8988)
- **(env)** add trace logging for module hook PATH diagnostics by @jdx
in [#8981](#8981)
- **(go)** Query module proxy directly for version resolution by @c22 in
[#8968](#8968)
- **(install)** render tera templates in tool postinstall hooks by @jdx
in [#8978](#8978)
- **(install)** add missing env vars to tool postinstall hooks by @jdx
in [#8977](#8977)
- **(task)** prevent hang when skipped task has dependents by @jdx in
[#8937](#8937)
- **(task)** invalidate dependent task sources when dependency runs by
@jdx in [#8975](#8975)
- **(task)** prevent deadlock when MISE_JOBS=1 with sub-task references
by @jdx in [#8976](#8976)
- **(task)** fetch remote task files before parsing usage specs by @jdx
in [#8979](#8979)
- **(task)** prevent panic when running parallel sub-tasks with
replacing output by @jdx in
[#8986](#8986)
- **(upgrade)** update lockfile and config when upgrading to specific
version by @jdx in [#8983](#8983)

### 📚 Documentation

- **(node)** remove "recommended for teams" from pin example by @jdx in
[b334363](b334363)

### 📦️ Dependency Updates

- update ghcr.io/jdx/mise:alpine docker digest to 17a29f2 by
@renovate[bot] in [#8995](#8995)
- update docker/dockerfile:1 docker digest to 2780b5c by @renovate[bot]
in [#8994](#8994)

### New Contributors

- @baby-joel made their first contribution in
[#8890](#8890)
- @cameronbrill made their first contribution in
[#8966](#8966)
- @c22 made their first contribution in
[#8968](#8968)
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.

1 participant