Title
DbContext concurrency exception in DownloadClientQueuePoller causes "Using cached queue data" and downstream "Import Blocked" failures
Summary
On v1.2.2-canary (commit 4555ad2, the #599 queue-polling refactor), the queue poller for a download client (observed with both SABnzbd and qBittorrent configured simultaneously) repeatedly throws an EF Core concurrency exception:
System.InvalidOperationException: A second operation was started on this context instance before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext.
at Listenarr.Application.Downloads.Queue.DownloadClientQueuePoller.FetchAsync(DownloadClientConfiguration client, TimeSpan clientQueueTimeout, TimeSpan staleSnapshotMaxAge) in /home/runner/work/Listenarr/Listenarr/listenarr.application/Downloads/Queue/DownloadClientQueuePoller.cs:line 90
This is immediately followed by a secondary cascade error on the same connection:
[ERR] [Microsoft.EntityFrameworkCore.Database.Command] Failed executing DbCommand (0ms) [Parameters=[@downloadClientId='?' (Size = 23)], CommandType='Text', CommandTimeout='30']
System.InvalidOperationException: ExecuteReader can only be called when the connection is open.
The UI surfaces this as:
Using cached queue data — SABnzbd using cached data 5s/10s old after a client error
This repeats on every poll cycle (every ~5s), so the client is treated as permanently degraded even though the actual SABnzbd/qBittorrent API calls succeed (visible a few lines earlier in the log: Retrieved 1 total items from SABnzbd (queue + history)).
Suspected root cause
DownloadClientQueuePoller.FetchAsync appears to reuse a single injected DbContext instance across concurrent polling tasks (one per configured download client, e.g. SABnzbd + qBittorrent polled in parallel). EF Core's DbContext is not thread-safe; concurrent use throws ConcurrentMethodInvocation. This looks like a regression from the v1.2.2-canary "split queue display and monitor polling contracts" refactor (PR #599), since it introduces separate/parallel polling paths per client.
Possible downstream impact
We suspect this concurrency failure is also a contributing cause of the still-recurring "Import Blocked" / Inconsistency: Download has no path set failures (#631), even after the #632 fix. If a poll cycle throws before it finishes writing Download.DownloadPath from the SABnzbd/qBittorrent history storage field, the import processor can still pick up the download in an inconsistent state on the next cycle. We observed this exact sequence twice after upgrading to v1.2.2-canary: SABnzbd/qBittorrent reports storage/completed correctly, but Download.finalPath stays empty and the item is stuck in ImportBlocked with importAttempts: 0 forever (no automatic retry).
Environment
- Listenarr: v1.2.2-canary (
ghcr.io/listenarrs/listenarr:canary, image built 2026-06-30T20:34:27Z, commit 4555ad2 / build 3b25386)
- Download clients configured simultaneously: SABnzbd 5.x and qBittorrent (both enabled at the same time)
- Docker Desktop on Windows, all services on a shared bridge network
Steps to reproduce
- Configure both a SABnzbd and a qBittorrent download client in Listenarr at the same time (both enabled).
- Let Listenarr poll both clients' queues concurrently for a few minutes.
- Observe the Activity page banner "Using cached queue data" appearing repeatedly, and the corresponding EF Core concurrency exception in the logs.
- Separately, a completed download's
finalPath may remain empty and the item gets stuck in ImportBlocked despite the source download client reporting a valid, verified storage path (verified independently via each download client's own API).
Expected behavior
- Each concurrent poll of a download client should use its own scoped
DbContext instance (e.g. via IDbContextFactory<T> or a fresh scope per polling task), so parallel polling of multiple clients does not throw.
- A failed poll cycle should not leave the
Download record in a state where DownloadPath/finalPath never gets populated on a later, successful cycle (currently importAttempts stays at 0 forever and there is no way to manually retry an ImportBlocked download from the UI or API — only GET/DELETE are available on /api/v1/downloads/{id}).
Logs
2026-07-28 14:08:33.104 +02:00 [INF] [Listenarr.Infrastructure.DownloadClients.Sabnzbd.SabnzbdAdapter] Retrieved 1 total items from SABnzbd (queue + history)
2026-07-28 14:08:33.104 +02:00 [ERR] [Microsoft.EntityFrameworkCore.Query] An exception occurred while iterating over the results of a query for context type 'Listenarr.Infrastructure.Persistence.ListenArrDbContext'.
System.InvalidOperationException: A second operation was started on this context instance before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.
2026-07-28 14:08:33.105 +02:00 [WRN] [Listenarr.Application.Downloads.Queue.DownloadClientQueuePoller] Error getting queue snapshot from download client SABnzbd
at Listenarr.Application.Downloads.Queue.DownloadClientQueuePoller.FetchAsync(DownloadClientConfiguration client, TimeSpan clientQueueTimeout, TimeSpan staleSnapshotMaxAge) in /home/runner/work/Listenarr/Listenarr/listenarr.application/Downloads/Queue/DownloadClientQueuePoller.cs:line 90
2026-07-28 14:08:33.154 +02:00 [INF] [Listenarr.Application.Downloads.Queue.DownloadQueueService] Client SABnzbd has 1 queue items
2026-07-28 14:08:33.155 +02:00 [WRN] [Listenarr.Application.Downloads.Queue.DownloadQueueService] Using cached queue snapshot for client SABnzbd (age: 5.0s, reason: error)
Related: #631, #632
Title
DbContext concurrency exception in DownloadClientQueuePoller causes "Using cached queue data" and downstream "Import Blocked" failures
Summary
On v1.2.2-canary (commit 4555ad2, the #599 queue-polling refactor), the queue poller for a download client (observed with both SABnzbd and qBittorrent configured simultaneously) repeatedly throws an EF Core concurrency exception:
This is immediately followed by a secondary cascade error on the same connection:
The UI surfaces this as:
This repeats on every poll cycle (every ~5s), so the client is treated as permanently degraded even though the actual SABnzbd/qBittorrent API calls succeed (visible a few lines earlier in the log:
Retrieved 1 total items from SABnzbd (queue + history)).Suspected root cause
DownloadClientQueuePoller.FetchAsyncappears to reuse a single injectedDbContextinstance across concurrent polling tasks (one per configured download client, e.g. SABnzbd + qBittorrent polled in parallel). EF Core'sDbContextis not thread-safe; concurrent use throwsConcurrentMethodInvocation. This looks like a regression from the v1.2.2-canary "split queue display and monitor polling contracts" refactor (PR #599), since it introduces separate/parallel polling paths per client.Possible downstream impact
We suspect this concurrency failure is also a contributing cause of the still-recurring "Import Blocked" /
Inconsistency: Download has no path setfailures (#631), even after the #632 fix. If a poll cycle throws before it finishes writingDownload.DownloadPathfrom the SABnzbd/qBittorrent historystoragefield, the import processor can still pick up the download in an inconsistent state on the next cycle. We observed this exact sequence twice after upgrading to v1.2.2-canary: SABnzbd/qBittorrent reportsstorage/completed correctly, butDownload.finalPathstays empty and the item is stuck inImportBlockedwithimportAttempts: 0forever (no automatic retry).Environment
ghcr.io/listenarrs/listenarr:canary, image built 2026-06-30T20:34:27Z, commit 4555ad2 / build 3b25386)Steps to reproduce
finalPathmay remain empty and the item gets stuck inImportBlockeddespite the source download client reporting a valid, verifiedstoragepath (verified independently via each download client's own API).Expected behavior
DbContextinstance (e.g. viaIDbContextFactory<T>or a fresh scope per polling task), so parallel polling of multiple clients does not throw.Downloadrecord in a state whereDownloadPath/finalPathnever gets populated on a later, successful cycle (currentlyimportAttemptsstays at 0 forever and there is no way to manually retry anImportBlockeddownload from the UI or API — onlyGET/DELETEare available on/api/v1/downloads/{id}).Logs
Related: #631, #632