[Bug]: Missing cmd.Wait() call leads to zombie processes in local runtime #226

Closed
opened 2026-02-16 01:15:02 -05:00 by yindo · 1 comment
Owner

Originally created by @NieRonghua on GitHub (Dec 28, 2025).

Self Checks

To make sure we get to you in time, please check the following :)

  • I have searched for existing issues https://github.com/langgenius/dify-plugin-daemon/issues , including closed ones.
  • I confirm that I am using English to submit this report (我已阅读并同意 https://github.com/langgenius/dify/issues/1542 ).
  • [FOR CHINESE USERS] 请务必使用英文提交 Issue,否则会被关闭。谢谢!:)
  • "Please do not modify this template :) and fill in all the required fields."

Versions

  1. dify-plugin-daemon Version: 0.5.1
  2. dify-api Version: 1.11.1

Describe the bug

In internal/core/local_runtime/instance.go, specifically within the StartStdout method, the subprocess is killed using s.cmd.Process.Kill() when the stdout reader is closed (or when the loop terminates), but s.cmd.Wait() is never called afterward.

According to Go's os/exec documentation, Wait must be called to release the resources associated with the command. Omitting this call causes the child process to remain in the system process table as a zombie process (marked as <defunct>) after it terminates. This leads to resource leakage (PID exhaustion) over time, especially in environments where plugins are frequently restarted.

To Reproduce

Steps to reproduce the behavior:

  1. Launch dify-plugin-daemon in a container or local environment.
  2. Install and launch any local plugin (e.g., a standard Python-based plugin).
  3. Trigger a plugin restart, stop action, or allow a plugin to timeout/crash, which invokes the process kill logic in StartStdout.
  4. Inspect the process list inside the container/host using ps aux | grep python.
  5. See error: Observe multiple processes in Z (Zombie) state, marked as <defunct>.

Expected behavior

The daemon should properly reap child processes by calling Wait() immediately after killing them or when they exit naturally. This ensures that the system process table is cleaned up and no zombie processes are left behind.

Screenshots

root        107  0.6  0.0 147848 65896 ?        Sl   18:51   0:01 .../.venv/bin/python -m main 
root        149  0.3  0.0      0     0 ?        Z    18:51   0:00 [python] <defunct> 
root        181  0.7  0.0 150340 67276 ?        Sl   18:51   0:01 .../.venv/bin/python -m main 
root        219  0.0  0.0      0     0 ?        Z    18:52   0:00 [python] <defunct> 

Additional context

The issue is located in internal/core/local_runtime/instance.go.

Current implementation snippet (around line 165):

	// once reader of stdout is closed, kill subprocess
	if err := s.cmd.Process.Kill(); err != nil {
		s.WalkNotifiers(func(notifier PluginInstanceNotifier) {
			notifier.OnInstanceErrorLog(s, fmt.Errorf("failed to kill subprocess: %s", err.Error()))
		})
	}
    // Missing s.cmd.Wait() here

Proposed fix:

	if err := s.cmd.Process.Kill(); err != nil {
		// ... error handling
	}
    // Add this line to reap the process
    s.cmd.Wait()
Originally created by @NieRonghua on GitHub (Dec 28, 2025). **Self Checks** To make sure we get to you in time, please check the following :) - [x] I have searched for existing issues `https://github.com/langgenius/dify-plugin-daemon/issues` , including closed ones. - [x] I confirm that I am using English to submit this report (我已阅读并同意 `https://github.com/langgenius/dify/issues/1542` ). - [x] [FOR CHINESE USERS] 请务必使用英文提交 Issue,否则会被关闭。谢谢!:) - [x] "Please do not modify this template :) and fill in all the required fields." **Versions** 1. dify-plugin-daemon Version: 0.5.1 2. dify-api Version: 1.11.1 **Describe the bug** In `internal/core/local_runtime/instance.go`, specifically within the `StartStdout` method, the subprocess is killed using `s.cmd.Process.Kill()` when the stdout reader is closed (or when the loop terminates), but `s.cmd.Wait()` is never called afterward. According to Go's `os/exec` documentation, `Wait` must be called to release the resources associated with the command. Omitting this call causes the child process to remain in the system process table as a **zombie process** (marked as `<defunct>`) after it terminates. This leads to resource leakage (PID exhaustion) over time, especially in environments where plugins are frequently restarted. **To Reproduce** Steps to reproduce the behavior: 1. Launch `dify-plugin-daemon` in a container or local environment. 2. Install and launch any local plugin (e.g., a standard Python-based plugin). 3. Trigger a plugin restart, stop action, or allow a plugin to timeout/crash, which invokes the process kill logic in `StartStdout`. 4. Inspect the process list inside the container/host using `ps aux | grep python`. 5. See error: Observe multiple processes in `Z` (Zombie) state, marked as `<defunct>`. **Expected behavior** The daemon should properly reap child processes by calling `Wait()` immediately after killing them or when they exit naturally. This ensures that the system process table is cleaned up and no zombie processes are left behind. **Screenshots** ```bash root 107 0.6 0.0 147848 65896 ? Sl 18:51 0:01 .../.venv/bin/python -m main root 149 0.3 0.0 0 0 ? Z 18:51 0:00 [python] <defunct> root 181 0.7 0.0 150340 67276 ? Sl 18:51 0:01 .../.venv/bin/python -m main root 219 0.0 0.0 0 0 ? Z 18:52 0:00 [python] <defunct> ``` **Additional context** The issue is located in `internal/core/local_runtime/instance.go`. Current implementation snippet (around line 165): ```go // once reader of stdout is closed, kill subprocess if err := s.cmd.Process.Kill(); err != nil { s.WalkNotifiers(func(notifier PluginInstanceNotifier) { notifier.OnInstanceErrorLog(s, fmt.Errorf("failed to kill subprocess: %s", err.Error())) }) } // Missing s.cmd.Wait() here ``` Proposed fix: ```go if err := s.cmd.Process.Kill(); err != nil { // ... error handling } // Add this line to reap the process s.cmd.Wait() ```
yindo added the bug label 2026-02-16 01:15:02 -05:00
yindo closed this issue 2026-02-16 01:15:02 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Dec 28, 2025):

Your diagnosis is correct for dify-plugin-daemon v0.5.1: missing a call to cmd.Wait() after killing the subprocess in StartStdout can cause zombie processes. This has been fixed in a recent update—PR #544 (merged 2025-12-19) adds s.cmd.Process.Wait() after the kill, with comments and error handling to ensure proper process cleanup and avoid zombies. You can see the updated logic and comments in the current codebase, which now explicitly reaps the subprocess after killing it, as recommended by Go's os/exec documentation. If you're on v0.5.1, upgrading to a version including this fix or manually applying the patch will resolve the issue and prevent PID/resource leaks in plugin restarts or crashes.
See the relevant code and PR

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Open in Cursor Join Discord Share on X

@dosubot[bot] commented on GitHub (Dec 28, 2025): <!-- Answer --> Your diagnosis is correct for dify-plugin-daemon v0.5.1: missing a call to cmd.Wait() after killing the subprocess in StartStdout can cause zombie processes. This has been fixed in a recent update—PR #544 (merged 2025-12-19) adds s.cmd.Process.Wait() after the kill, with comments and error handling to ensure proper process cleanup and avoid zombies. You can see the updated logic and comments in the current codebase, which now explicitly reaps the subprocess after killing it, as recommended by Go's os/exec documentation. If you're on v0.5.1, upgrading to a version including this fix or manually applying the patch will resolve the issue and prevent PID/resource leaks in plugin restarts or crashes. [See the relevant code and PR](https://github.com/langgenius/dify-plugin-daemon/pull/544) <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/f73f7236-8ef4-44d6-973e-853c629e7b48?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/f73f7236-8ef4-44d6-973e-853c629e7b48?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/f73f7236-8ef4-44d6-973e-853c629e7b48?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/f73f7236-8ef4-44d6-973e-853c629e7b48?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/f73f7236-8ef4-44d6-973e-853c629e7b48?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/f73f7236-8ef4-44d6-973e-853c629e7b48?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/f73f7236-8ef4-44d6-973e-853c629e7b48?feedback_type=other)</sup> [![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=Your%20diagnosis%20is%20correct%20for%20dify-plugin-daemon%20v0.5.1%3A%20missing%20a%20call%20to%20cmd.Wait%28%29%20after%20killing%20the%20subprocess%20in%20StartStdout%20can%20cause%20zombie%20processes.%20This%20has%20been%20fixed%20in%20a%20recent%20update%E2%80%94PR%20%23544%20%28merged%202025-12-19%29%20adds%20s.cmd.Process.Wait%28%29%20after%20the%20kill%2C%20with%20comments%20and%20error%20handling%20to%20ensure%20proper%20process%20cleanup%20and%20avoid%20zombies.%20You%20can%20see%20the%20updated%20logic%20and%20comments%20in%20the%20current%20codebase%2C%20which%20now%20explicitly%20reaps%20the%20subprocess%20after%20killing%20it%2C%20as%20recommended%20by%20Go%27s%20os/exec%20documentation.%20If%20you%27re%20on%20v0.5.1%2C%20upgrading%20to%20a%20version%20including%20this%20fix%20or%20manually%20applying%20the%20patch%20will%20resolve%20the%20issue%20and%20prevent%20PID/resource%20leaks%20in%20plugin%20restarts%20or%20crashes.%20%20%0A%5BSee%20the%20relevant%20code%20and%20PR%5D%28https%3A//github.com/langgenius/dify-plugin-daemon/pull/544%29)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/langgenius/dify-plugin-daemon/issues/553)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langgenius/dify-plugin-daemon#226