run_with_gunicorn.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. #!/usr/bin/env python
  2. """
  3. Start LightRAG server with Gunicorn
  4. """
  5. import os
  6. import sys
  7. import platform
  8. import pipmaster as pm
  9. # Capture this before importing LightRAG modules, because those imports load .env.
  10. # On macOS, libobjc needs this value in the inherited process environment.
  11. _PROCESS_START_OBJC_FORK_SAFETY = os.environ.get("OBJC_DISABLE_INITIALIZE_FORK_SAFETY")
  12. def check_and_install_dependencies():
  13. """Check and install required dependencies"""
  14. required_packages = [
  15. "gunicorn",
  16. "tiktoken",
  17. "psutil",
  18. # Add other required packages here
  19. ]
  20. for package in required_packages:
  21. if not pm.is_installed(package):
  22. print(f"Installing {package}...")
  23. pm.install(package)
  24. print(f"{package} installed successfully")
  25. def main():
  26. from lightrag.api.utils_api import display_splash_screen, check_env_file
  27. from lightrag.api.config import global_args, initialize_config
  28. from lightrag.utils import get_env_value
  29. from lightrag.kg.shared_storage import initialize_share_data
  30. from lightrag.constants import (
  31. DEFAULT_WOKERS,
  32. DEFAULT_TIMEOUT,
  33. )
  34. # Explicitly initialize configuration for Gunicorn mode
  35. initialize_config()
  36. # Set Gunicorn mode flag for lifespan cleanup detection
  37. os.environ["LIGHTRAG_GUNICORN_MODE"] = "1"
  38. # Check .env file
  39. if not check_env_file():
  40. sys.exit(1)
  41. # Check macOS fork safety environment variable for multi-worker mode
  42. if (
  43. platform.system() == "Darwin"
  44. and global_args.workers > 1
  45. and _PROCESS_START_OBJC_FORK_SAFETY != "YES"
  46. ):
  47. current_objc_fork_safety = os.environ.get("OBJC_DISABLE_INITIALIZE_FORK_SAFETY")
  48. print("\n" + "=" * 80)
  49. print("❌ ERROR: Missing required environment variable on macOS!")
  50. print("=" * 80)
  51. print("\nmacOS with Gunicorn multi-worker mode requires:")
  52. print(" OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES")
  53. print("\nReason:")
  54. print(" NumPy uses macOS's Accelerate framework (Objective-C based) for")
  55. print(" vector computations. The Objective-C runtime has fork safety checks")
  56. print(" that will crash worker processes when embedding functions are called.")
  57. print("\nCurrent configuration:")
  58. print(" - Operating System: macOS (Darwin)")
  59. print(f" - Workers: {global_args.workers}")
  60. print(
  61. " - Process Environment at Startup: "
  62. f"{_PROCESS_START_OBJC_FORK_SAFETY or 'NOT SET'}"
  63. )
  64. print(
  65. " - Environment After .env Load: "
  66. f"{current_objc_fork_safety or 'NOT SET'}"
  67. )
  68. if current_objc_fork_safety == "YES":
  69. print("\nNote:")
  70. print(" OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES was loaded from .env,")
  71. print(" but that is too late for the macOS Objective-C runtime.")
  72. print(" Export it before starting lightrag-gunicorn.")
  73. print("\nHow to fix:")
  74. print(" Option 1 - Set environment variable before starting (recommended):")
  75. print(" export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES")
  76. print(" lightrag-gunicorn --workers 2")
  77. print("\n Option 2 - Add to your shell profile (~/.zshrc or ~/.bash_profile):")
  78. print(" echo 'export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES' >> ~/.zshrc")
  79. print(" source ~/.zshrc")
  80. print("\n Option 3 - Use single worker mode (no multiprocessing):")
  81. print(" lightrag-server --workers 1")
  82. print("=" * 80 + "\n")
  83. sys.exit(1)
  84. # Check and install dependencies
  85. check_and_install_dependencies()
  86. # Note: Signal handlers are NOT registered here because:
  87. # - Master cleanup already handled by gunicorn_config.on_exit()
  88. # Display startup information
  89. display_splash_screen(global_args)
  90. print("🚀 Starting LightRAG with Gunicorn")
  91. print(f"🔄 Worker management: Gunicorn (workers={global_args.workers})")
  92. print("🔍 Preloading app: Enabled")
  93. print("📝 Note: Using Gunicorn's preload feature for shared data initialization")
  94. print("\n\n" + "=" * 80)
  95. print("MAIN PROCESS INITIALIZATION")
  96. print(f"Process ID: {os.getpid()}")
  97. print(f"Workers setting: {global_args.workers}")
  98. print("=" * 80 + "\n")
  99. # Import Gunicorn's StandaloneApplication
  100. from gunicorn.app.base import BaseApplication
  101. # Define a custom application class that loads our config
  102. class GunicornApp(BaseApplication):
  103. def __init__(self, app, options=None):
  104. self.options = options or {}
  105. self.application = app
  106. super().__init__()
  107. def load_config(self):
  108. # Define valid Gunicorn configuration options
  109. valid_options = {
  110. "bind",
  111. "workers",
  112. "worker_class",
  113. "timeout",
  114. "keepalive",
  115. "preload_app",
  116. "errorlog",
  117. "accesslog",
  118. "loglevel",
  119. "certfile",
  120. "keyfile",
  121. "limit_request_line",
  122. "limit_request_fields",
  123. "limit_request_field_size",
  124. "graceful_timeout",
  125. "max_requests",
  126. "max_requests_jitter",
  127. }
  128. # Special hooks that need to be set separately
  129. special_hooks = {
  130. "on_starting",
  131. "on_reload",
  132. "on_exit",
  133. "pre_fork",
  134. "post_fork",
  135. "pre_exec",
  136. "pre_request",
  137. "post_request",
  138. "worker_init",
  139. "worker_exit",
  140. "nworkers_changed",
  141. "child_exit",
  142. }
  143. # Import and configure the gunicorn_config module
  144. from lightrag.api import gunicorn_config
  145. # Set configuration variables in gunicorn_config, prioritizing command line arguments
  146. gunicorn_config.workers = (
  147. global_args.workers
  148. if global_args.workers
  149. else get_env_value("WORKERS", DEFAULT_WOKERS, int)
  150. )
  151. # Bind configuration prioritizes command line arguments
  152. host = (
  153. global_args.host
  154. if global_args.host != "0.0.0.0"
  155. else os.getenv("HOST", "0.0.0.0")
  156. )
  157. port = (
  158. global_args.port
  159. if global_args.port != 9621
  160. else get_env_value("PORT", 9621, int)
  161. )
  162. gunicorn_config.bind = f"{host}:{port}"
  163. # Log level configuration prioritizes command line arguments
  164. gunicorn_config.loglevel = (
  165. global_args.log_level.lower()
  166. if global_args.log_level
  167. else os.getenv("LOG_LEVEL", "info")
  168. )
  169. # Timeout configuration prioritizes command line arguments
  170. gunicorn_config.timeout = (
  171. global_args.timeout + 30
  172. if global_args.timeout is not None
  173. else get_env_value(
  174. "TIMEOUT", DEFAULT_TIMEOUT + 30, int, special_none=True
  175. )
  176. )
  177. # Keepalive configuration
  178. gunicorn_config.keepalive = get_env_value("KEEPALIVE", 5, int)
  179. # SSL configuration prioritizes command line arguments
  180. if global_args.ssl or os.getenv("SSL", "").lower() in (
  181. "true",
  182. "1",
  183. "yes",
  184. "t",
  185. "on",
  186. ):
  187. gunicorn_config.certfile = (
  188. global_args.ssl_certfile
  189. if global_args.ssl_certfile
  190. else os.getenv("SSL_CERTFILE")
  191. )
  192. gunicorn_config.keyfile = (
  193. global_args.ssl_keyfile
  194. if global_args.ssl_keyfile
  195. else os.getenv("SSL_KEYFILE")
  196. )
  197. # Set configuration options from the module
  198. for key in dir(gunicorn_config):
  199. if key in valid_options:
  200. value = getattr(gunicorn_config, key)
  201. # Skip functions like on_starting and None values
  202. if not callable(value) and value is not None:
  203. self.cfg.set(key, value)
  204. # Set special hooks
  205. elif key in special_hooks:
  206. value = getattr(gunicorn_config, key)
  207. if callable(value):
  208. self.cfg.set(key, value)
  209. if hasattr(gunicorn_config, "logconfig_dict"):
  210. self.cfg.set(
  211. "logconfig_dict", getattr(gunicorn_config, "logconfig_dict")
  212. )
  213. def load(self):
  214. # Import the application
  215. from lightrag.api.lightrag_server import get_application
  216. return get_application(global_args)
  217. # Create the application
  218. app = GunicornApp("")
  219. # Force workers to be an integer and greater than 1 for multi-process mode
  220. workers_count = global_args.workers
  221. if workers_count > 1:
  222. # Set a flag to indicate we're in the main process
  223. os.environ["LIGHTRAG_MAIN_PROCESS"] = "1"
  224. initialize_share_data(workers_count)
  225. else:
  226. initialize_share_data(1)
  227. # Run the application
  228. print("\nStarting Gunicorn with direct Python API...")
  229. app.run()
  230. if __name__ == "__main__":
  231. main()