Skip to content

fix: splash hangs forever on "Loading Cargo Data" when a cargo download fails#127

Open
raman78 wants to merge 1 commit into
STOCD:mainfrom
raman78:fix/cargo-loading-freeze-on-fresh-install
Open

fix: splash hangs forever on "Loading Cargo Data" when a cargo download fails#127
raman78 wants to merge 1 commit into
STOCD:mainfrom
raman78:fix/cargo-loading-freeze-on-fresh-install

Conversation

@raman78
Copy link
Copy Markdown
Contributor

@raman78 raman78 commented May 28, 2026

Summary

On a fresh install (and occasionally on updates), the splash screen gets stuck on "Loading Cargo Data..." with no error and no progress. Users have reported this on Discord — the workaround so far has been to hand-copy somebody else's cargo/*.json files, which masks the real bug.

The freeze is not a slow download. It's three small issues in the cargo loading path that combine into one silent hang.

Root cause

  1. CargoManager.get_cargo_data can silently return None.
    When the wiki download fails and the GitHub cache fallback fails and there are no local backup files (the situation on every fresh install), the function falls through the # TODO what happens when both backups fail? branch without a return. Callers like cache_boff_data then do for boff_ability in boff_cargo: and crash with TypeError: 'NoneType' is not iterable.

  2. Downloader.fetch_json only catches Timeout and JSONDecodeError.
    Anything else requests can raise — ConnectionError, SSLError, ChunkedEncodingError, Cloudflare returning a 5xx with a malformed body, etc. — propagates straight up.

  3. widgets.Thread.run doesn't catch exceptions from its target.
    init_backend runs inside this QThread. If the target raises (because of 1 or 2), self.done.emit() is never reached. complete_app_init is wired to done, so it never runs and the splash stays on "Loading Cargo Data..." forever, with no error in the UI.

The reason it tends to hit later cargo tables (boff abilities, doffs, modifiers) is just that those queries are larger/slower on the wiki and so more prone to timing out or being rate-limited. Once one fails, the whole splash freezes.

Fix

Three minimal changes, one per file:

src/cargomanager.py — replace the TODO with a real error so the caller can surface it instead of crashing on None:

-                # TODO what happens when both backups fail?
+                # Download failed and no usable backup exists (typical on a
+                # fresh install when the wiki/GitHub cache is unreachable).
+                # Raise so the caller surfaces a real error instead of silently
+                # returning None — which would crash with TypeError further up.
+                raise RuntimeError(
+                    f"Could not obtain cargo table '{filename}': download "
+                    f"failed and no backup is available.")

src/downloader.py — widen the exception net to all requests failures:

-from requests.exceptions import Timeout
+from requests.exceptions import RequestException
...
-        except (Timeout, JSONDecodeError):
+        except (RequestException, JSONDecodeError, ValueError):
             return None

src/widgets.py — make Thread emit done even when the target raises, and add an error signal so failures can be surfaced:

     result: Signal = Signal(object)
     done: Signal = Signal()
+    error: Signal = Signal(object)
...
     @Slot()
     def run(self):
-        self.result.emit(self._target(*self._args, **self._kwargs))
-        self.done.emit()
+        try:
+            self.result.emit(self._target(*self._args, **self._kwargs))
+        except BaseException as exc:
+            import traceback
+            traceback.print_exc()
+            self.error.emit(exc)
+        finally:
+            self.done.emit()

Verification

Used a small repro harness (not included in the PR) that drives each failure path without launching the GUI:

# Scenario Before After
1 Fresh install, downloader returns None, no backups get_cargo_data returns None, caller TypeErrors RuntimeError with a clear message
2 requests.Session.get raises ConnectionError exception propagates, thread dies fetch_json returns None
3 Thread target raises done never emitted, splash hangs done emitted, traceback printed

In real terms: after this change, a transient wiki/Cloudflare failure during cargo load still fails — but it fails visibly (traceback in stderr, done fires, app can move forward / show an error) instead of silently freezing the splash.

Out of scope

  • Surfacing the Thread.error signal in the splash UI / showing a user-facing message box. Worth doing as a follow-up, but kept separate so this change stays focused on the freeze itself.
  • Retry/backoff for transient wiki failures.

…ad fails

Three small, related issues conspire to freeze the splash screen on the
"Loading Cargo Data..." step when any cargo table fails to download:

- get_cargo_data falls through to an implicit None when the network
  download fails and no backup exists (typical on a fresh install),
  causing the caller to TypeError while iterating None.
- Downloader.fetch_json only catches Timeout/JSONDecodeError, so other
  requests failures (ConnectionError, SSLError, ChunkedEncodingError,
  Cloudflare-style malformed responses) propagate uncaught.
- widgets.Thread.run does not catch exceptions from its target, so any
  failure above prevents the `done` signal from ever firing — the splash
  stays on "Loading Cargo Data..." with no error shown.

Fix all three so a cargo failure becomes a real, attributable error
instead of a permanent freeze.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant