mirror of
https://github.com/NVIDIA/Model-Optimizer.git
synced 2026-10-19 05:49:19 +08:00
## Summary - Add a `cancel` subcommand to the client that terminates the currently running command on the server - Server now runs commands in the background with PID tracking, enabling cancellation mid-execution - Client-side timeouts automatically cancel the running command on the server (previously the server process was left running) - Hardened against race conditions through 4 rounds of adversarial review (15 fixes total) ### Key changes **server.sh:** - Commands run in background with PID tracked in `$RELAY_DIR/running` (atomic tmp+mv write) - Cancel detection loop checks for `$RELAY_DIR/cancel` file with cmd_id verification - SIGTERM with 5s grace period, then SIGKILL escalation for stuck processes - `.exit` file written before `running` marker removed (ordering guarantee) - `set -e`-safe: `wait` uses `|| exit_code=$?` pattern; cleanup trap fully guarded - Stale cancel files cleared at command start; mismatched/empty signals rejected - Command file read into memory and removed before execution (eliminates TOCTOU with client timeout) **client.sh:** - New `cancel` subcommand: writes target cmd_id to cancel file, waits for server acknowledgment (30s timeout) - `run` timeout now sends targeted cancel signal (verifies cmd_id match to avoid killing wrong command) - `run` timeout cleans up orphaned result files - `status` shows currently running command - `flush` rejects if a command is currently running (prevents state corruption) - Exit code validated as numeric before use ### Protocol additions ``` .relay/ ├── running # server writes cmd_id:pid while executing (atomic) ├── cancel # client writes target cmd_id to request cancellation ``` ## Test plan - [ ] Start server in Docker, handshake from host - [ ] Run a command (`client.sh run "sleep 30"`), cancel it (`client.sh cancel`), verify exit code 130 - [ ] Run a command with short timeout (`--timeout 5 run "sleep 30"`), verify auto-cancel - [ ] Run a command that exits non-zero, verify server stays alive - [ ] Run `status` during execution, verify it shows the running command - [ ] Attempt `flush` during execution, verify it is rejected - [ ] Cancel when nothing is running, verify clean message 🤖 Generated with [Claude Code](https://claude.com/claude-code) ### Before your PR is "*Ready for review*" Make sure you read and follow [Contributor guidelines](https://github.com/NVIDIA/Model-Optimizer/blob/main/CONTRIBUTING.md) and your commits are signed (`git commit -s -S`). Make sure you read and follow the [Security Best Practices](https://github.com/NVIDIA/Model-Optimizer/blob/main/SECURITY.md#security-coding-practices-for-contributors) (e.g. avoiding hardcoded `trust_remote_code=True`, `torch.load(..., weights_only=False)`, `pickle`, etc.). - Is this change backward compatible?: ✅ - If you copied code from any other sources or added a new PIP dependency, did you follow guidance in `CONTRIBUTING.md`: N/A - Did you write any new necessary tests?: N/A (bash scripts for dev tooling, tested manually) - Did you update [Changelog](https://github.com/NVIDIA/Model-Optimizer/blob/main/CHANGELOG.rst)?: N/A (internal tooling) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Added a comprehensive debug skill guide and protocol reference with quick CLI examples and a new “Cancelling Commands” section. * **New Features** * Client-side `cancel` command to terminate the currently running remote command. * Status now reports active command (or `(idle)`). * **Improvements** * Stronger startup/validation guidance, safer shutdown/cleanup, deterministic cancel exit semantics (130), and auto-cancel on client-side timeout. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Chenjie Luo <chenjiel@nvidia.com>
4.4 KiB
4.4 KiB
File-Based Command Relay (Debugger)
A lightweight client/server system for running commands inside a Docker container from the host, using only a shared filesystem — no networking required.
Overview
Host (Claude Code) Docker Container
┌─────────────┐ ┌─────────────────┐
│ client.sh │ writes cmd file │ server.sh │
│ run "X" │ ───────────────────► │ detects cmd │
│ │ │ executes X │
│ reads │ writes result file │ writes result │
│ result │ ◄─────────────────── │ │
└─────────────┘ └─────────────────┘
└──── shared filesystem (.relay/) ────┘
Assumptions
- The ModelOpt repo is accessible from both host and container (e.g., bind-mounted)
- HuggingFace models are mounted at
/hf-local - The server auto-detects the repo root from the location of
server.sh
Quick Start
1. Start the server (inside Docker)
# The server auto-detects the repo root (two levels up from tools/debugger/)
bash /path/to/modelopt/tools/debugger/server.sh
The server automatically sets the working directory to the repo root. You can override with --workdir.
2. Connect from the host
bash tools/debugger/client.sh handshake
3. Run commands
# Run a simple command
bash tools/debugger/client.sh run "echo hello"
# Run a test script
bash tools/debugger/client.sh run "bash llm_ptq/scripts/huggingface_example.sh"
# Run with a long timeout (default is 600s)
bash tools/debugger/client.sh --timeout 1800 run "python my_long_test.py"
# Cancel a running command
bash tools/debugger/client.sh cancel
# Check status
bash tools/debugger/client.sh status
Protocol
The relay uses a directory at tools/debugger/.relay/ with this structure:
.relay/
├── server.ready # Written by server on startup
├── client.ready # Written by client during handshake
├── handshake.done # Written by server to confirm handshake
├── running # Written by server while a command is executing (cmd_id:pid)
├── cancel # Written by client to request cancellation of the running command
├── cmd/ # Client writes command .sh files here
│ └── <id>.sh # Command to execute
└── result/ # Server writes results here
├── <id>.log # stdout + stderr
└── <id>.exit # Exit code
Handshake
- Server starts, creates
.relay/server.ready - Client writes
.relay/client.ready - Server detects it, writes
.relay/handshake.done - Both sides are now connected
Command Execution
- Client writes a command to
.relay/cmd/<id>.sh - Server detects the file, reads the command content, and removes the
.shfile - Server runs
bash -c <content>in a new process group, writes.relay/running - Server writes
.relay/result/<id>.exitand.relay/result/<id>.log, then removes.relay/running - Client reads results and cleans up
Cancellation
- Client writes the target
cmd_idto.relay/cancel - Server verifies the
cmd_idmatches, then kills the command's process group - Server writes exit code 130 and removes
.relay/runningand.relay/cancel - Client-side timeout also triggers cancellation automatically
Options
Server
| Flag | Default | Description |
|---|---|---|
--relay-dir |
<script_dir>/.relay |
Relay directory path |
--workdir |
Auto-detected repo root | Working directory for commands |
Client
| Flag | Default | Description |
|---|---|---|
--relay-dir |
<script_dir>/.relay |
Relay directory path |
--timeout |
600 |
Seconds to wait for command result |
Notes
- The
.relay/directory is in.gitignore— it is not checked in. - Only one server should run at a time (startup clears the relay directory).
- Commands run sequentially in the order the server discovers them.
- A running command can be cancelled via
client.sh cancel. Cancelled commands exit with code 130. - Client-side timeouts automatically cancel the running command on the server.