Conversation
The dokploy-server branch of runCommand opens a write stream for the deployment log and never closes it, so every scheduled run leaks one file descriptor for the lifetime of the process. A schedule running each minute reaches EMFILE in weeks: the panel stays up and listening but can no longer accept connections, and the logs fill with EMFILE on the schedule log path. The catch that surrounds the call cannot close the stream either, since the const is scoped inside the try. Wrapping the spawn in try/finally closes it on success, on a failing script, and on a throw from spawnAsync.
Comment on lines
+174
to
+176
| } finally { | ||
| writeStream.end(); | ||
| } |
Contributor
There was a problem hiding this comment.
Stream closes before callbacks
When an output chunk contains a PID, the async callback waits for updateDeployment before writing that chunk. However, spawnAsync does not wait for callback promises, so it can finish while the update is still pending. This finally block then ends the stream, and the callback can later write after end(), dropping log output and potentially emitting an unhandled ERR_STREAM_WRITE_AFTER_END. Wait for pending callbacks before closing the stream, or write the chunk before awaiting the update.
Author
There was a problem hiding this comment.
fixed in fd18384 — the chunk is now written before the pid update is awaited, so nothing can write after end()
spawnAsync ignores the callback's promise, so the run can finish while an update is still pending and the stream is closed underneath it. Writing first keeps the chunk and avoids a write after end.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
runCommandopens a write stream for the deployment log in thedokploy-serverbranch and never closes it, so every scheduled run leaks one file descriptor for the lifetime of the process.packages/server/src/utils/schedules/utils.ts(canary, L149):No
end(),close()ordestroy()on any path.fs.WriteStreamhas no GC finalizer, so the fd is held until the process exits. The outercatchcannot close it either — the stream is scoped to the branch.Impact
Three
dokploy-serverschedules on a one-minute cron, v0.26.3: ~3 fds/min. After roughly two months the panel hit EMFILE — the process stayed up and kept listening on 3000, but closed every connection without a reply (curl: (52) Empty reply from server), while the logs filled with:docker service lsreported1/1throughout.docker service update --forcecleared it.ls -l /proc/<pid>/fdshowed one open log file per run, several marked(deleted):removeLastTenDeploymentsunlinks older logs while the fds stay open, so disk usage stays flat and only the fd table grows.Reproduction
Same pattern (
createWriteStream+spawnAsync+unlinkof older logs), 10 iterations undernode:20.16.0-slim: 10 leaked fds, 5 of them(deleted). WithwriteStream.end()in afinally: none. An explicitglobal.gc()reclaims nothing.Fix
Wrap the spawn in
try/finallyand close the stream there — covers success, a failing script, and a throw fromspawnAsync. Three lines plus re-indentation; no change to the surrounding control flow.The PR is not yet safe to merge because a pending PID-update callback can write after the newly added stream closure.
Summary
This PR closes the deployment-log write stream after a
dokploy-serverscheduled command finishes, addressing the per-run file-descriptor leak.try/finally.Reviews (1) · Last reviewed commit: "fix(schedules): close log write stream a..."