[Bug]: After cover update, cover appears broken until page is reloaded #1695

Closed
opened 2026-04-24 23:55:17 +02:00 by adam · 4 comments
Owner

Originally created by @mikiher on GitHub (Jan 26, 2024).

Describe the issue

Sometimes (seems like this happens more often recently) after a cover update (e.g. when submitting a match result), the book cover appears broken, like this:

Screenshot 2024-01-26 114338

After I reload the page, the problem disappears:

Screenshot 2024-01-26 114458

I suspect there's some race condition between downloading/saving the image and notifying the web-socket that the update is done.

I caught this on a local version built from head on Windows.

Steps to reproduce the issue

  1. Update a book cover (e.g. via match result)
  2. Cover image sometimes appears broken on the page.

Audiobookshelf version

v2.7.2

How are you running audiobookshelf?

Built from source

Originally created by @mikiher on GitHub (Jan 26, 2024). ### Describe the issue Sometimes (seems like this happens more often recently) after a cover update (e.g. when submitting a match result), the book cover appears broken, like this: ![Screenshot 2024-01-26 114338](https://github.com/advplyr/audiobookshelf/assets/22557398/defd27df-b19e-4283-b31c-e437c4d16251) After I reload the page, the problem disappears: ![Screenshot 2024-01-26 114458](https://github.com/advplyr/audiobookshelf/assets/22557398/e8c235ed-69a7-409e-b4a7-fdf6cc23d07a) I suspect there's some race condition between downloading/saving the image and notifying the web-socket that the update is done. I caught this on a local version built from head on Windows. ### Steps to reproduce the issue 1. Update a book cover (e.g. via match result) 2. Cover image sometimes appears broken on the page. ### Audiobookshelf version v2.7.2 ### How are you running audiobookshelf? Built from source
adam added the bug label 2026-04-24 23:55:17 +02:00
adam closed this issue 2026-04-24 23:55:18 +02:00
Author
Owner

@doctordarko commented on GitHub (Jan 29, 2024):

Can confirm the same behaviour on docker version

@doctordarko commented on GitHub (Jan 29, 2024): Can confirm the same behaviour on docker version
Author
Owner

@mikiher commented on GitHub (Jan 31, 2024):

OK, I investigated this and I think I understand what's happening.
There is indeed a race condition that can manifest itself in different ways, or not at all:

  • When I hit Submit in the Match details page submitMatchUpdate in Match.vue is run.
  • If all fields are checked, submitMatchUpdate, triggers two API calls:
    • a POST request for the cover (/api/items/:id/cover)
    • a PATCH request for the rest of the fields (/api/items/:id/media)
  • Each of the API calls triggers a separate item_updated web socket event when done.
  • Each item_updated event, in turn, triggers an update of the corresponding image url on the bookshelf
  • This in turn causes the browser to issue two separate get requests for the same image at roughly the same time (though not with the same url, as the update timestamp is part of the url and that causes a difference). See example:
    Screenshot 2024-01-31 220238
  • Now different things may happen depending on timing, for example:
    • both requests end successfully
    • the two requests try use ffmpeg to resize the image at the same time, but since they both try to write to the same file, one or both ffmpeg calls fail. Example:
      [2024-01-31 09:53:48.349] ERROR: [FfmpegHelpers] Resize Image Error Error: ffmpeg exited with code 4294967283: Error opening output file C:\Users\Mhchael\AppData\Local\Audiobookshelf\metadata\cache\covers\af749f96-b09e-4fa3-8d2f-3464ab3dbf78_400.webp..
      in this case, one of both requests return a 500 error.
    • one request has started but not finished resizing the image, but the cache file is already created on disk (but is not complete). then the other request comes in, sees the the cache file, and returns it, but it is not complete, so browser might show a broken image, depending on timing.

So, long story short, there are a couple of core issues here and a number of possible fixes:

  1. The item_updated web sockets event should not be triggered twice. This can be fixed in various ways, but I think that the right way to do this is by adding an API that merges the cover and media requests into a single request, and using that API instead of the existing two.
  2. LibraryItemController.getCover, and especially the call to CacheManager.handleCoverCache should be protected from trying to access the same resource concurrently. Doing so requires introducing some lock/mutex mechanism which is currently altogether absent from the codebase. However, if 1. is fixed, the probability of concurrent access to the same resource becomes negligible, making it not worthwhile to introduce complex locking unnecessarily.

@advplyr, what do you think, and how would you like me to proceed here?

@mikiher commented on GitHub (Jan 31, 2024): OK, I investigated this and I think I understand what's happening. There is indeed a race condition that can manifest itself in different ways, or not at all: - When I hit Submit in the Match details page `submitMatchUpdate` in `Match.vue` is run. - If all fields are checked, `submitMatchUpdate`, triggers two API calls: - a POST request for the cover (`/api/items/:id/cover`) - a PATCH request for the rest of the fields (`/api/items/:id/media`) - Each of the API calls triggers a *separate* item_updated web socket event when done. - Each item_updated event, in turn, triggers an update of the corresponding image url on the bookshelf - This in turn causes the browser to issue *two separate* get requests for the same image at roughly the same time (though not with the same url, as the update timestamp is part of the url and that causes a difference). See example: ![Screenshot 2024-01-31 220238](https://github.com/advplyr/audiobookshelf/assets/22557398/135dd0a2-707d-451e-846c-efeb9061abad) - Now different things may happen depending on timing, for example: - both requests end successfully - the two requests try use ffmpeg to resize the image at the same time, but since they both try to write to the same file, one or both ffmpeg calls fail. Example: `[2024-01-31 09:53:48.349] ERROR: [FfmpegHelpers] Resize Image Error Error: ffmpeg exited with code 4294967283: Error opening output file C:\Users\Mhchael\AppData\Local\Audiobookshelf\metadata\cache\covers\af749f96-b09e-4fa3-8d2f-3464ab3dbf78_400.webp.`. in this case, one of both requests return a 500 error. - one request has started but not finished resizing the image, but the cache file is already created on disk (but is not complete). then the other request comes in, sees the the cache file, and returns it, but it is not complete, so browser might show a broken image, depending on timing. So, long story short, there are a couple of core issues here and a number of possible fixes: 1. The `item_updated` web sockets event should not be triggered twice. This can be fixed in various ways, but I think that the right way to do this is by adding an API that merges the cover and media requests into a single request, and using that API instead of the existing two. 2. `LibraryItemController.getCover`, and especially the call to `CacheManager.handleCoverCache` should be protected from trying to access the same resource concurrently. Doing so requires introducing some lock/mutex mechanism which is currently altogether absent from the codebase. However, if 1. is fixed, the probability of concurrent access to the same resource becomes negligible, making it not worthwhile to introduce complex locking unnecessarily. @advplyr, what do you think, and how would you like me to proceed here?
Author
Owner

@advplyr commented on GitHub (Feb 1, 2024):

Thanks for tracking this down. I think we should do 1 and use the existing /api/items/${this.libraryItemId}/media endpoint.

Maybe check for coverUrl in req.body

https://github.com/advplyr/audiobookshelf/blob/432e25565e9a4aca43d9c3194f7babe89eb6206b/server/controllers/LibraryItemController.js#L123

Update: I just removed the unnecessary cache purge in that function. That was leftover from back when the coverPath could be updated.

@advplyr commented on GitHub (Feb 1, 2024): Thanks for tracking this down. I think we should do 1 and use the existing `/api/items/${this.libraryItemId}/media` endpoint. Maybe check for `coverUrl` in `req.body` https://github.com/advplyr/audiobookshelf/blob/432e25565e9a4aca43d9c3194f7babe89eb6206b/server/controllers/LibraryItemController.js#L123 Update: I just removed the unnecessary cache purge in that function. That was leftover from back when the coverPath could be updated.
Author
Owner

@advplyr commented on GitHub (Mar 17, 2024):

Fixed in v2.8.1

@advplyr commented on GitHub (Mar 17, 2024): Fixed in [v2.8.1](https://github.com/advplyr/audiobookshelf/releases/tag/v2.8.1)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/audiobookshelf#1695