Missing media (404 avatar/banner) throws an unhandled UnimplementedError in the image loader #58

Open
opened 2026-10-06 14:03:15 +00:00 by robocub · 1 comment
Member

Bug: when an avatar, banner or other mxc:// image doesn't exist on the server (deleted media, a typo'd url, a room avatar pointing at purged content), Commet's image loader throws an unhandled UnimplementedError.

Found while testing #48 (Explore rooms): a banner with a missing mxc made the integration test fail with

M_NOT_FOUND: Local thumbnail not found. (MatrixApi.unexpectedResponse)
The following UnimplementedError was thrown running a test (but after the test had completed)
#0 MatrixMxcImage.retryUntilOnline (matrix_mxc_image_provider.dart:74)
#1 LODImageCompleter._loadThumbnail (lod_image.dart:178)

Cause (commet/lib/client/matrix/matrix_mxc_image_provider.dart, commet/lib/utils/image/lod_image.dart):

  1. MatrixMxcImage.retryUntilOnline logs every non-network error at error level, then throws a bare UnimplementedError (the real error is lost).
  2. LODImageCompleter.loadImages() starts _loadThumbnail() / _loadFullRes() without awaiting them and never catches, so the error escapes as an uncaught async error (in release builds: log noise; in tests: a failure blamed on whichever test is running).
  3. thumbnailLoading / fullResLoading are only reset after a successful await, so after one failure every later fetch of that image replays the failed future.

Upstream check (2026-10-06): commetchat/commet has no issue or PR for this. The perf/runaway-image-listener branch only touches image_utils.dart. #508 ("Retry fetching images if connection fails") introduced retryUntilOnline.

Fix plan: rethrow the original error (not UnimplementedError); log a missing-media 404 as a warning, not an error; catch load failures in LODImageCompleter, reset the loading futures in finally, and report them to the image stream with reportError(silent: true) so errorBuilders still see them and the placeholder stays.

**Bug:** when an avatar, banner or other `mxc://` image doesn't exist on the server (deleted media, a typo'd url, a room avatar pointing at purged content), Commet's image loader throws an **unhandled** `UnimplementedError`. Found while testing #48 (Explore rooms): a banner with a missing mxc made the integration test fail with ``` M_NOT_FOUND: Local thumbnail not found. (MatrixApi.unexpectedResponse) The following UnimplementedError was thrown running a test (but after the test had completed) #0 MatrixMxcImage.retryUntilOnline (matrix_mxc_image_provider.dart:74) #1 LODImageCompleter._loadThumbnail (lod_image.dart:178) ``` **Cause** (`commet/lib/client/matrix/matrix_mxc_image_provider.dart`, `commet/lib/utils/image/lod_image.dart`): 1. `MatrixMxcImage.retryUntilOnline` logs every non-network error at error level, then throws a bare `UnimplementedError` (the real error is lost). 2. `LODImageCompleter.loadImages()` starts `_loadThumbnail()` / `_loadFullRes()` without awaiting them and never catches, so the error escapes as an uncaught async error (in release builds: log noise; in tests: a failure blamed on whichever test is running). 3. `thumbnailLoading` / `fullResLoading` are only reset after a successful await, so after one failure every later fetch of that image replays the failed future. **Upstream check (2026-10-06):** commetchat/commet has no issue or PR for this. The `perf/runaway-image-listener` branch only touches `image_utils.dart`. #508 ("Retry fetching images if connection fails") introduced `retryUntilOnline`. **Fix plan:** rethrow the original error (not `UnimplementedError`); log a missing-media 404 as a warning, not an error; catch load failures in `LODImageCompleter`, reset the loading futures in `finally`, and report them to the image stream with `reportError(silent: true)` so `errorBuilder`s still see them and the placeholder stays.
Author
Member

Fixed on branch fix/missing-media (fc1d2ade, based on main; not in testing yet, that needs the operator's go).

  • retryUntilOnline rethrows the real error; M_NOT_FOUND is no longer logged as an app error.
  • LODImageCompleter catches load/decode failures, keeps what's shown (placeholder/blurhash/lower LOD), logs a warning, and clears the in-flight future so the next fetch retries. Failures are deliberately not reported to the image stream: Image widgets without an errorBuilder rethrow stream errors in debug builds, which would just move the crash.

Verification

  • New unit_test/lod_image_test.dart: a failed-then-recovered thumbnail, and a corrupt image. Both fail on the old code and pass with the fix; the whole unit suite (23) passes; flutter analyze shows only the 14 known upstream infos.
  • Original repro: the #48 Explore e2e test with the banner pointed at a nonexistent mxc. Before the fix it failed with the unhandled UnimplementedError; with the fix it passes 3/3, and the log shows only Couldn't load image: M_NOT_FOUND: Local thumbnail not found. / …Local media not found. warnings.
Fixed on branch `fix/missing-media` (fc1d2ade, based on main; **not in testing yet**, that needs the operator's go). - `retryUntilOnline` rethrows the real error; `M_NOT_FOUND` is no longer logged as an app error. - `LODImageCompleter` catches load/decode failures, keeps what's shown (placeholder/blurhash/lower LOD), logs a warning, and clears the in-flight future so the next fetch retries. Failures are deliberately **not** reported to the image stream: `Image` widgets without an `errorBuilder` rethrow stream errors in debug builds, which would just move the crash. **Verification** - New `unit_test/lod_image_test.dart`: a failed-then-recovered thumbnail, and a corrupt image. Both **fail on the old code** and pass with the fix; the whole unit suite (23) passes; `flutter analyze` shows only the 14 known upstream infos. - Original repro: the #48 Explore e2e test with the banner pointed at a nonexistent mxc. Before the fix it failed with the unhandled `UnimplementedError`; with the fix it passes 3/3, and the log shows only `Couldn't load image: M_NOT_FOUND: Local thumbnail not found.` / `…Local media not found.` warnings.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
nether/vommet#58
No description provided.