Skip to content

Commit 0cbe7c7

Browse files
Copilotlstein
andcommitted
Fix: Kill the server with one keyboard interrupt (#94)
* Initial plan * Handle KeyboardInterrupt in run_app to allow single Ctrl+C shutdown Co-authored-by: lstein <111189+lstein@users.noreply.github.com> * Force os._exit(0) on KeyboardInterrupt to avoid hanging on background threads Co-authored-by: lstein <111189+lstein@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: lstein <111189+lstein@users.noreply.github.com> Fix graceful shutdown to wait for download/install worker threads (#102) * Initial plan * Replace os._exit(0) with ApiDependencies.shutdown() on KeyboardInterrupt Instead of immediately force-exiting the process on CTRL+C, call ApiDependencies.shutdown() to gracefully stop the download and install manager services, allowing active work to complete or cancel cleanly before the process exits. Co-authored-by: lstein <111189+lstein@users.noreply.github.com> * Make stop() idempotent in download and model install services When CTRL+C is pressed, uvicorn's graceful shutdown triggers the FastAPI lifespan which calls ApiDependencies.shutdown(), then a KeyboardInterrupt propagates from run_until_complete() hitting the except block which tries to call ApiDependencies.shutdown() a second time. Change both stop() methods to return silently (instead of raising) when the service is not running. This handles: - Double-shutdown: lifespan already stopped the services - Early interrupt: services were never fully started Co-authored-by: lstein <111189+lstein@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: lstein <111189+lstein@users.noreply.github.com> Fix shutdown hang on session processor thread lock (#108) * Initial plan * Fix shutdown hang: wake session processor thread on stop() and mark daemon Co-authored-by: lstein <111189+lstein@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: lstein <111189+lstein@users.noreply.github.com>
1 parent 6b57b00 commit 0cbe7c7

4 files changed

Lines changed: 15 additions & 3 deletions

File tree

invokeai/app/run_app.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,4 +100,12 @@ def run_app() -> None:
100100
for hdlr in logger.handlers:
101101
uvicorn_logger.addHandler(hdlr)
102102

103-
loop.run_until_complete(server.serve())
103+
try:
104+
loop.run_until_complete(server.serve())
105+
except KeyboardInterrupt:
106+
logger.info("InvokeAI shutting down...")
107+
# Gracefully shut down services (e.g. model download and install managers) so that any
108+
# active work is completed or cleanly cancelled before the process exits.
109+
from invokeai.app.api.dependencies import ApiDependencies
110+
111+
ApiDependencies.shutdown()

invokeai/app/services/download/download_default.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ def stop(self, *args: Any, **kwargs: Any) -> None:
8888
"""Stop the download worker threads."""
8989
with self._lock:
9090
if not self._worker_pool:
91-
raise Exception("Attempt to stop the download service before it was started")
91+
return
9292
self._accept_download_requests = False # reject attempts to add new jobs to queue
9393
queued_jobs = [x for x in self.list_jobs() if x.status == DownloadJobStatus.WAITING]
9494
active_jobs = [x for x in self.list_jobs() if x.status == DownloadJobStatus.RUNNING]

invokeai/app/services/model_install/model_install_default.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ def start(self, invoker: Optional[Invoker] = None) -> None:
330330
def stop(self, invoker: Optional[Invoker] = None) -> None:
331331
"""Stop the installer thread; after this the object can be deleted and garbage collected."""
332332
if not self._running:
333-
raise Exception("Attempt to stop the install service before it was started")
333+
return
334334
self._logger.debug("calling stop_event.set()")
335335
self._stop_event.set()
336336
self._clear_pending_jobs()

invokeai/app/services/session_processor/session_processor_default.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,7 @@ def start(self, invoker: Invoker) -> None:
355355
self._thread = Thread(
356356
name="session_processor",
357357
target=self._process,
358+
daemon=True,
358359
kwargs={
359360
"stop_event": self._stop_event,
360361
"poll_now_event": self._poll_now_event,
@@ -366,6 +367,9 @@ def start(self, invoker: Invoker) -> None:
366367

367368
def stop(self, *args, **kwargs) -> None:
368369
self._stop_event.set()
370+
# Wake the thread if it is sleeping in poll_now_event.wait() or blocked in resume_event.wait() (paused).
371+
self._poll_now_event.set()
372+
self._resume_event.set()
369373

370374
def _poll_now(self) -> None:
371375
self._poll_now_event.set()

0 commit comments

Comments
 (0)