install plugin in cluster env, InitPythonEnvironment will delete .venv dir causing race conditions and failures #221

Closed
opened 2026-02-16 00:20:33 -05:00 by yindo · 6 comments
Owner

Originally created by @avtion on GitHub (Dec 17, 2025).

Originally assigned to: @fatelei on GitHub.

Self Checks

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

  • I have searched for existing issues search for existing issues, including closed ones.
  • I confirm that I am using English to submit this report (我已阅读并同意 Language Policy).
  • [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.10.1-fix.1

Describe the bug
When running multiple pods of dify-plugin-daemon in k8s, the InitPythonEnvironment() function can be executed concurrently by different pods for the same plugin. This race condition may cause the deleteVirtualEnvironment() function to be triggered while another pod is still initializing the Python virtual environment, resulting in the virtual environment being deleted during the initialization process. This leads to initialization failures and unpredictable behavior.

Root Cause
The issue is triggered by the handleNewLocalPlugins() function in the cluster mode:

  1. Each pod runs startLocalMonitor() which calls handleNewLocalPlugins() every 30 seconds
  2. handleNewLocalPlugins() lists all installed plugins from the shared installedBucket (cross-pod shared storage)
  3. Each pod checks if the plugin is already running locally (c.localPluginRuntimes.Exists())
  4. If not running locally, the pod calls LaunchLocalPlugin() -> InitEnvironment() -> InitPythonEnvironment()
  5. Although LaunchLocalPlugin() has a localPluginInstallationLock, this lock is in-memory and only prevents concurrent launches within the same pod
  6. Multiple pods can simultaneously pass this local lock check and all attempt to initialize the same plugin's Python environment in the shared storage
  7. This causes race conditions where one pod's deleteVirtualEnvironment() destroys the environment being built by another pod

To Reproduce
Steps to reproduce the behavior:

  1. Deploy dify-plugin-daemon with multiple replicas (e.g., 3+ pods) in a Kubernetes cluster
  2. Share the same persistent volume or storage for plugin working directories across all pods
  3. Install or update a plugin that triggers Python environment initialization (it take about 1 minute to initialize)
  4. Observe that multiple pods attempt to initialize the Python environment simultaneously
  5. See error: virtual environment gets deleted while being initialized, causing "virtual environment is invalid" errors

Expected behavior

  • Only one pod should be allowed to initialize the Python environment for a specific plugin at a time
  • Other pods should wait for the initialization to complete or timeout gracefully
  • The initialization process should be protected by a distributed lock mechanism
  • Log messages should be emitted when the virtual environment is being deleted and recreated
Originally created by @avtion on GitHub (Dec 17, 2025). Originally assigned to: @fatelei on GitHub. **Self Checks** To make sure we get to you in time, please check the following :) - [x] I have searched for existing issues [search 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 (我已阅读并同意 [Language Policy](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.10.1-fix.1 **Describe the bug** When running multiple pods of dify-plugin-daemon in k8s, the `InitPythonEnvironment()` function can be executed concurrently by different pods for the same plugin. This race condition may cause the `deleteVirtualEnvironment()` function to be triggered while another pod is still initializing the Python virtual environment, resulting in the virtual environment being deleted during the initialization process. This leads to initialization failures and unpredictable behavior. **Root Cause** The issue is triggered by the `handleNewLocalPlugins()` function in the cluster mode: 1. Each pod runs `startLocalMonitor()` which calls `handleNewLocalPlugins()` every 30 seconds 2. `handleNewLocalPlugins()` lists all installed plugins from the shared `installedBucket` (cross-pod shared storage) 3. Each pod checks if the plugin is already running locally (`c.localPluginRuntimes.Exists()`) 4. If not running locally, the pod calls `LaunchLocalPlugin()` -> `InitEnvironment()` -> `InitPythonEnvironment()` 5. Although `LaunchLocalPlugin()` has a `localPluginInstallationLock`, this lock is in-memory and only prevents concurrent launches within the same pod 6. Multiple pods can simultaneously pass this local lock check and all attempt to initialize the same plugin's Python environment in the shared storage 7. This causes race conditions where one pod's `deleteVirtualEnvironment()` destroys the environment being built by another pod **To Reproduce** Steps to reproduce the behavior: 1. Deploy dify-plugin-daemon with multiple replicas (e.g., 3+ pods) in a Kubernetes cluster 2. Share the same persistent volume or storage for plugin working directories across all pods 3. Install or update a plugin that triggers Python environment initialization (it take about 1 minute to initialize) 4. Observe that multiple pods attempt to initialize the Python environment simultaneously 5. See error: virtual environment gets deleted while being initialized, causing "virtual environment is invalid" errors **Expected behavior** - Only one pod should be allowed to initialize the Python environment for a specific plugin at a time - Other pods should wait for the initialization to complete or timeout gracefully - The initialization process should be protected by a distributed lock mechanism - Log messages should be emitted when the virtual environment is being deleted and recreated
yindo added the bug label 2026-02-16 00:20:33 -05:00
yindo closed this issue 2026-02-16 00:20:33 -05:00
Author
Owner

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

Hi @avtion! I'm Dosu and I’m helping the dify-plugin-daemon team.

This is a known issue in cluster environments: when multiple dify-plugin-daemon pods share storage, they can concurrently run InitPythonEnvironment, which deletes and recreates the .venv directory. The in-memory lock (localPluginInstallationLock) only prevents concurrent initialization within a single pod, not across pods, so race conditions occur and environments get deleted mid-initialization, causing failures and "virtual environment is invalid" errors. There is currently no distributed locking for environment initialization—only a temporary distributed lock (using Redis) for plugin installation tasks, not for the Python environment setup itself (code reference, issue #527).

The open-source version does not officially support multi-instance cluster mode; this is acknowledged by maintainers, and the enterprise edition may have different features (issue #132). There are no built-in workarounds or best practices documented for this scenario. To avoid these race conditions, you can: run only a single replica, avoid sharing plugin working directories across pods, or implement your own distributed lock (e.g., using Redis) around environment initialization.

If you need true multi-pod support with shared storage, a distributed locking mechanism is required to serialize environment setup for each plugin.

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 17, 2025): <!-- Greeting --> Hi @avtion! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the dify-plugin-daemon team. <!-- Answer --> This is a known issue in cluster environments: when multiple dify-plugin-daemon pods share storage, they can concurrently run `InitPythonEnvironment`, which deletes and recreates the `.venv` directory. The in-memory lock (`localPluginInstallationLock`) only prevents concurrent initialization within a single pod, not across pods, so race conditions occur and environments get deleted mid-initialization, causing failures and "virtual environment is invalid" errors. There is currently no distributed locking for environment initialization—only a temporary distributed lock (using Redis) for plugin installation tasks, not for the Python environment setup itself ([code reference](https://github.com/langgenius/dify-plugin-daemon/blob/72e9a7bc21acd14f7994f0b496da6454b99ee7fc/internal/core/local_runtime/environment_python.go#L10-L79), [issue #527](https://github.com/langgenius/dify-plugin-daemon/issues/527)). The open-source version does not officially support multi-instance cluster mode; this is acknowledged by maintainers, and the enterprise edition may have different features ([issue #132](https://github.com/langgenius/dify-plugin-daemon/issues/132)). There are no built-in workarounds or best practices documented for this scenario. To avoid these race conditions, you can: run only a single replica, avoid sharing plugin working directories across pods, or implement your own distributed lock (e.g., using Redis) around environment initialization. If you need true multi-pod support with shared storage, a distributed locking mechanism is required to serialize environment setup for each plugin. <!-- 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/ba35b318-b95e-4787-ac97-8475043947ea?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/ba35b318-b95e-4787-ac97-8475043947ea?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/ba35b318-b95e-4787-ac97-8475043947ea?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/ba35b318-b95e-4787-ac97-8475043947ea?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/ba35b318-b95e-4787-ac97-8475043947ea?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/ba35b318-b95e-4787-ac97-8475043947ea?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/ba35b318-b95e-4787-ac97-8475043947ea?feedback_type=other)</sup> [![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=This%20is%20a%20known%20issue%20in%20cluster%20environments%3A%20when%20multiple%20dify-plugin-daemon%20pods%20share%20storage%2C%20they%20can%20concurrently%20run%20%60InitPythonEnvironment%60%2C%20which%20deletes%20and%20recreates%20the%20%60.venv%60%20directory.%20The%20in-memory%20lock%20%28%60localPluginInstallationLock%60%29%20only%20prevents%20concurrent%20initialization%20within%20a%20single%20pod%2C%20not%20across%20pods%2C%20so%20race%20conditions%20occur%20and%20environments%20get%20deleted%20mid-initialization%2C%20causing%20failures%20and%20%22virtual%20environment%20is%20invalid%22%20errors.%20There%20is%20currently%20no%20distributed%20locking%20for%20environment%20initialization%E2%80%94only%20a%20temporary%20distributed%20lock%20%28using%20Redis%29%20for%20plugin%20installation%20tasks%2C%20not%20for%20the%20Python%20environment%20setup%20itself%C2%A0%28%5Bcode%20reference%5D%28https%3A//github.com/langgenius/dify-plugin-daemon/blob/72e9a7bc21acd14f7994f0b496da6454b99ee7fc/internal/core/local_runtime/environment_python.go%23L10-L79%29%2C%20%5Bissue%20%23527%5D%28https%3A//github.com/langgenius/dify-plugin-daemon/issues/527%29%29.%0A%0AThe%20open-source%20version%20does%20not%20officially%20support%20multi-instance%20cluster%20mode%3B%20this%20is%20acknowledged%20by%20maintainers%2C%20and%20the%20enterprise%20edition%20may%20have%20different%20features%C2%A0%28%5Bissue%20%23132%5D%28https%3A//github.com/langgenius/dify-plugin-daemon/issues/132%29%29.%20There%20are%20no%20built-in%20workarounds%20or%20best%20practices%20documented%20for%20this%20scenario.%20To%20avoid%20these%20race%20conditions%2C%20you%20can%3A%20run%20only%20a%20single%20replica%2C%20avoid%20sharing%20plugin%20working%20directories%20across%20pods%2C%20or%20implement%20your%20own%20distributed%20lock%20%28e.g.%2C%20using%20Redis%29%20around%20environment%20initialization.%20%0A%0AIf%20you%20need%20true%20multi-pod%20support%20with%20shared%20storage%2C%20a%20distributed%20locking%20mechanism%20is%20required%20to%20serialize%20environment%20setup%20for%20each%20plugin.)&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/541)
Author
Owner

@avtion commented on GitHub (Dec 17, 2025):

@dosu this is not about multi install or update plugin request, this is a new promble for 0.5.0 version ControlPanel watchdog functions.

@avtion commented on GitHub (Dec 17, 2025): @dosu this is not about multi install or update plugin request, this is a new promble for 0.5.0 version ControlPanel watchdog functions.
Author
Owner

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

You're right—this isn't about concurrent install/update requests, but a new issue introduced by the ControlPanel watchdog logic in 0.5.0. The watchdog (specifically, startLocalMonitor and handleNewLocalPlugins) now scans for and auto-launches plugins every 30 seconds across all pods. In a cluster, this means multiple pods can simultaneously try to initialize the same plugin environment, each running InitPythonEnvironment and deleteVirtualEnvironment without any distributed lock. This causes race conditions where one pod deletes the .venv directory while another is still initializing it, leading to failures and "virtual environment is invalid" errors.

There’s currently no built-in distributed locking or coordination for this watchdog-triggered environment setup—only for plugin install/upgrade tasks. The only mitigations are to run a single replica, avoid shared plugin directories, or implement your own distributed lock around environment initialization. No config flags or hooks exist to disable or coordinate the watchdog in multi-pod setups, and no recent PRs address this gap.

If you need true multi-pod support, a distributed lock (e.g., via Redis) is required to serialize environment setup for each plugin.
Relevant code reference

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 17, 2025): <!-- Answer --> You're right—this isn't about concurrent install/update requests, but a new issue introduced by the ControlPanel watchdog logic in 0.5.0. The watchdog (specifically, startLocalMonitor and handleNewLocalPlugins) now scans for and auto-launches plugins every 30 seconds across all pods. In a cluster, this means multiple pods can simultaneously try to initialize the same plugin environment, each running InitPythonEnvironment and deleteVirtualEnvironment without any distributed lock. This causes race conditions where one pod deletes the .venv directory while another is still initializing it, leading to failures and "virtual environment is invalid" errors. There’s currently no built-in distributed locking or coordination for this watchdog-triggered environment setup—only for plugin install/upgrade tasks. The only mitigations are to run a single replica, avoid shared plugin directories, or implement your own distributed lock around environment initialization. No config flags or hooks exist to disable or coordinate the watchdog in multi-pod setups, and no recent PRs address this gap. If you need true multi-pod support, a distributed lock (e.g., via Redis) is required to serialize environment setup for each plugin. [Relevant code reference](https://github.com/langgenius/dify-plugin-daemon/blob/72e9a7bc21acd14f7994f0b496da6454b99ee7fc/internal/core/control_panel/server_local.go#L14-L155) <!-- 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/e5518996-b76c-4b7a-9564-f2debff3ca28?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/e5518996-b76c-4b7a-9564-f2debff3ca28?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/e5518996-b76c-4b7a-9564-f2debff3ca28?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/e5518996-b76c-4b7a-9564-f2debff3ca28?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/e5518996-b76c-4b7a-9564-f2debff3ca28?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/e5518996-b76c-4b7a-9564-f2debff3ca28?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/e5518996-b76c-4b7a-9564-f2debff3ca28?feedback_type=other)</sup> [![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=You%27re%20right%E2%80%94this%20isn%27t%20about%20concurrent%20install/update%20requests%2C%20but%20a%20new%20issue%20introduced%20by%20the%20ControlPanel%20watchdog%20logic%20in%200.5.0.%20The%20watchdog%20%28specifically%2C%20startLocalMonitor%20and%20handleNewLocalPlugins%29%20now%20scans%20for%20and%20auto-launches%20plugins%20every%2030%20seconds%20across%20all%20pods.%20In%20a%20cluster%2C%20this%20means%20multiple%20pods%20can%20simultaneously%20try%20to%20initialize%20the%20same%20plugin%20environment%2C%20each%20running%20InitPythonEnvironment%20and%20deleteVirtualEnvironment%20without%20any%20distributed%20lock.%20This%20causes%20race%20conditions%20where%20one%20pod%20deletes%20the%20.venv%20directory%20while%20another%20is%20still%20initializing%20it%2C%20leading%20to%20failures%20and%20%22virtual%20environment%20is%20invalid%22%20errors.%20%0A%0AThere%E2%80%99s%20currently%20no%20built-in%20distributed%20locking%20or%20coordination%20for%20this%20watchdog-triggered%20environment%20setup%E2%80%94only%20for%20plugin%20install/upgrade%20tasks.%20The%20only%20mitigations%20are%20to%20run%20a%20single%20replica%2C%20avoid%20shared%20plugin%20directories%2C%20or%20implement%20your%20own%20distributed%20lock%20around%20environment%20initialization.%20No%20config%20flags%20or%20hooks%20exist%20to%20disable%20or%20coordinate%20the%20watchdog%20in%20multi-pod%20setups%2C%20and%20no%20recent%20PRs%20address%20this%20gap.%20%0A%0AIf%20you%20need%20true%20multi-pod%20support%2C%20a%20distributed%20lock%20%28e.g.%2C%20via%20Redis%29%20is%20required%20to%20serialize%20environment%20setup%20for%20each%20plugin.%20%20%0A%5BRelevant%20code%20reference%5D%28https%3A//github.com/langgenius/dify-plugin-daemon/blob/72e9a7bc21acd14f7994f0b496da6454b99ee7fc/internal/core/control_panel/server_local.go%23L14-L155%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/541)
Author
Owner

@Yeuoly commented on GitHub (Dec 17, 2025):

Hi, In a cluster environment, you should never share the cwd directory among pods, it definitely cases the race condition error, instead, you may separate them, only share directories like plugins plugin_packages etc.

@Yeuoly commented on GitHub (Dec 17, 2025): Hi, In a cluster environment, you should never share the `cwd` directory among pods, it definitely cases the race condition error, instead, you may separate them, only share directories like `plugins` `plugin_packages` etc.
Author
Owner

@avtion commented on GitHub (Dec 17, 2025):

@Yeuoly Thanks for your reply. Not sharing the cwd folder between pods is indeed a way to avoid concurrent operations by multiple pods, but it also introduces new issues.

If the cwd folder is not shared, it means that each Pod needs to individually unzip the difypkg file, download dependencies, and precompile during startup or when installing new plugins. In cases with a large number of plugins, this could lead to significantly longer startup times for each Pod.

At the same time, in the current Helm deployment method recommended by Dify, PLUGIN_WORKING_PATH usually points to /app/storage/cwd, while the mounted Persistent Volume directory is /app/storage. Splitting the cwd directory does not seem to be an easy task for previously deployed clusters.

https://github.com/douban/charts/blob/master/charts/dify/templates/deployment.yaml#L503
https://github.com/douban/charts/blob/master/charts/dify/templates/deployment.yaml#L503

Perhaps could consider introducing support for distributed locks in the InitPythonEnvironment process to avoid issues that may arise from multiple pods coexisting and potentially triggering deleteVirtualEnvironment when sharing the cwd directory.

@avtion commented on GitHub (Dec 17, 2025): @Yeuoly Thanks for your reply. Not sharing the `cwd` folder between pods is indeed a way to avoid concurrent operations by multiple pods, but it also introduces new issues. If the cwd folder is not shared, it means that each Pod needs to individually unzip the `difypkg` file, download dependencies, and precompile during startup or when installing new plugins. In cases with a large number of plugins, this could lead to significantly longer startup times for each Pod. At the same time, in the current Helm deployment method recommended by Dify, `PLUGIN_WORKING_PATH` usually points to `/app/storage/cwd`, while the mounted Persistent Volume directory is `/app/storage`. Splitting the `cwd` directory does not seem to be an easy task for previously deployed clusters. https://github.com/douban/charts/blob/master/charts/dify/templates/deployment.yaml#L503 https://github.com/douban/charts/blob/master/charts/dify/templates/deployment.yaml#L503 Perhaps could consider introducing support for distributed locks in the `InitPythonEnvironment` process to avoid issues that may arise from multiple pods coexisting and potentially triggering `deleteVirtualEnvironment` when sharing the `cwd` directory.
Author
Owner

@Yeuoly commented on GitHub (Dec 19, 2025):

@avtion , it's by design, adding locks here the there causes so much issues in distributed systems, in cases:

  • What if a plugin was initialised successfully on pod A but failed on pod B?
  • What if a pod was killed by host, the cwd were created but not completed already?
  • What if the cwd was deleted by pod A but pod B were still using it?
  • ...

There were so much issues we need to handle, as a result, you need to use https://github.com/langgenius/dify-plugin-daemon/blob/main/docs/runtime/sri.md if you are hosting a huge system which requires many plugins or you can contact business service.

As the secondary solution, the local plugin runtime requires a long time startup, but it works, I guess you may increase the parallel startup counts here https://github.com/langgenius/dify-plugin-daemon/blob/main/internal/types/app/config.go#L103C2-L103C32, and use rolling updates on k8s

@Yeuoly commented on GitHub (Dec 19, 2025): @avtion , it's by design, adding locks here the there causes so much issues in distributed systems, in cases: - What if a plugin was initialised successfully on pod A but failed on pod B? - What if a pod was killed by host, the cwd were created but not completed already? - What if the cwd was deleted by pod A but pod B were still using it? - ... There were so much issues we need to handle, as a result, you need to use https://github.com/langgenius/dify-plugin-daemon/blob/main/docs/runtime/sri.md if you are hosting a huge system which requires many plugins or you can contact business service. As the secondary solution, the local plugin runtime requires a long time startup, but it works, I guess you may increase the parallel startup counts here https://github.com/langgenius/dify-plugin-daemon/blob/main/internal/types/app/config.go#L103C2-L103C32, and use rolling updates on k8s
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langgenius/dify-plugin-daemon#221