mirror of
https://github.com/blw1138/Zordon.git
synced 2026-02-04 21:26:09 +00:00
Improve server shutdown (#126)
* Cleaned up server shutdown process * Fix exception on shutdown in Windows
This commit is contained in:
22
client.py
22
client.py
@@ -3,7 +3,7 @@ import logging
|
|||||||
import threading
|
import threading
|
||||||
from collections import deque
|
from collections import deque
|
||||||
|
|
||||||
from server import start_server
|
from server import ZordonServer
|
||||||
|
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
|
|
||||||
@@ -13,6 +13,7 @@ def __setup_buffer_handler():
|
|||||||
|
|
||||||
class BufferingHandler(logging.Handler, QObject):
|
class BufferingHandler(logging.Handler, QObject):
|
||||||
new_record = pyqtSignal(str)
|
new_record = pyqtSignal(str)
|
||||||
|
flushOnClose = True
|
||||||
|
|
||||||
def __init__(self, capacity=100):
|
def __init__(self, capacity=100):
|
||||||
logging.Handler.__init__(self)
|
logging.Handler.__init__(self)
|
||||||
@@ -52,12 +53,25 @@ def __show_gui(buffer_handler):
|
|||||||
window.buffer_handler = buffer_handler
|
window.buffer_handler = buffer_handler
|
||||||
window.show()
|
window.show()
|
||||||
|
|
||||||
return app.exec()
|
exit_code = app.exec()
|
||||||
|
|
||||||
|
# cleanup: remove and close the GUI logging handler before interpreter shutdown
|
||||||
|
root_logger = logging.getLogger()
|
||||||
|
if buffer_handler in root_logger.handlers:
|
||||||
|
root_logger.removeHandler(buffer_handler)
|
||||||
|
try:
|
||||||
|
buffer_handler.close()
|
||||||
|
except Exception:
|
||||||
|
# never let logging cleanup throw during shutdown
|
||||||
|
pass
|
||||||
|
|
||||||
|
return exit_code
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
local_server_thread = threading.Thread(target=start_server, args=[True], daemon=True)
|
server = ZordonServer()
|
||||||
local_server_thread.start()
|
server.start_server()
|
||||||
__show_gui(__setup_buffer_handler())
|
__show_gui(__setup_buffer_handler())
|
||||||
|
server.stop_server()
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|||||||
117
server.py
117
server.py
@@ -24,53 +24,15 @@ from src.version import APP_NAME, APP_VERSION, APP_REPO_NAME, APP_REPO_OWNER
|
|||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
|
|
||||||
|
|
||||||
def start_server(skip_updates=False) -> int:
|
class ZordonServer:
|
||||||
"""Initializes the application and runs it.
|
|
||||||
|
|
||||||
Args:
|
def __init__(self):
|
||||||
server_only: Run in server-only CLI mode. Default is False (runs in GUI mode).
|
# setup logging
|
||||||
|
logging.basicConfig(format='%(asctime)s: %(levelname)s: %(module)s: %(message)s', datefmt='%d-%b-%y %H:%M:%S',
|
||||||
|
level=Config.server_log_level.upper())
|
||||||
|
logging.getLogger("requests").setLevel(logging.WARNING) # suppress noisy requests/urllib3 logging
|
||||||
|
logging.getLogger("urllib3").setLevel(logging.WARNING)
|
||||||
|
|
||||||
Returns:
|
|
||||||
int: The exit status code.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def existing_process(process_name):
|
|
||||||
import psutil
|
|
||||||
current_pid = os.getpid()
|
|
||||||
current_process = psutil.Process(current_pid)
|
|
||||||
for proc in psutil.process_iter(['pid', 'name', 'ppid']):
|
|
||||||
proc_name = proc.info['name'].lower().rstrip('.exe')
|
|
||||||
if proc_name == process_name.lower() and proc.info['pid'] != current_pid:
|
|
||||||
if proc.info['pid'] == current_process.ppid():
|
|
||||||
continue # parent process
|
|
||||||
elif proc.info['ppid'] == current_pid:
|
|
||||||
continue # child process
|
|
||||||
else:
|
|
||||||
return proc # unrelated process
|
|
||||||
return None
|
|
||||||
|
|
||||||
# setup logging
|
|
||||||
logging.basicConfig(format='%(asctime)s: %(levelname)s: %(module)s: %(message)s', datefmt='%d-%b-%y %H:%M:%S',
|
|
||||||
level=Config.server_log_level.upper())
|
|
||||||
logging.getLogger("requests").setLevel(logging.WARNING) # suppress noisy requests/urllib3 logging
|
|
||||||
logging.getLogger("urllib3").setLevel(logging.WARNING)
|
|
||||||
|
|
||||||
# check for existing instance
|
|
||||||
existing_proc = existing_process(APP_NAME)
|
|
||||||
if existing_proc:
|
|
||||||
logger.fatal(f"Another instance of {APP_NAME} is already running (pid: {existing_proc.pid})")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# check for updates
|
|
||||||
if not skip_updates:
|
|
||||||
update_thread = threading.Thread(target=check_for_updates, args=(APP_REPO_NAME, APP_REPO_OWNER, APP_NAME,
|
|
||||||
APP_VERSION))
|
|
||||||
update_thread.start()
|
|
||||||
|
|
||||||
# main start
|
|
||||||
logger.info(f"Starting {APP_NAME} Render Server")
|
|
||||||
return_code = 0
|
|
||||||
try:
|
|
||||||
# Load Config YAML
|
# Load Config YAML
|
||||||
Config.setup_config_dir()
|
Config.setup_config_dir()
|
||||||
Config.load_config(system_safe_path(os.path.join(Config.config_dir(), 'config.yaml')))
|
Config.load_config(system_safe_path(os.path.join(Config.config_dir(), 'config.yaml')))
|
||||||
@@ -88,6 +50,34 @@ def start_server(skip_updates=False) -> int:
|
|||||||
logger.debug(f"Thumbs directory: {PreviewManager.storage_path}")
|
logger.debug(f"Thumbs directory: {PreviewManager.storage_path}")
|
||||||
logger.debug(f"Engines directory: {EngineManager.engines_path}")
|
logger.debug(f"Engines directory: {EngineManager.engines_path}")
|
||||||
|
|
||||||
|
self.api_server = None
|
||||||
|
|
||||||
|
def start_server(self):
|
||||||
|
|
||||||
|
def existing_process(process_name):
|
||||||
|
import psutil
|
||||||
|
current_pid = os.getpid()
|
||||||
|
current_process = psutil.Process(current_pid)
|
||||||
|
for proc in psutil.process_iter(['pid', 'name', 'ppid']):
|
||||||
|
proc_name = proc.info['name'].lower().rstrip('.exe')
|
||||||
|
if proc_name == process_name.lower() and proc.info['pid'] != current_pid:
|
||||||
|
if proc.info['pid'] == current_process.ppid():
|
||||||
|
continue # parent process
|
||||||
|
elif proc.info['ppid'] == current_pid:
|
||||||
|
continue # child process
|
||||||
|
else:
|
||||||
|
return proc # unrelated process
|
||||||
|
return None
|
||||||
|
|
||||||
|
# check for existing instance
|
||||||
|
existing_proc = existing_process(APP_NAME)
|
||||||
|
if existing_proc:
|
||||||
|
err_msg = f"Another instance of {APP_NAME} is already running (pid: {existing_proc.pid})"
|
||||||
|
logger.fatal(err_msg)
|
||||||
|
raise ProcessLookupError(err_msg)
|
||||||
|
|
||||||
|
# main start
|
||||||
|
logger.info(f"Starting {APP_NAME} Render Server")
|
||||||
# Set up the RenderQueue object
|
# Set up the RenderQueue object
|
||||||
RenderQueue.load_state(database_directory=system_safe_path(os.path.expanduser(Config.upload_folder)))
|
RenderQueue.load_state(database_directory=system_safe_path(os.path.expanduser(Config.upload_folder)))
|
||||||
ServerProxyManager.subscribe_to_listener()
|
ServerProxyManager.subscribe_to_listener()
|
||||||
@@ -97,9 +87,9 @@ def start_server(skip_updates=False) -> int:
|
|||||||
local_hostname = socket.gethostname()
|
local_hostname = socket.gethostname()
|
||||||
|
|
||||||
# configure and start API server
|
# configure and start API server
|
||||||
api_server = threading.Thread(target=start_api_server, args=(local_hostname,))
|
self.api_server = threading.Thread(target=start_api_server, args=(local_hostname,))
|
||||||
api_server.daemon = True
|
self.api_server.daemon = True
|
||||||
api_server.start()
|
self.api_server.start()
|
||||||
|
|
||||||
# start zeroconf server
|
# start zeroconf server
|
||||||
ZeroconfServer.configure(f"_{APP_NAME.lower()}._tcp.local.", local_hostname, Config.port_number)
|
ZeroconfServer.configure(f"_{APP_NAME.lower()}._tcp.local.", local_hostname, Config.port_number)
|
||||||
@@ -115,27 +105,26 @@ def start_server(skip_updates=False) -> int:
|
|||||||
logger.info(f"{APP_NAME} Render Server started - Hostname: {local_hostname}")
|
logger.info(f"{APP_NAME} Render Server started - Hostname: {local_hostname}")
|
||||||
RenderQueue.start() # Start evaluating the render queue
|
RenderQueue.start() # Start evaluating the render queue
|
||||||
|
|
||||||
# check for updates for render engines if configured or on first launch
|
def is_running(self):
|
||||||
# if Config.update_engines_on_launch or not EngineManager.get_engines():
|
return self.api_server and self.api_server.is_alive()
|
||||||
# EngineManager.update_all_engines()
|
|
||||||
|
|
||||||
api_server.join()
|
def stop_server(self):
|
||||||
|
logger.info(f"{APP_NAME} Render Server is preparing to stop")
|
||||||
except KeyboardInterrupt:
|
|
||||||
pass
|
|
||||||
except Exception as e:
|
|
||||||
logging.error(f"Unhandled exception: {e}")
|
|
||||||
return_code = 1
|
|
||||||
finally:
|
|
||||||
# shut down gracefully
|
|
||||||
logger.info(f"{APP_NAME} Render Server is preparing to shut down")
|
|
||||||
try:
|
try:
|
||||||
|
ZeroconfServer.stop()
|
||||||
RenderQueue.prepare_for_shutdown()
|
RenderQueue.prepare_for_shutdown()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(f"Exception during prepare for shutdown: {e}")
|
logger.exception(f"Exception during prepare for shutdown: {e}")
|
||||||
ZeroconfServer.stop()
|
|
||||||
logger.info(f"{APP_NAME} Render Server has shut down")
|
logger.info(f"{APP_NAME} Render Server has shut down")
|
||||||
return sys.exit(return_code)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
start_server()
|
server = ZordonServer()
|
||||||
|
try:
|
||||||
|
server.start_server()
|
||||||
|
server.api_server.join()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Unhandled exception: {e}")
|
||||||
|
finally:
|
||||||
|
server.stop_server()
|
||||||
|
|||||||
Reference in New Issue
Block a user