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

  1. Guard with `if not service._worker_pool: service.start()` or track your own started flag before calling start()
  2. Call stop() before start() if a restart is intended
  3. 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

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


AI-assisted analysis of invoke-ai/InvokeAI@0b6a024f2f (2026-08-29). Data as JSON: /api/errors/5af66e405d2e7473. Report an issue: GitHub.