Fix/cognee UI start backend server (#1380)
<!-- .github/pull_request_template.md --> ## Description <!-- Please provide a clear, human-generated description of the changes in this PR. DO NOT use AI-generated descriptions. We want to understand your thought process and reasoning. --> ## Type of Change <!-- Please check the relevant option --> - [x] Bug fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [ ] Code refactoring - [x] Performance improvement - [ ] Other (please specify): ## Changes Made <!-- List the specific changes made in this PR --> - - - ## Testing <!-- Describe how you tested your changes --> ## Screenshots/Videos (if applicable) <!-- Add screenshots or videos to help explain your changes --> ## Pre-submission Checklist <!-- Please check all boxes that apply before submitting your PR --> - [ ] **I have tested my changes thoroughly before submitting this PR** - [ ] **This PR contains minimal changes necessary to address the issue/feature** - [ ] My code follows the project's coding standards and style guidelines - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] I have added necessary documentation (if applicable) - [ ] All new and existing tests pass - [ ] I have searched existing PRs to ensure this change hasn't been submitted already - [ ] I have linked any relevant issues in the description - [ ] My commits have clear and descriptive messages ## Related Issues <!-- Link any related issues using "Fixes #issue_number" or "Relates to #issue_number" --> ## Additional Notes <!-- Add any additional notes, concerns, or context for reviewers --> ## DCO Affirmation I affirm that all code in every commit of this pull request conforms to the terms of the Topoteretes Developer Certificate of Origin.
This commit is contained in:
commit
353aa6df7f
2 changed files with 36 additions and 30 deletions
|
|
@ -7,7 +7,7 @@ import webbrowser
|
||||||
import zipfile
|
import zipfile
|
||||||
import requests
|
import requests
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional, Tuple
|
from typing import Callable, Optional, Tuple
|
||||||
import tempfile
|
import tempfile
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
|
|
@ -326,6 +326,7 @@ def prompt_user_for_download() -> bool:
|
||||||
|
|
||||||
|
|
||||||
def start_ui(
|
def start_ui(
|
||||||
|
pid_callback: Callable[[int], None],
|
||||||
host: str = "localhost",
|
host: str = "localhost",
|
||||||
port: int = 3000,
|
port: int = 3000,
|
||||||
open_browser: bool = True,
|
open_browser: bool = True,
|
||||||
|
|
@ -346,6 +347,7 @@ def start_ui(
|
||||||
6. Optionally open the browser
|
6. Optionally open the browser
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
pid_callback: Callback to notify with PID of each spawned process
|
||||||
host: Host to bind the frontend server to (default: localhost)
|
host: Host to bind the frontend server to (default: localhost)
|
||||||
port: Port to run the frontend server on (default: 3000)
|
port: Port to run the frontend server on (default: 3000)
|
||||||
open_browser: Whether to open the browser automatically (default: True)
|
open_browser: Whether to open the browser automatically (default: True)
|
||||||
|
|
@ -391,20 +393,19 @@ def start_ui(
|
||||||
"--port",
|
"--port",
|
||||||
str(backend_port),
|
str(backend_port),
|
||||||
],
|
],
|
||||||
stdout=subprocess.PIPE,
|
# Inherit stdout/stderr from parent process to show logs
|
||||||
stderr=subprocess.PIPE,
|
stdout=None,
|
||||||
text=True,
|
stderr=None,
|
||||||
preexec_fn=os.setsid if hasattr(os, "setsid") else None,
|
preexec_fn=os.setsid if hasattr(os, "setsid") else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
pid_callback(backend_process.pid)
|
||||||
|
|
||||||
# Give the backend a moment to start
|
# Give the backend a moment to start
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
|
|
||||||
if backend_process.poll() is not None:
|
if backend_process.poll() is not None:
|
||||||
stdout, stderr = backend_process.communicate()
|
logger.error("Backend server failed to start - process exited early")
|
||||||
logger.error("Backend server failed to start:")
|
|
||||||
logger.error(f"stdout: {stdout}")
|
|
||||||
logger.error(f"stderr: {stderr}")
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
logger.info(f"✓ Backend API started at http://{backend_host}:{backend_port}")
|
logger.info(f"✓ Backend API started at http://{backend_host}:{backend_port}")
|
||||||
|
|
@ -460,7 +461,7 @@ def start_ui(
|
||||||
logger.info("This may take a moment to compile and start...")
|
logger.info("This may take a moment to compile and start...")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Use process group to ensure all child processes get terminated together
|
# Create frontend in its own process group for clean termination
|
||||||
process = subprocess.Popen(
|
process = subprocess.Popen(
|
||||||
["npm", "run", "dev"],
|
["npm", "run", "dev"],
|
||||||
cwd=frontend_path,
|
cwd=frontend_path,
|
||||||
|
|
@ -468,11 +469,11 @@ def start_ui(
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE,
|
stderr=subprocess.PIPE,
|
||||||
text=True,
|
text=True,
|
||||||
preexec_fn=os.setsid
|
preexec_fn=os.setsid if hasattr(os, "setsid") else None,
|
||||||
if hasattr(os, "setsid")
|
|
||||||
else None, # Create new process group on Unix
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
pid_callback(process.pid)
|
||||||
|
|
||||||
# Give it a moment to start up
|
# Give it a moment to start up
|
||||||
time.sleep(3)
|
time.sleep(3)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -174,30 +174,23 @@ def main() -> int:
|
||||||
|
|
||||||
# Handle UI flag
|
# Handle UI flag
|
||||||
if hasattr(args, "start_ui") and args.start_ui:
|
if hasattr(args, "start_ui") and args.start_ui:
|
||||||
server_process = None
|
spawned_pids = []
|
||||||
|
|
||||||
def signal_handler(signum, frame):
|
def signal_handler(signum, frame):
|
||||||
"""Handle Ctrl+C and other termination signals"""
|
"""Handle Ctrl+C and other termination signals"""
|
||||||
nonlocal server_process
|
nonlocal spawned_pids
|
||||||
fmt.echo("\nShutting down UI server...")
|
fmt.echo("\nShutting down UI server...")
|
||||||
if server_process:
|
|
||||||
|
for pid in spawned_pids:
|
||||||
try:
|
try:
|
||||||
# Try graceful termination first
|
pgid = os.getpgid(pid)
|
||||||
server_process.terminate()
|
os.killpg(pgid, signal.SIGTERM)
|
||||||
try:
|
fmt.success(f"✓ Process group {pgid} (PID {pid}) terminated.")
|
||||||
server_process.wait(timeout=5)
|
except (OSError, ProcessLookupError) as e:
|
||||||
fmt.success("UI server stopped gracefully.")
|
fmt.warning(f"Could not terminate process {pid}: {e}")
|
||||||
except subprocess.TimeoutExpired:
|
|
||||||
# If graceful termination fails, force kill
|
|
||||||
fmt.echo("Force stopping UI server...")
|
|
||||||
server_process.kill()
|
|
||||||
server_process.wait()
|
|
||||||
fmt.success("UI server stopped.")
|
|
||||||
except Exception as e:
|
|
||||||
fmt.warning(f"Error stopping server: {e}")
|
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
# Set up signal handlers
|
|
||||||
signal.signal(signal.SIGINT, signal_handler) # Ctrl+C
|
signal.signal(signal.SIGINT, signal_handler) # Ctrl+C
|
||||||
signal.signal(signal.SIGTERM, signal_handler) # Termination request
|
signal.signal(signal.SIGTERM, signal_handler) # Termination request
|
||||||
|
|
||||||
|
|
@ -206,8 +199,18 @@ def main() -> int:
|
||||||
|
|
||||||
fmt.echo("Starting cognee UI...")
|
fmt.echo("Starting cognee UI...")
|
||||||
|
|
||||||
|
# Callback to capture PIDs of all spawned processes
|
||||||
|
def pid_callback(pid):
|
||||||
|
nonlocal spawned_pids
|
||||||
|
spawned_pids.append(pid)
|
||||||
|
|
||||||
server_process = start_ui(
|
server_process = start_ui(
|
||||||
host="localhost", port=3000, open_browser=True, start_backend=True
|
host="localhost",
|
||||||
|
port=3000,
|
||||||
|
open_browser=True,
|
||||||
|
start_backend=True,
|
||||||
|
auto_download=True,
|
||||||
|
pid_callback=pid_callback,
|
||||||
)
|
)
|
||||||
|
|
||||||
if server_process:
|
if server_process:
|
||||||
|
|
@ -229,10 +232,12 @@ def main() -> int:
|
||||||
return 0
|
return 0
|
||||||
else:
|
else:
|
||||||
fmt.error("Failed to start UI server. Check the logs above for details.")
|
fmt.error("Failed to start UI server. Check the logs above for details.")
|
||||||
|
signal_handler(signal.SIGTERM, None)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
fmt.error(f"Error starting UI: {str(ex)}")
|
fmt.error(f"Error starting UI: {str(ex)}")
|
||||||
|
signal_handler(signal.SIGTERM, None)
|
||||||
if debug.is_debug_enabled():
|
if debug.is_debug_enabled():
|
||||||
raise ex
|
raise ex
|
||||||
return 1
|
return 1
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue