wolf: log bug-1980 (jobs submit stdin block) as fixed
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
parent
01f61507ba
commit
a437eff2c9
2 changed files with 117 additions and 0 deletions
|
|
@ -245,6 +245,32 @@
|
|||
"00f81c5a"
|
||||
],
|
||||
"record": ".wolf/bugs/bug-1979.md"
|
||||
},
|
||||
{
|
||||
"id": "bug-1980",
|
||||
"status": "fixed",
|
||||
"status_updated": "2026-10-30",
|
||||
"status_note": "Gated stdin-script read on len(args)==0; regression test + live t1 verification",
|
||||
"component": "xcecli",
|
||||
"severity": "hang",
|
||||
"timestamp": "2026-10-30T01:52:00Z",
|
||||
"last_seen": "2026-10-30T01:52:00Z",
|
||||
"occurrences": 1,
|
||||
"error_message": "jobs submit with explicit command blocks forever on a held-open stdin pipe\n\n**Observed:** a `xcecli jobs submit -q normal -R \"rusage[extlic=1]\" -- /tmp/e2e/licjob.sh\nsleep 120` process (pid 705795) sat in `unix_stream_data_wait` on fd 0 for the rest of\nthe session (found in the user's process list the next day). No RPC was ever sent; the\njob did not exist on the controller. A minimal repro confirms the hang is in the CLI,\nnot the caller:\n\n```\ntimeout 8 xcecli jobs submit -q normal -R \"rusage[extlic=1]\" -- /tmp/e2e/licjob.sh sleep 15 < <(sleep 30)\n# rc=124 (timeout kill), no output, no job created\n```\n\n**Expected:** `jobs submit` with an explicit command on the command line must submit\nregardless of what stdin is attached to (bsub semantics: stdin is the job script only\nwhen no command is given).\n\n**Impact:** any tooling or human invocation that leaves a pipe on submit's stdin\n(heredoc quoting accidents, script wrappers) hangs the CLI forever with no error and\nno job.\n\n**Found by:** the user spotted the hung process in their process list the day after\nthe esd-recover verification session (the session's own submit probe, pid 705795).",
|
||||
"file": "src/frontend/xcecli/cmd/jobs/submit.go",
|
||||
"root_cause": "`JobSubmitter.ParseCmdArgs` (src/frontend/xcecli/cmd/jobs/submit.go:105-114) gated on\n\"stdin is not a char device\" and then `io.ReadAll(os.Stdin)` **before** looking at the\ncommand args: any non-terminal stdin was treated as a potential job script\n(`parseConfigFile`, the `#xce job submit` script feature). A pipe held open without\ndata or EOF blocks `io.ReadAll` forever. The command args present in the same\ninvocation are never considered — a silent-open stdin wins over the explicit command,\nand had data arrived it would have been reinterpreted as a config script next to it.",
|
||||
"fix": "Gate the stdin-script read on `len(args) == 0`: stdin is consumed as a job script only\nwhen no command is given on the command line; an explicit command wins and stdin is\nnever read (`submit.go`, ParseCmdArgs). This matches the bsub semantics the CLI's help\ntext claims (`command args override config file`), removes the unbounded block, and\nremoves the data-ambiguity (stdin bytes silently merged with an explicit command).\n\nVerification:\n- Test: `TestParseCmdArgs_ExplicitCommandIgnoresHeldOpenStdin`\n (src/frontend/xcecli/cmd/jobs/submit_test.go) — a held-open pipe as os.Stdin plus\n explicit command args; RED pre-fix (test panics on the 30s timeout guard inside\n `io.ReadAll(os.Stdin)`, confirmed), GREEN post-fix (0.00s).\n- Full suite: `go test ./...` in src/frontend/xcecli — 11/11 packages ok.\n- Live (t1, fixed binary): `jobs submit ... -- licjob.sh sleep 15 < <(sleep 30)` →\n rc=0, Job submitted instantly, RUN→DONE, pool marker released (pre-fix rc=124).\n- Stdin-script path intact: `printf '#xce job submit -q normal\\nsleep 5\\n' |\n xcecli jobs submit` → submitted, DONE (parseConfigFile's os.Exit(0) path cannot be\n unit-tested in-process; covered live).\n- Suites: `go test ./...` in src/frontend/xcecli — 11/11 packages ok (cmd/jobs,\n cmd/users, cmd/clusters, cmd/hosts, common/format, common/output, common/request,\n filters, models, and the remaining two); apiserver/mcp modules untouched by the fix.",
|
||||
"tags": [
|
||||
"cli",
|
||||
"stdin",
|
||||
"jobs-submit",
|
||||
"external-license-resources"
|
||||
],
|
||||
"related_bugs": [],
|
||||
"fix_commits": [
|
||||
"01f61507"
|
||||
],
|
||||
"record": ".wolf/bugs/bug-1980.md"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
91
.wolf/bugs/bug-1980.md
Normal file
91
.wolf/bugs/bug-1980.md
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
---
|
||||
id: "bug-1980"
|
||||
status: "fixed"
|
||||
status_updated: "2026-10-30"
|
||||
status_note: "Gated stdin-script read on len(args)==0; regression test + live t1 verification"
|
||||
component: "xcecli"
|
||||
severity: "hang"
|
||||
timestamp: "2026-10-30T01:52:00Z"
|
||||
last_seen: "2026-10-30T01:52:00Z"
|
||||
occurrences: 1
|
||||
files: ["src/frontend/xcecli/cmd/jobs/submit.go"]
|
||||
tags: ["cli", "stdin", "jobs-submit", "external-license-resources"]
|
||||
related: []
|
||||
fix_commits: ["01f61507"]
|
||||
summary: "jobs submit with explicit command blocks forever on a held-open stdin pipe"
|
||||
---
|
||||
|
||||
# bug-1980: jobs submit with explicit command blocks forever on a held-open stdin pipe
|
||||
|
||||
## Description
|
||||
**Observed:** a `xcecli jobs submit -q normal -R "rusage[extlic=1]" -- /tmp/e2e/licjob.sh
|
||||
sleep 120` process (pid 705795) sat in `unix_stream_data_wait` on fd 0 for the rest of
|
||||
the session (found in the user's process list the next day). No RPC was ever sent; the
|
||||
job did not exist on the controller. A minimal repro confirms the hang is in the CLI,
|
||||
not the caller:
|
||||
|
||||
```
|
||||
timeout 8 xcecli jobs submit -q normal -R "rusage[extlic=1]" -- /tmp/e2e/licjob.sh sleep 15 < <(sleep 30)
|
||||
# rc=124 (timeout kill), no output, no job created
|
||||
```
|
||||
|
||||
**Expected:** `jobs submit` with an explicit command on the command line must submit
|
||||
regardless of what stdin is attached to (bsub semantics: stdin is the job script only
|
||||
when no command is given).
|
||||
|
||||
**Impact:** any tooling or human invocation that leaves a pipe on submit's stdin
|
||||
(heredoc quoting accidents, script wrappers) hangs the CLI forever with no error and
|
||||
no job.
|
||||
|
||||
**Found by:** the user spotted the hung process in their process list the day after
|
||||
the esd-recover verification session (the session's own submit probe, pid 705795).
|
||||
|
||||
## Environment
|
||||
- OS: Ubuntu 24.04.2 LTS; kernel Linux 6.8.0-147-generic x86_64
|
||||
- libc: glibc 2.39 (host ldd); xcecli is static Go (CGO_ENABLED=0, "not a dynamic executable")
|
||||
- Toolchain: go 1.23.x
|
||||
- Build: debug (src/build.dbg), deployed /opt/xce-denix/bin/xcecli
|
||||
- Code: working @ 7520cca7 (E2 external-resources branch line)
|
||||
- Deployment: t1 test cluster
|
||||
|
||||
## Reproduction
|
||||
1. `timeout 8 xcecli jobs submit -q normal -- sleep 15 < <(sleep 30)` (or any pipe that
|
||||
stays open without writing data or EOF)
|
||||
2. rc=124, process blocked in `unix_stream_data_wait` on the stdin socket
|
||||
- Frequency: deterministic
|
||||
- Evidence: /proc/705795/fd trace at discovery (2026-10-30 10:19 local); repro above
|
||||
|
||||
## Root cause
|
||||
`JobSubmitter.ParseCmdArgs` (src/frontend/xcecli/cmd/jobs/submit.go:105-114) gated on
|
||||
"stdin is not a char device" and then `io.ReadAll(os.Stdin)` **before** looking at the
|
||||
command args: any non-terminal stdin was treated as a potential job script
|
||||
(`parseConfigFile`, the `#xce job submit` script feature). A pipe held open without
|
||||
data or EOF blocks `io.ReadAll` forever. The command args present in the same
|
||||
invocation are never considered — a silent-open stdin wins over the explicit command,
|
||||
and had data arrived it would have been reinterpreted as a config script next to it.
|
||||
|
||||
## Fix
|
||||
Gate the stdin-script read on `len(args) == 0`: stdin is consumed as a job script only
|
||||
when no command is given on the command line; an explicit command wins and stdin is
|
||||
never read (`submit.go`, ParseCmdArgs). This matches the bsub semantics the CLI's help
|
||||
text claims (`command args override config file`), removes the unbounded block, and
|
||||
removes the data-ambiguity (stdin bytes silently merged with an explicit command).
|
||||
|
||||
## Verification
|
||||
- Test: `TestParseCmdArgs_ExplicitCommandIgnoresHeldOpenStdin`
|
||||
(src/frontend/xcecli/cmd/jobs/submit_test.go) — a held-open pipe as os.Stdin plus
|
||||
explicit command args; RED pre-fix (test panics on the 30s timeout guard inside
|
||||
`io.ReadAll(os.Stdin)`, confirmed), GREEN post-fix (0.00s).
|
||||
- Full suite: `go test ./...` in src/frontend/xcecli — 11/11 packages ok.
|
||||
- Live (t1, fixed binary): `jobs submit ... -- licjob.sh sleep 15 < <(sleep 30)` →
|
||||
rc=0, Job submitted instantly, RUN→DONE, pool marker released (pre-fix rc=124).
|
||||
- Stdin-script path intact: `printf '#xce job submit -q normal\nsleep 5\n' |
|
||||
xcecli jobs submit` → submitted, DONE (parseConfigFile's os.Exit(0) path cannot be
|
||||
unit-tested in-process; covered live).
|
||||
- Suites: `go test ./...` in src/frontend/xcecli — 11/11 packages ok (cmd/jobs,
|
||||
cmd/users, cmd/clusters, cmd/hosts, common/format, common/output, common/request,
|
||||
filters, models, and the remaining two); apiserver/mcp modules untouched by the fix.
|
||||
|
||||
## References
|
||||
- Found while verifying xceesd kill/restart recover for the external-resources
|
||||
manual (§11.5 scenario 4); the hung process was that session's own submit probe.
|
||||
Loading…
Add table
Add a link
Reference in a new issue