Skip to content

Cached lookup

This private operation serializes access to the per-instance connection-string cache, returning a cached value when present and populating the cache from a retrieval when it is not. Failed retrievals are never cached.

Field Value
Operation ID OP-DBC-06
Trigger Called internally by GetSqlServerConnection, GetPostgreSqlServerConnection, and GetOracleConnection
Inputs connectionName (cache key), urlPath
Outputs Connection string (or an exception propagated from retrieval)
Primary components ConnectionClient.GetCachedConnectionString (private)
Entry point src/SAIF.NetCore.DbConnectionClient/SAIF.NetCore.DBConnectionClient/SAIF.NetCore.DBConnectionClient/ConnectionClient.cs

Steps

  1. Acquire lock(_connections) (BR-DBC-06). The lock is instance-wide — shared across every cache key, not just connectionName — so a slow or failing retrieval for one key blocks lookups for all other keys on the same instance until it completes*.
  2. If the key is present in the cache, return the cached value immediately and release the lock — no HTTP call is made.
  3. If the key is absent, retrieve it with bounded retry before releasing the lock:
  4. Validate the backoff invariant (RetrieveBackoffDelays.Length >= MaxRetrieveAttempts - 1); if it does not hold, throw InvalidOperationException before any HTTP call (BR-DBC-07).
  5. Loop up to MaxRetrieveAttempts (4, by default) (BR-DBC-08): call HttpClient.GetAsync(urlPath) and read the per-attempt result.
  6. On a successful, non-empty response: decode the legacy ZSPECIALCHAR token (BR-DBC-12), add the result to the cache, and return it.
  7. On HTTP 401, 403, or 404: throw DBClientException immediately, with no retry (BR-DBC-09).
  8. On a 503 status or a non-null ErrorException (network-level failure): treat it as transient (BR-DBC-10) — if attempts remain, wait the fixed backoff delay (2s, then 3s, then 5s) and retry; otherwise throw DBClientException.
  9. On any other non-success status, or a successful-but-empty body: throw DBClientException immediately (BR-DBC-11).
  10. If retrieval throws for any reason, nothing is added to the cache — failures are never cached (BR-DBC-06).
  11. Origin: Steps 3.1–3.5 are implemented in Retrieve with retry and executed on behalf of this operation, including the exact exception messages for each branch.

  12. Return the cached value and release the lock.

Process flow

flowchart TD
    Start([GetCachedConnectionString called]) --> Lock[Acquire instance-wide lock on _connections]
    Lock --> Present{Key present?}
    Present -->|Yes| Return(["Return cached value, release lock"])
    Present -->|No| Validate{Backoff config valid?}
    Validate -->|No| ConfigErr(["Throw InvalidOperationException, release lock"])
    Validate -->|Yes| Get[GetAsync urlPath]
    Get --> Outcome{Outcome}
    Outcome -->|Success, non-empty body| Decode["Decode ZSPECIALCHAR, add to cache"]
    Decode --> Return
    Outcome -->|401/403/404| FailFast(["Throw DBClientException, no retry, release lock"])
    Outcome -->|503 or ErrorException| Remaining{Attempts remaining?}
    Remaining -->|Yes| Delay["Delay 2s/3s/5s"] --> Get
    Remaining -->|No| ThrowTransient(["Throw DBClientException, release lock"])
    Outcome -->|Other non-success or empty body| ImmediateFail(["Throw DBClientException, release lock"])

Failure and edge-case behavior

  • Because the lock is held across the entire retrieval attempt (including HTTP calls and backoff delays), a cold or failing lookup blocks every other caller of the same instance — including lookups for unrelated keys — for the duration of the retry loop*.
  • A successfully retrieved value is cached for the life of the instance and never expires or is invalidated. Refreshing it requires replacing the instance — restarting the usual singleton-hosted process is the typical way to do this. Rotating the outbound NTLM service-account credential is distinct from this cache: it changes the identity presented on the next uncached request but does not clear any already-cached connection string*.

Note: Statements marked with * were inferred from code structure, naming conventions, or contextual evidence where direct confirmation was not available in source artifacts.

Sources

  • src/SAIF.NetCore.DbConnectionClient/SAIF.NetCore.DBConnectionClient/SAIF.NetCore.DBConnectionClient/ConnectionClient.cs