Skip to content

TransportOptionsFactory permanently caches default options when queue doesn't exist at first resolution #120

@blehnen

Description

@blehnen

Problem

PostgreSqlMessageQueueTransportOptionsFactory.Create() (and peer factories for SqlServer, SQLite, LiteDb, Redis) lazily load transport options from the DB/store and cache the result. When the queue's configuration table/record doesn't exist at first resolution, the factory falls through to a fresh new PostgreSqlMessageQueueTransportOptions() with all defaults, and caches that default for the lifetime of the container. There is no refresh / re-load mechanism if the queue is subsequently created.

Any consumer of IBaseTransportOptions resolved through this factory is stuck on defaults, even after the queue comes into existence. This affects every option on the transport — EnableHistory, EnableStatus, EnableHeartBeat, EnableDelayedProcessing, EnableRoute, and so on.

Reproduction

  • Start the dashboard container with a connection string targeting a queue that doesn't exist yet.
  • Anything in the dashboard-admin container that resolves IBaseTransportOptions (e.g., maintenance service startup, various decorator chains) now has defaults cached.
  • Create the queue with non-default options via the producer/consumer app.
  • The dashboard still sees defaults — the factory will not reload.

Shipped workaround right now: restart the dashboard after queue creation.

Surfaced via #119 (history reads)

Fix in PR #119 removes the EnableHistory read-side dependency, which fixes the user-visible history bug without resolving this root-cause. Other options remain affected (though they're less frequently consumed from the dashboard side).

Affected files

  • Source/DotNetWorkQueue.Transport.PostgreSQL/Basic/Factory/PostgreSqlMessageQueueTransportOptionsFactory.cs
  • Source/DotNetWorkQueue.Transport.SqlServer/Basic/Factory/SqlServerMessageQueueTransportOptionsFactory.cs
  • Source/DotNetWorkQueue.Transport.SQLite/Basic/Factory/SqLiteMessageQueueTransportOptionsFactory.cs
  • Source/DotNetWorkQueue.Transport.LiteDB/Basic/Factory/LiteDbMessageQueueTransportOptionsFactory.cs
  • Source/DotNetWorkQueue.Transport.Redis/Basic/Factory/RedisMessageQueueTransportOptionsFactory.cs

(all have the same cache-on-null pattern — one line near if (_options == null) _options = new ...())

Proposed approaches

  1. Don't cache the default-fallback — retry loading on each Create() call when the previous attempt returned defaults. Costs one DB/store lookup per resolve; acceptable if resolution frequency is low. The happy path (options loaded successfully) still caches.
  2. Time-boxed cache — cache loaded options with a TTL; re-load on expiry. More complex; risks staleness windows but avoids per-call DB hits.
  3. Event-driven invalidation — add a "config table now exists" signal (e.g., cache invalidation when the admin container sees a newly appearing table). Highest complexity.

Lean: option 1 — it's the smallest, matches "correct and complete" preference, and the extra DB lookup only happens while options are defaults (transient state).

Acceptance

  • Integration test: start a container against a non-existent queue, resolve IBaseTransportOptions once, create the queue with non-default options, re-resolve — asserts the factory now returns the non-default options.
  • All five transport option factories covered.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions