Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion api/ragflow_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,18 @@ def signal_handler(sig, frame):
logging.info(
f'project base: {utils.file_utils.get_project_base_directory()}'
)

# Warning about development mode
logging.warning("=" * 80)
logging.warning("⚠️ DEVELOPMENT MODE WARNING ⚠️")
logging.warning("You are running RAGFlow in development mode using FastAPI development server.")
logging.warning("This is NOT recommended for production environments!")
logging.warning("")
logging.warning("For production deployment, please use:")
logging.warning("1. Docker: The entrypoint.sh will automatically use Gunicorn WSGI")
logging.warning("2. Manual: gunicorn --workers 4 --bind 0.0.0.0:9380 api.wsgi:application")
logging.warning("=" * 80)

show_configs()
settings.init_settings()
print_rag_settings()
Expand Down Expand Up @@ -130,7 +142,9 @@ def signal_handler(sig, frame):

# start http server
try:
logging.info("RAGFlow HTTP server start...")
logging.warning("Starting RAGFlow HTTP server in DEVELOPMENT mode...")
logging.warning(
"Consider using Gunicorn for production: gunicorn --workers 4 --bind 0.0.0.0:9380 api.wsgi:application")
run_simple(
hostname=settings.HOST_IP,
port=settings.HOST_PORT,
Expand Down
140 changes: 140 additions & 0 deletions api/wsgi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
#
# Copyright 2024 The InfiniFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

from api.utils.log_utils import initRootLogger
from plugin import GlobalPluginManager

# Initialize logging first
initRootLogger("ragflow_server")

import logging
import os
import signal
import threading
import uuid
from concurrent.futures import ThreadPoolExecutor

from api import settings
from api.apps import app
from api.db.runtime_config import RuntimeConfig
from api.db.services.document_service import DocumentService
from api import utils

from api.db.db_models import init_database_tables as init_web_db
from api.db.init_data import init_web_data
from api.versions import get_ragflow_version
from api.utils import show_configs
from rag.settings import print_rag_settings
from rag.utils.redis_conn import RedisDistributedLock

# Global stop event and executor for background tasks
stop_event = threading.Event()
background_executor = None

RAGFLOW_DEBUGPY_LISTEN = int(os.environ.get('RAGFLOW_DEBUGPY_LISTEN', "0"))


def update_progress():
"""Background task to update document processing progress"""
lock_value = str(uuid.uuid4())
redis_lock = RedisDistributedLock("update_progress", lock_value=lock_value, timeout=60)
logging.info(f"update_progress lock_value: {lock_value}")
while not stop_event.is_set():
try:
if redis_lock.acquire():
DocumentService.update_progress()
redis_lock.release()
stop_event.wait(6)
except Exception:
logging.exception("update_progress exception")
finally:
redis_lock.release()


def signal_handler(sig, frame):
"""Handle shutdown signals gracefully"""
logging.info("Received interrupt signal, shutting down...")
stop_event.set()
if background_executor:
background_executor.shutdown(wait=False)


def initialize_ragflow():
"""Initialize RAGFlow application"""
global background_executor

logging.info(r"""
____ ___ ______ ______ __
/ __ \ / | / ____// ____// /____ _ __
/ /_/ // /| | / / __ / /_ / // __ \| | /| / /
/ _, _// ___ |/ /_/ // __/ / // /_/ /| |/ |/ /
/_/ |_|/_/ |_|\____//_/ /_/ \____/ |__/|__/

""")
logging.info(f'RAGFlow version: {get_ragflow_version()}')
logging.info(f'project base: {utils.file_utils.get_project_base_directory()}')

show_configs()
settings.init_settings()
print_rag_settings()

if RAGFLOW_DEBUGPY_LISTEN > 0:
logging.info(f"debugpy listen on {RAGFLOW_DEBUGPY_LISTEN}")
import debugpy
debugpy.listen(("0.0.0.0", RAGFLOW_DEBUGPY_LISTEN))

# Initialize database
init_web_db()
init_web_data()

# Initialize runtime config
RuntimeConfig.DEBUG = False # Force production mode for WSGI
RuntimeConfig.init_env()
RuntimeConfig.init_config(JOB_SERVER_HOST=settings.HOST_IP, HTTP_PORT=settings.HOST_PORT)

# Load plugins
GlobalPluginManager.load_plugins()

# Set up signal handlers
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)

# Start background progress update task
background_executor = ThreadPoolExecutor(max_workers=1)
background_executor.submit(update_progress)

logging.info("RAGFlow WSGI application initialized successfully in production mode")


# Initialize the application when module is imported
initialize_ragflow()

# Export the Flask app for WSGI
application = app

if __name__ == '__main__':
# This should not be used in production
logging.warning("Running WSGI module directly - this is not recommended for production")
from werkzeug.serving import run_simple

run_simple(
hostname=settings.HOST_IP,
port=settings.HOST_PORT,
application=app,
threaded=True,
use_reloader=False,
use_debugger=False,
)
70 changes: 70 additions & 0 deletions conf/gunicorn.conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Gunicorn configuration file for RAGFlow production deployment
import multiprocessing
import os

# Server socket
bind = f"{os.environ.get('RAGFLOW_HOST_IP', '0.0.0.0')}:{os.environ.get('RAGFLOW_HOST_PORT', '9380')}"
backlog = 2048

# Worker processes
workers = int(os.environ.get('GUNICORN_WORKERS', min(multiprocessing.cpu_count() * 2 + 1, 8)))
worker_class = 'sync'
worker_connections = 1000
timeout = 120
keepalive = 2
max_requests = 1000
max_requests_jitter = 100

# Restart workers after this many requests, to help prevent memory leaks
preload_app = True

# Logging
accesslog = '-'
errorlog = '-'
loglevel = 'info'
access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" %(D)s'

# Process naming
proc_name = 'ragflow_gunicorn'

# Server mechanics
daemon = False
pidfile = '/tmp/ragflow_gunicorn.pid'
tmp_upload_dir = None

# Security
limit_request_line = 8192
limit_request_fields = 200
limit_request_field_size = 8190

# Performance tuning for RAGFlow
worker_tmp_dir = '/dev/shm' # Use memory for temporary files if available

# SSL (if needed)
# keyfile = None
# certfile = None

# Environment variables that gunicorn should pass to workers
raw_env = [
'PYTHONPATH=/ragflow/',
]

def when_ready(server):
"""Called just after the server is started."""
server.log.info("RAGFlow Gunicorn server is ready. Production mode active.")

def worker_int(worker):
"""Called just after a worker exited on SIGINT or SIGQUIT."""
worker.log.info("RAGFlow worker received INT or QUIT signal")

def pre_fork(server, worker):
"""Called just before a worker is forked."""
server.log.info("RAGFlow worker about to be forked")

def post_fork(server, worker):
"""Called just after a worker has been forked."""
server.log.info("RAGFlow worker spawned (pid: %s)", worker.pid)

def worker_abort(worker):
"""Called when a worker received the SIGABRT signal."""
worker.log.info("RAGFlow worker received SIGABRT signal")
35 changes: 31 additions & 4 deletions docker/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ ENABLE_MCP_SERVER=0
CONSUMER_NO_BEG=0
CONSUMER_NO_END=0
WORKERS=1
GUNICORN_WORKERS=${GUNICORN_WORKERS:-4} # Number of Gunicorn workers for the web server

MCP_HOST="127.0.0.1"
MCP_PORT=9382
Expand Down Expand Up @@ -161,10 +162,36 @@ if [[ "${ENABLE_WEBSERVER}" -eq 1 ]]; then
echo "Starting nginx..."
/usr/sbin/nginx

echo "Starting ragflow_server..."
while true; do
"$PY" api/ragflow_server.py
done &
echo "Starting ragflow_server with Gunicorn (Production Mode)..."
# Get host and port from environment variables or use defaults
RAGFLOW_HOST=${RAGFLOW_HOST_IP:-0.0.0.0}
RAGFLOW_PORT=${RAGFLOW_HOST_PORT:-9380}
GUNICORN_WORKERS=${GUNICORN_WORKERS:-4}
GUNICORN_TIMEOUT=${GUNICORN_WORKERS:120}

echo "Gunicorn config: Workers=${GUNICORN_WORKERS}, Host=${RAGFLOW_HOST}, Port=${RAGFLOW_PORT}"

# Check if gunicorn config file exists and use it, otherwise use command line options
if [[ -f "/ragflow/conf/gunicorn.conf.py" ]]; then
echo "Using Gunicorn configuration file..."
exec gunicorn --config /ragflow/conf/gunicorn.conf.py 'api.wsgi:application'
else
echo "Using Gunicorn command line configuration..."
# Start gunicorn with our WSGI application
exec gunicorn --workers ${GUNICORN_WORKERS} \
--worker-class sync \
--worker-connections 1000 \
--max-requests 1000 \
--max-requests-jitter 100 \
--timeout ${GUNICORN_TIMEOUT} \
--keep-alive 2 \
--preload \
--bind ${RAGFLOW_HOST}:${RAGFLOW_PORT} \
--access-logfile - \
--error-logfile - \
--log-level info \
'api.wsgi:application'
fi
fi


Expand Down
28 changes: 10 additions & 18 deletions docker/launch_backend_service.sh
Original file line number Diff line number Diff line change
Expand Up @@ -91,25 +91,17 @@ task_exe(){
fi
}

# Function to execute ragflow_server with retry logic
# Function to execute ragflow_server with Gunicorn
run_server(){
local retry_count=0
while ! $STOP && [ $retry_count -lt $MAX_RETRIES ]; do
echo "Starting ragflow_server.py (Attempt $((retry_count+1)))"
$PY api/ragflow_server.py
EXIT_CODE=$?
if [ $EXIT_CODE -eq 0 ]; then
echo "ragflow_server.py exited successfully."
break
else
echo "ragflow_server.py failed with exit code $EXIT_CODE. Retrying..." >&2
retry_count=$((retry_count + 1))
sleep 2
fi
done

if [ $retry_count -ge $MAX_RETRIES ]; then
echo "ragflow_server.py failed after $MAX_RETRIES attempts. Exiting..." >&2
echo "Starting ragflow_server with Gunicorn..."
# GUNICORN_WORKERS, RAGFLOW_HOST_IP, RAGFLOW_HOST_PORT can be set in .env file
# Defaults are provided if they are not set.
gunicorn --workers ${GUNICORN_WORKERS:-4} \
--bind ${RAGFLOW_HOST_IP:-0.0.0.0}:${RAGFLOW_HOST_PORT:-9380} \
--preload 'api.apps:app'
EXIT_CODE=$?
if [ $EXIT_CODE -ne 0 ]; then
echo "Gunicorn failed with exit code $EXIT_CODE. Exiting..." >&2
cleanup
fi
}
Expand Down
Loading
Loading