handle cli started ui closure gracefully
This commit is contained in:
parent
d93f31ad30
commit
7220052ca6
2 changed files with 66 additions and 10 deletions
|
|
@ -1,4 +1,5 @@
|
||||||
import os
|
import os
|
||||||
|
import signal
|
||||||
import subprocess
|
import subprocess
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
|
@ -401,6 +402,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
|
||||||
process = subprocess.Popen(
|
process = subprocess.Popen(
|
||||||
["npm", "run", "dev"],
|
["npm", "run", "dev"],
|
||||||
cwd=frontend_path,
|
cwd=frontend_path,
|
||||||
|
|
@ -408,6 +410,7 @@ def start_ui(
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE,
|
stderr=subprocess.PIPE,
|
||||||
text=True,
|
text=True,
|
||||||
|
preexec_fn=os.setsid if hasattr(os, 'setsid') else None, # Create new process group on Unix
|
||||||
)
|
)
|
||||||
|
|
||||||
# Give it a moment to start up
|
# Give it a moment to start up
|
||||||
|
|
@ -447,7 +450,7 @@ def start_ui(
|
||||||
|
|
||||||
def stop_ui(process: subprocess.Popen) -> bool:
|
def stop_ui(process: subprocess.Popen) -> bool:
|
||||||
"""
|
"""
|
||||||
Stop a running UI server process.
|
Stop a running UI server process and all its children.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
process: The subprocess.Popen object returned by start_ui()
|
process: The subprocess.Popen object returned by start_ui()
|
||||||
|
|
@ -459,12 +462,38 @@ def stop_ui(process: subprocess.Popen) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
process.terminate()
|
# Try to terminate the process group (includes child processes like Next.js)
|
||||||
|
if hasattr(os, 'killpg'):
|
||||||
|
try:
|
||||||
|
# Kill the entire process group
|
||||||
|
os.killpg(os.getpgid(process.pid), signal.SIGTERM)
|
||||||
|
logger.debug("Sent SIGTERM to process group")
|
||||||
|
except (OSError, ProcessLookupError):
|
||||||
|
# Fall back to terminating just the main process
|
||||||
|
process.terminate()
|
||||||
|
logger.debug("Terminated main process only")
|
||||||
|
else:
|
||||||
|
process.terminate()
|
||||||
|
logger.debug("Terminated main process (Windows)")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
process.wait(timeout=10)
|
process.wait(timeout=10)
|
||||||
|
logger.info("UI server stopped gracefully")
|
||||||
except subprocess.TimeoutExpired:
|
except subprocess.TimeoutExpired:
|
||||||
logger.warning("Process didn't terminate gracefully, forcing kill")
|
logger.warning("Process didn't terminate gracefully, forcing kill")
|
||||||
process.kill()
|
|
||||||
|
# Force kill the process group
|
||||||
|
if hasattr(os, 'killpg'):
|
||||||
|
try:
|
||||||
|
os.killpg(os.getpgid(process.pid), signal.SIGKILL)
|
||||||
|
logger.debug("Sent SIGKILL to process group")
|
||||||
|
except (OSError, ProcessLookupError):
|
||||||
|
process.kill()
|
||||||
|
logger.debug("Force killed main process only")
|
||||||
|
else:
|
||||||
|
process.kill()
|
||||||
|
logger.debug("Force killed main process (Windows)")
|
||||||
|
|
||||||
process.wait()
|
process.wait()
|
||||||
|
|
||||||
logger.info("UI server stopped")
|
logger.info("UI server stopped")
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import argparse
|
import argparse
|
||||||
|
import signal
|
||||||
|
import subprocess
|
||||||
from typing import Any, Sequence, Dict, Type, cast, List
|
from typing import Any, Sequence, Dict, Type, cast, List
|
||||||
import click
|
import click
|
||||||
|
|
||||||
|
|
@ -172,16 +174,43 @@ 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
|
||||||
|
|
||||||
|
def signal_handler(signum, frame):
|
||||||
|
"""Handle Ctrl+C and other termination signals"""
|
||||||
|
nonlocal server_process
|
||||||
|
fmt.echo("\nShutting down UI server...")
|
||||||
|
if server_process:
|
||||||
|
try:
|
||||||
|
# Try graceful termination first
|
||||||
|
server_process.terminate()
|
||||||
|
try:
|
||||||
|
server_process.wait(timeout=5)
|
||||||
|
fmt.success("UI server stopped gracefully.")
|
||||||
|
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)
|
||||||
|
|
||||||
|
# Set up signal handlers
|
||||||
|
signal.signal(signal.SIGINT, signal_handler) # Ctrl+C
|
||||||
|
signal.signal(signal.SIGTERM, signal_handler) # Termination request
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from cognee import start_ui
|
from cognee import start_ui
|
||||||
fmt.echo("Starting cognee UI...")
|
fmt.echo("Starting cognee UI...")
|
||||||
server = start_ui(
|
server_process = start_ui(
|
||||||
host="localhost",
|
host="localhost",
|
||||||
port=3001,
|
port=3001,
|
||||||
open_browser=True
|
open_browser=True
|
||||||
)
|
)
|
||||||
|
|
||||||
if server:
|
if server_process:
|
||||||
fmt.success("UI server started successfully!")
|
fmt.success("UI server started successfully!")
|
||||||
fmt.echo("The interface is available at: http://localhost:3001")
|
fmt.echo("The interface is available at: http://localhost:3001")
|
||||||
fmt.note("Press Ctrl+C to stop the server...")
|
fmt.note("Press Ctrl+C to stop the server...")
|
||||||
|
|
@ -189,13 +218,11 @@ def main() -> int:
|
||||||
try:
|
try:
|
||||||
# Keep the server running
|
# Keep the server running
|
||||||
import time
|
import time
|
||||||
while server.poll() is None: # While process is still running
|
while server_process.poll() is None: # While process is still running
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
fmt.echo("\nStopping UI server...")
|
# This shouldn't happen now due to signal handler, but kept for safety
|
||||||
server.terminate()
|
signal_handler(signal.SIGINT, None)
|
||||||
server.wait()
|
|
||||||
fmt.success("UI server stopped.")
|
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue