dify - 💡(How to fix) Fix Unify Dify runtime around gevent-only startup and remove DEBUG-based runner drift [2 comments, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
langgenius/dify#36014Fetched 2026-05-11 03:28:58
View on GitHub
Comments
2
Participants
1
Timeline
3
Reactions
2
Participants
Timeline (top)
commented ×2cross-referenced ×1

Dify's current startup paths drift across Docker API, local dev API, worker, and beat. Some paths run under Gunicorn gevent workers, while others bypass Gunicorn entirely (python -m app, flask run --debug, celery -A app.celery ...).

That means the effective runtime model changes depending on the entrypoint:

  • stdlib monkey patching may or may not happen
  • gRPC / psycopg2 gevent compatibility patching may or may not happen
  • DEBUG currently changes the process model, not just application debug behavior

At this point the codebase already depends on a gevent runtime in practice (gevent.pywsgi, gevent-websocket, long-lived streaming / websocket paths), so the safest direction is to explicitly support a gevent-only startup model and remove the remaining runner drift.

Root Cause

Because of that, keeping early startup print(...) for patch confirmation is acceptable here. This is one of the few places where print is preferable to assuming logging is ready.

Fix Action

Fix / Workaround

  • stdlib monkey patching may or may not happen
  • gRPC / psycopg2 gevent compatibility patching may or may not happen
  • DEBUG currently changes the process model, not just application debug behavior

2. Keep stdlib monkey patching framework-owned

Do not move stdlib monkey patching into application import side effects.

RAW_BUFFERClick to expand / collapse

This proposal is created by Codex with 5.4 XHigh. I'm responsible for its content.

Summary

Dify's current startup paths drift across Docker API, local dev API, worker, and beat. Some paths run under Gunicorn gevent workers, while others bypass Gunicorn entirely (python -m app, flask run --debug, celery -A app.celery ...).

That means the effective runtime model changes depending on the entrypoint:

  • stdlib monkey patching may or may not happen
  • gRPC / psycopg2 gevent compatibility patching may or may not happen
  • DEBUG currently changes the process model, not just application debug behavior

At this point the codebase already depends on a gevent runtime in practice (gevent.pywsgi, gevent-websocket, long-lived streaming / websocket paths), so the safest direction is to explicitly support a gevent-only startup model and remove the remaining runner drift.

Proposed implementation

1. API runtime: always run under Gunicorn gevent worker

  • Remove the DEBUG=true -> python -m app branch from api/docker/entrypoint.sh
  • Make the API service always start through Gunicorn
  • Stop using DEBUG to decide which runner to use
  • Keep DEBUG only for application-level behavior
  • Keep app.py in the repo, but mark its __main__ path as not supported as a manual server entrypoint

2. Keep stdlib monkey patching framework-owned

Do not move stdlib monkey patching into application import side effects.

Instead:

  • let Gunicorn continue to own gevent.monkey.patch_all() timing for API workers
  • let Celery continue to own gevent pool patch timing for worker/beat processes

This keeps patch timing as-early-as-possible without introducing fragile app-level patch side effects.

3. Extract a shared helper for third-party compatibility patching

Create one shared helper that only applies third-party gevent compatibility patches, for example:

  • grpc.experimental.gevent.init_gevent()
  • psycogreen.gevent.patch_psycopg()

Then reuse that helper from:

  • api/gunicorn.conf.py
  • api/celery_entrypoint.py

The helper should be idempotent.

4. Local dev API should also use Gunicorn gevent

Update local dev startup so it no longer uses flask run --debug.

Instead, dev/start-api should run Gunicorn with reload enabled and a gevent websocket-capable worker.

This ensures local dev uses the same runtime model as Docker / production-like startup.

5. Local worker and beat should also go through celery_entrypoint

Update:

  • dev/start-worker
  • dev/start-beat

so both use celery -A celery_entrypoint.celery ...

This avoids config / patching drift between Docker and local development.

6. Update VS Code debug entrypoints

vscode/launch.json.template also needs to be updated to reflect the gevent-only startup model.

If developers use VS Code debug launch configs, they should not be routed through an unpatched / non-Gunicorn / non-Celery startup path.

The debug template should point to the supported gevent-compatible startup path for API / worker debugging.

7. Update developer documentation

Document the runtime decision clearly:

  • Dify backend runtime is gevent-only
  • DEBUG no longer changes the runner
  • supported dev startup commands / debug paths
  • why Gunicorn / Celery own stdlib monkey patch timing
  • why direct manual server entrypoints are unsupported

Major design decisions

Gevent-only is the supported runtime

We should stop pretending that threading / direct debug server paths are equivalent runtime modes. They are not.

The implementation and runtime assumptions already lean on gevent, so the supported model should be explicit.

DEBUG must not change the process model

DEBUG silently switching from Gunicorn to python -m app is too large a semantic difference.

This should be removed.

print is acceptable in early patching code

The shared third-party patch helper may need to run before logging is fully initialized.

Because of that, keeping early startup print(...) for patch confirmation is acceptable here. This is one of the few places where print is preferable to assuming logging is ready.

Keep app.py, but do not treat it as a supported manual server runner

app.py can remain as a module-level integration point, but its __main__ path should not remain a normal supported startup mode.

Beat should also use celery_entrypoint

Even if beat is less likely to hit the exact same issue surface as workers, using the same entrypoint avoids future configuration drift.

Out of scope

  • redesigning application logic around a non-gevent runtime
  • preserving flask run / direct server startup as a first-class supported mode
  • moving stdlib monkey patching into arbitrary app import paths

Acceptance criteria

  • API startup no longer depends on DEBUG for runner selection
  • Docker API and local dev API both run through Gunicorn gevent startup
  • local worker and beat both use celery_entrypoint
  • Gunicorn and Celery both reuse the same shared third-party patch helper
  • VS Code launch template is updated to a supported gevent-compatible startup path
  • developer docs reflect the new startup model
  • app.py remains present, but is clearly marked as not being a supported manual server entrypoint

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING