Skip to content

feat(task): minimal interactive tty scheduler barrier#8464

Closed
mackwic wants to merge 1 commit intojdx:mainfrom
mackwic:feat/minimal-interactive-tty
Closed

feat(task): minimal interactive tty scheduler barrier#8464
mackwic wants to merge 1 commit intojdx:mainfrom
mackwic:feat/minimal-interactive-tty

Conversation

@mackwic
Copy link
Copy Markdown
Contributor

@mackwic mackwic commented Mar 4, 2026

Summary

This PR implements a minimal interactive TTY model in the existing DAG scheduler, with a small, runtime-focused diff.

What's changed

1) Interactive barrier in the scheduler (main change)

Interactive execution is enforced with semaphore policy in the existing scheduler:

  • Interactive root tasks acquire the full semaphore (max_permits) so no unrelated runtime task can run concurrently.
  • Injected descendants of an interactive task are tagged (__MISE_INTERACTIVE_CHAIN=1) and bypass permit acquisition, so they run under the parent barrier.
  • Mixed runtime+injected chains are handled without deadlock by tagging injected descendants of permit-holding parents (__MISE_PARENT_PERMIT_CHAIN=1) to bypass permit acquisition.
  • Permit acquisition is ordered to avoid racey start-order inversions under contention.
  • Runtime work still consumes scheduler slots (--jobs is respected).

Other changes (small)

  • Expose interactive in tasks info --json, tasks ls --json, MCP mise://tasks, and task schemas.
  • Restore terminal state after raw/interactive execution on Unix.
  • Minor internal cleanup around runtime-slot detection and permit acquisition.
  • Add e2e coverage for mixed parent+injected behavior under --jobs=1.

Validation

  • cargo fmt --all
  • cargo check -q
  • cargo build -q
  • ./e2e/run_test tasks/test_task_mixed_injected_respects_jobs1
  • ./e2e/run_test tasks/test_task_injected_runtime_jobs1_deadlock_regression
  • ./e2e/run_test tasks/test_task_interactive_mixed_external_barrier
  • ./e2e/run_test tasks/test_task_interactive_scheduler
  • ./e2e/run_test tasks/test_task_interactive_scheduler_extended
  • ./e2e/run_test tasks/test_task_interactive_pty_scheduler_matrix_slow
  • ./e2e/run_test tasks/test_task_info
  • ./e2e/run_test tasks/test_task_ls
  • ./e2e/run_test tasks/test_task_interactive_schema_api
  • ./e2e/run_test cli/test_mcp_tasks_interactive

Copilot AI review requested due to automatic review settings March 4, 2026 21:27
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a significant enhancement to the task scheduler by integrating an interactive TTY model. The core objective is to provide a robust mechanism for interactive tasks to run without interference from other concurrent processes, while also ensuring that injected sub-tasks correctly inherit and respect these interactive barriers. This change improves the reliability and predictability of task execution, particularly for user-facing interactive commands, and addresses potential deadlock scenarios in complex task dependencies.

Highlights

  • Interactive TTY Scheduler Barrier: Implemented a minimal interactive TTY model within the existing DAG scheduler, ensuring interactive tasks acquire a full semaphore to prevent concurrent unrelated runtime tasks. Injected descendants bypass permit acquisition, running under the parent's barrier, and mixed runtime+injected chains are handled to avoid deadlocks.
  • Task Schema and CLI Exposure: Added an interactive boolean property to task schemas and exposed this field in tasks info --json, tasks ls --json, and MCP mise://tasks output.
  • TTY State Restoration and Ctrl-C Handling: Ensured terminal state is restored after raw/interactive execution on Unix systems. Enhanced Ctrl-C handling to track running PIDs and allow for graceful interruption of tasks.
  • Improved Scheduler Logic: Refactored permit acquisition in the scheduler to support ordered acquisition, handle interactive task barriers, and prevent deadlocks in mixed task chains, especially under --jobs=1.
  • New E2E Tests: Added several end-to-end tests to validate the new interactive scheduler behavior, including scenarios for mixed parent/injected tasks, deadlock regressions, and interactive PTY usage.
Changelog
  • e2e/tasks/test_task_mixed_injected_respects_jobs1
    • Added a new end-to-end test to verify correct behavior of mixed parent and injected child tasks under --jobs=1 concurrency, specifically ensuring no deadlocks or overlaps occur.
  • schema/mise-task.json
    • Added a new interactive boolean property to the task schema, defaulting to false, to indicate tasks requiring direct TTY/stdin/stdout/stderr passthrough.
  • schema/mise.json
    • Added a new interactive boolean property to the main mise configuration schema for tasks, defaulting to false, to enable direct TTY/stdin/stdout/stderr passthrough.
  • src/cli/mcp.rs
    • Exposed the new interactive task property in the JSON output for the MCP mise://tasks endpoint.
  • src/cli/run.rs
    • Updated imports to include new task helper functions for interactive and parent permit chain detection.
    • Implemented permit_count_for_task to dynamically determine the number of semaphore permits a task requires based on its interactive status or if it's part of an injected chain.
    • Introduced acquire_permit_guard for ordered semaphore acquisition, handling potential failures during wait and ensuring proper permit release.
    • Modified spawn_sched_job to utilize the new permit_count_for_task and acquire_permit_guard logic for more granular and ordered concurrency control.
  • src/cli/tasks/info.rs
    • Exposed the new interactive task property in the JSON output for tasks info.
  • src/cli/tasks/ls.rs
    • Exposed the new interactive task property in the JSON output for tasks ls.
  • src/cmd.rs
    • Implemented logic to save and restore the TTY state on Unix systems before and after raw/interactive command execution, preventing terminal configuration issues.
    • Integrated tracking of running process IDs (RUNNING_PIDS) for interactive/raw commands to enable global Ctrl-C signal handling.
  • src/task/deps.rs
    • Added sorting to the leaves function to ensure deterministic tie-breaking for concurrently ready tasks in the dependency graph.
  • src/task/mod.rs
    • Added an interactive boolean field to the Task struct, defaulting to false.
    • Implemented has_runtime_script method to check if a task contains any direct script execution entries.
  • src/task/task_executor.rs
    • Updated imports to include new constants and helper functions related to interactive and parent permit chains.
    • Modified is_stopping to also check for Ctrl-C interruptions, ensuring task execution can halt promptly.
    • Updated the condition for task_timings to use the new has_runtime_script method.
    • Adjusted inject_and_wait calls to pass interactive_barrier and parent_holds_permit flags, propagating context to injected subgraphs.
    • Modified inject_and_wait to push INTERACTIVE_CHAIN_ENV and PARENT_PERMIT_CHAIN_ENV environment directives to injected tasks.
    • Enhanced run_task_sched to handle interactive tasks by ensuring stdin is a TTY and setting up direct stdin/stdout/stderr passthrough.
    • Added hints for users regarding redaction behavior when interactive or raw tasks are used.
    • Updated output handling for interactive tasks to inherit standard I/O streams directly.
  • src/task/task_helpers.rs
    • Introduced INTERACTIVE_CHAIN_ENV and PARENT_PERMIT_CHAIN_ENV constants for internal tracking of task chain properties.
    • Replaced task_needs_permit with task_uses_runtime_slot for more precise determination of tasks requiring scheduler permits.
    • Added task_is_interactive_chain, task_is_parent_permit_chain, and task_propagates_interactive_barrier functions to identify and manage interactive task chains and their permit inheritance.
  • src/task/task_output_handler.rs
    • Updated task_needs_permit to task_uses_runtime_slot when initializing tasks for KeepOrder output.
    • Modified Replacing output handling to gracefully manage injected tasks that may not be pre-registered in task_prs.
  • src/task/task_scheduler.rs
    • Added max_permits, permit_order_next, permit_order_cursor, and permit_order_notify fields to the Scheduler and SpawnContext structs to support ordered permit acquisition.
    • Initialized new permit ordering fields in the Scheduler::new and Scheduler::context methods.
    • Refactored the initial leaf forwarding logic to use a single receiver, preventing potential race conditions during task injection.
  • src/ui/ctrlc.rs
    • Added a INTERRUPTED atomic boolean to track if Ctrl-C has been pressed during the current mise run execution.
    • Implemented was_interrupted function to query the INTERRUPTED status.
  • src/ui/ctrlc_stub.rs
    • Added a stub implementation for was_interrupted for non-Unix environments, always returning false.
Activity
  • The author implemented a minimal interactive TTY model in the DAG scheduler, focusing on semaphore policy for interactive execution.
  • The author addressed edge cases for interactive tasks, mixed tasks with injected sub-tasks, and permit acquisition ordering.
  • Small changes were made to expose the interactive property in various CLI commands and schemas.
  • Terminal state restoration after raw/interactive execution on Unix was implemented.
  • Minor internal cleanup was performed around runtime-slot detection and permit acquisition.
  • Extensive end-to-end tests were added and validated to cover new interactive and mixed task behaviors, including deadlock prevention and scheduler functionality.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a minimal “interactive TTY” execution model into the existing DAG task scheduler, primarily by enforcing a runtime-wide concurrency barrier for interactive tasks while preserving --jobs semantics and avoiding deadlocks in mixed runtime/injected task chains.

Changes:

  • Add interactive barrier semantics to the scheduler via semaphore permit policies + deterministic permit acquisition ordering.
  • Propagate internal env markers across injected subgraphs to prevent deadlocks and to preserve interactive barriers.
  • Expose interactive across JSON/task schemas and related CLI/MCP APIs; improve TTY/raw handling (restore termios, Ctrl-C interruption detection) and add e2e coverage.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/ui/ctrlc_stub.rs Add was_interrupted() stub API for non-unix/tests.
src/ui/ctrlc.rs Track sticky Ctrl-C interruption state for mise run.
src/task/task_scheduler.rs Add permit ordering primitives + max permit tracking into scheduler context.
src/task/task_output_handler.rs Align “runtime slot” detection; avoid panic for injected tasks in replacing output mode.
src/task/task_helpers.rs Introduce runtime-slot helpers + injected-chain env markers for barrier/permit behavior.
src/task/task_executor.rs Propagate barrier/permit markers to injected tasks; interactive TTY passthrough and stopping-on-Ctrl-C.
src/task/mod.rs Add interactive task field + has_runtime_script() helper.
src/task/deps.rs Make leaf scheduling deterministic by sorting ready leaves.
src/cmd.rs Track raw-mode child PIDs; restore Unix terminal state after raw execution.
src/cli/tasks/ls.rs Include interactive in JSON output.
src/cli/tasks/info.rs Include interactive in JSON output.
src/cli/run.rs Implement permit-count policy + ordered permit acquisition to support interactive barrier and deadlock avoidance.
src/cli/mcp.rs Include interactive in MCP task payloads.
schema/mise.json Add interactive to task schema variants.
schema/mise-task.json Add interactive to task schema variants.
e2e/tasks/test_task_mixed_injected_respects_jobs1 Add e2e regression coverage for mixed parent+injected behavior under --jobs=1.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 4, 2026

Greptile Summary

This PR introduces a minimal interactive TTY scheduler barrier into mise's existing DAG task scheduler. Interactive tasks (interactive=true) now acquire all available semaphore permits (max_permits) to prevent concurrent unrelated tasks from running, while injected sub-tasks of an interactive parent bypass permit acquisition via env-marker propagation (__MISE_INTERACTIVE_CHAIN, __MISE_PARENT_PERMIT_CHAIN). A ticket-based ordering mechanism (permit_order_next / permit_order_cursor / Notify) is added to prevent start-order inversions under contention. The PR also adds: TTY state save/restore after raw/interactive execution, PID tracking in RUNNING_PIDS for interactive processes so Ctrl-C can kill them, a sticky INTERRUPTED flag to drive is_stopping() without requiring a task failure, a deps.rs sort for deterministic leaf ordering, and schema/JSON/MCP exposure of the interactive field.

Key findings:

  • Interactive barrier & ordering logic: The permit-acquisition mechanism and ticket-based ordering are correct and well-tested by the comprehensive e2e suite. The double-subscribe race fix in pump_deps/inject_and_wait correctly eliminates a completion-event hang.
  • TTY validation timing issue: The ensure!(stdin.is_terminal()) check in task_executor.rs fires inside the JoinSet task, after max_permits have been acquired. On misconfiguration (non-TTY stdin), all other queued work is briefly blocked while the error propagates. Moving this check earlier would improve latency on this error path.
  • Overall correctness: The core semaphore-barrier mechanism is logically sound, permit counting and env-marker propagation work correctly, and the refactored spawn-and-acquire pattern maintains proper ordering invariants.

Confidence Score: 4/5

  • Safe to merge; one minor logic issue flagged (TTY validation timing) that improves error-path performance but doesn't affect core functionality.
  • The interactive scheduler barrier mechanism is correct and well-tested. The permit-ordering protocol, env-marker propagation, and double-subscribe race fix are all sound. The single flagged issue (TTY check after semaphore acquisition) is a latency concern on misconfiguration, not a correctness bug. The happy path and primary interactive task execution are unaffected.
  • src/task/task_executor.rs (TTY validation placement)

Sequence Diagram

sequenceDiagram
    participant Sched as Scheduler Loop
    participant SpawnJob as spawn_sched_job
    participant AcqGuard as acquire_permit_guard
    participant Semaphore as Semaphore (max_permits)
    participant Executor as TaskExecutor
    participant InjectWait as inject_and_wait

    Sched->>SpawnJob: (interactive_task, deps)
    SpawnJob->>SpawnJob: permit_count = max_permits<br/>permit_order = fetch_add(order_next)
    SpawnJob->>SpawnJob: in_flight++
    SpawnJob->>AcqGuard: acquire_permit_guard(permit_count, permit_order)
    AcqGuard->>AcqGuard: wait until cursor == order
    AcqGuard->>Semaphore: acquire_many_owned(max_permits) ← blocks all other tasks
    Semaphore-->>AcqGuard: permit granted
    AcqGuard->>AcqGuard: cursor++, notify_waiters()
    AcqGuard-->>SpawnJob: (permit, should_run=true)
    SpawnJob->>Executor: run_task_sched(interactive_task)
    Executor->>Executor: ensure! stdin is_terminal()
    Executor->>Executor: stdin/stdout/stderr = Stdio::inherit()
    Executor->>InjectWait: inject_and_wait(injected_children,<br/>interactive_barrier=true, parent_holds_permit=false)
    InjectWait->>InjectWait: add __MISE_INTERACTIVE_CHAIN=1 to env
    InjectWait->>Sched: send injected child tasks (permit_count=0, bypass semaphore)
    Sched-->>InjectWait: children complete
    InjectWait-->>Executor: done
    Executor-->>SpawnJob: Ok(())
    SpawnJob->>Semaphore: drop permit (max_permits released)
    SpawnJob->>SpawnJob: in_flight--

    Note over Sched,Semaphore: Regular tasks queued after interactive task<br/>are blocked in ordering wait until permit released
Loading

Last reviewed commit: c49c340

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 introduces a minimal interactive TTY model into the existing DAG scheduler, significantly enhancing its capability to handle interactive tasks and complex task dependencies. The changes include a robust permit acquisition mechanism to prevent deadlocks, especially under --jobs=1 for mixed parent/injected child chains, and ensures proper TTY state restoration on Unix systems. The propagation of interactive barriers and parent permits to injected subtasks is well-implemented through environment variables, and the scheduler's responsiveness to Ctrl-C interruptions has been improved. The addition of a new e2e test for a specific concurrency scenario is a valuable contribution to the project's test suite.

@mackwic mackwic force-pushed the feat/minimal-interactive-tty branch 4 times, most recently from 9d720d0 to ad2c456 Compare March 4, 2026 22:22
@jdx
Copy link
Copy Markdown
Owner

jdx commented Mar 4, 2026

Thanks for iterating on this. The PR is still more complex than it needs to be though. The semaphore-draining approach (acquire_many_owned(max_permits)), ticket-lock ordering (permit_order_next/permit_order_cursor/Notify), and env var propagation (__MISE_INTERACTIVE_CHAIN/__MISE_PARENT_PERMIT_CHAIN) are all solving problems that wouldn't exist with the right primitive.

The goal is simple: only one interactive task runs at a time, and no other tasks run concurrently with it. This maps directly to a tokio::sync::RwLock<()>:

  • Normal tasks acquire a read lock (many can run concurrently, gated by the existing --jobs semaphore as today)
  • Interactive tasks acquire a write lock (exclusive — waits for all running tasks to finish, blocks new ones from starting)

Here's roughly what it should look like in spawn_sched_job:

// Add to SpawnContext:
pub interactive_lock: Arc<tokio::sync::RwLock<()>>,

// In spawn_sched_job, after permit acquisition:
let _interactive_guard = if task.interactive {
    Some(ctx.interactive_lock.write_owned().await)
} else {
    Some(ctx.interactive_lock.read_owned().await)  
};

That's it for the scheduler side. No ticket locks, no env var markers, no semaphore draining. The RwLock semantics naturally handle everything — concurrent readers, exclusive writer, proper ordering.

The parts of this PR that are fine and should be kept:

  • Adding interactive field to Task + schema + JSON output
  • Terminal state save/restore in cmd.rs
  • Forcing stdin/stdout/stderr=inherit for interactive tasks in the executor
  • TTY validation (ensure!(stdin.is_terminal()))

The parts that should be removed/replaced:

  • permit_order_next/permit_order_cursor/permit_order_notify (ticket lock)
  • __MISE_INTERACTIVE_CHAIN/__MISE_PARENT_PERMIT_CHAIN env vars
  • acquire_many_owned(max_permits) semaphore draining
  • permit_count_for_task / acquire_permit_guard
  • validate_injected_interactive_non_tty (50+ lines of graph walking)
  • All the helper functions in task_helpers.rs (task_is_interactive_chain, task_is_parent_permit_chain, task_propagates_interactive_barrier, task_uses_runtime_slot)

Could you simplify this down to the RwLock approach?

This comment was generated by Claude Code.

@mackwic
Copy link
Copy Markdown
Contributor Author

mackwic commented Mar 5, 2026

Thanks again for the review and discussion here.

I opened a new PR that supersedes this one: #8473.

I also want to clarify a misunderstanding on my side: I hadn’t understood that the intended RwLock scope was the finest task runtime segment (i.e. only around actual command execution), rather than the entire task lifecycle. The new PR follows that segment-level locking model.

This new PR keeps the implementation intentionally minimal and avoids the extra ticket/env-marker/semaphore-drain complexity discussed here.

Closing #8464 in favor of #8473.

@mackwic mackwic closed this Mar 5, 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.

3 participants