invoke-ai/InvokeAI · error · Exception
Attempt to start the download service twice
Error message
Attempt to start the download service twice
What it means
DownloadService.start() is idempotency-guarded: under a lock it checks _worker_pool and raises if workers already exist. Calling start() twice without an intervening stop() would duplicate worker threads, so it is rejected.
Source
Thrown at invokeai/app/services/download/download_default.py:103
# download_proxy must still be honored rather than silently ignored when both
# settings are present. It is applied per request (see the single call site in
# _do_download) because that is the only level that takes precedence over
# ambient *_PROXY variables in a plain Session.
self._requests = requests.Session()
if self._app_config.download_proxy:
proxy = self._app_config.download_proxy
self._request_proxies = {"http": proxy, "https": proxy}
else:
self._requests = build_guarded_session(proxy=self._app_config.download_proxy)
warn_if_proxied(self._requests, self._logger)
self._accept_download_requests = False
self._max_parallel_dl = max_parallel_dl
def start(self, *args: Any, **kwargs: Any) -> None:
"""Start the download worker threads."""
with self._lock:
if self._worker_pool:
raise Exception("Attempt to start the download service twice")
self._stop_event.clear()
self._start_workers(self._max_parallel_dl)
self._accept_download_requests = True
def stop(self, *args: Any, **kwargs: Any) -> None:
"""Stop the download worker threads."""
with self._lock:
if not self._worker_pool:
return
self._accept_download_requests = False # reject attempts to add new jobs to queue
queued_jobs = [x for x in self.list_jobs() if x.status == DownloadJobStatus.WAITING]
active_jobs = [x for x in self.list_jobs() if x.status == DownloadJobStatus.RUNNING]
if queued_jobs:
self._logger.warning(f"Cancelling {len(queued_jobs)} queued downloads")
if active_jobs:
self._logger.info(f"Waiting for {len(active_jobs)} active download jobs to complete")
with self._queue.mutex:
self._queue.queue.clear()View on GitHub (pinned to 0b6a024f2f)
Solutions
- Guard with `if not service._worker_pool: service.start()` or track your own started flag before calling start()
- Call stop() before start() if a restart is intended
- Ensure only one code path owns service lifecycle (single init point in app startup)
Example fix
// before
service.start()
service.start() # raises
// after
if not getattr(service, "_worker_pool", None):
service.start() Defensive patterns
Strategy: try-catch
Validate before calling
def download_service_started(s) -> bool:
return getattr(s, "_worker_pool", None) is not None Type guard
def can_start(s: object) -> bool:
return hasattr(s, "start") and getattr(s, "_worker_pool", None) is None Try / catch
try:
service.start()
except Exception as e:
if "twice" in str(e):
logger.warning("download service already started; ignoring")
else:
raise Prevention
- Start the service in exactly one initialization path (app lifespan)
- Track started state in a wrapper/flag instead of calling start() repeatedly
- In tests, use a fixture that starts the service once per session
- Pair every stop() with a clear restart procedure
When it happens
Trigger: Calling service.start() a second time on an already-started DownloadService instance (e.g. app startup code invoked again, re-import/re-initialization paths, or a hot-reload that re-runs init).
Common situations: Application double-initialization during tests or lifespan handlers, calling start() after a restart script without stop(), or constructing two references to the same service and starting both.
Related errors
- The download service is not currently accepting requests. Pl
- only relative download paths accepted
- Unrecognized job
- Timeout exceeded
- Job was cancelled before start
AI-assisted analysis of invoke-ai/InvokeAI@0b6a024f2f (2026-08-29).
Data as JSON: /api/errors/5af66e405d2e7473.
Report an issue: GitHub.