Deployment runner can deadlock on subprocess output and time out during OpenTofu apply #460
Labels
No labels
Compat/Breaking
Component/Enclave-Builder
Component/Infrastructure
Deploy/BYOC
Deploy/Full-managed
Deploy/Self-hosted
Interface/API
Interface/CLI
Interface/Git
Interface/Web
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority/Critical
Priority/High
Priority/Low
Priority/Medium
Reviewed/Confirmed
Reviewed/Duplicate
Reviewed/Invalid
Reviewed/Won't Fix
Status/Abandoned
Status/Blocked
Status/Need More Info
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
public/platform#460
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Platform's deployment process runner pipes stdout and stderr but does not read either stream until the child exits or is killed. A sufficiently verbose command can fill a pipe and block while the parent waits for it to exit, eventually producing a misleading deployment timeout.
Observed behavior
On dev on 2026-09-28, with Platform commit
bfe75a71f87e2b912fc6ed3fcf771789dee0517b, deploying a new disposable app usinggit push caution HEAD:mainproduced:tofu initin approximately 1.5 seconds.tofu applystarting at 17:20:41 UTC and timing out at 17:30:41 UTC.Nitro deployment failedresponse, followed by a failed app record with no public IP.The API log reports:
Captured stdout contains 9,804 bytes and ends during the printed plan, after the
aws_instance.enclaveblock. It contains no plan summary or resource-creation progress; stderr is empty. Automatic rollback subsequently reported success.The application did not reach a usable endpoint
Implementation defect
In
src/api/src/deployment.rs,run_with_timeout(lines 249-300 at the commit above):Stdio::piped().child.try_wait()without draining either pipe.child.wait_with_output()only after exit or timeout-triggered termination.If either pipe fills, the child can block writing output and never reach exit. Increasing the deadline does not resolve that condition. The helper is shared by multiple OpenTofu operations, so this is not specific to secret-enabled apps.
Reproduction evidence and confidence
A local diagnostic using the same wait-before-drain sequence launched a child writing 128 KiB to stdout. After one second, the child was still blocked; killing it yielded 65,536 bytes. A control that drained output while waiting completed successfully with all 131,072 bytes in approximately 0.028 seconds.
This reproduces the subprocess I/O defect. The alpha log is consistent with the same failure, but the terminated alpha process was not traced, so pipe backpressure remains the likely explanation for that particular deployment timeout rather than a directly observed process wait state.
Expected behavior / rewrite acceptance criteria
Related to #376 (
run_with_timeout is sync, should be async).These concern the same helper but document distinct failure modes:
I suggest keeping #460 linked to #376. One implementation change may resolve both, but making the runner async alone does not guarantee correct output handling. Regression coverage should include large stdout/stderr, simultaneous output, and a genuinely hung child.
The observed deployment timeout is consistent with pipe backpressure; it is not yet a confirmed diagnosis of that individual failure.