Skip to content

Refresh check and cache reload

Field Value
Owner CheckForUpdates (locking + reload orchestration) and TestForRefreshSettings/NeedsRefresh (the due-for-refresh probe)
Trigger Every call to Load section (unconditionally, before checking whether the specific section is already loaded)
Inputs Global cache state: Settings (empty or not), LastCheckForRefresh, LastRefreshed, TimeToWaitSeconds (60, set during Initialize environment client)
Outputs true/false — whether a full cache reload was performed during this call

Steps

  1. CheckForUpdates acquires _refreshSettingsLock (a plain lock, not the SectionsSemaphore) for the duration of the check.
  2. It evaluates NeedsRefresh, which calls TestForRefreshSettings:
  3. If the global Settings dictionary is empty (nothing loaded yet, including on the very first call), it immediately returns true — no server probe is made in this case.
  4. Otherwise, if LastCheckForRefresh.AddSeconds(TimeToWaitSeconds) < DateTime.Now (i.e., at least 60 seconds have passed since the last probe) (BR-EC-14), it calls GET /settings/lastRefreshed on the Environment API, sets LastCheckForRefresh = DateTime.Now, and returns true only if the call was successful and the returned server timestamp is later than the client's current LastRefreshed.
  5. Otherwise (probe not yet due) it returns false without any network call.
  6. If NeedsRefresh returned false, CheckForUpdates returns false immediately — no reload.
  7. If NeedsRefresh returned true, CheckForUpdates acquires SectionsSemaphore, then: sets LastRefreshed = DateTime.Now before loading (not after a successful load completes), clears the entire global Settings dictionary, reloads the literal-empty-string "root" section (LoadSettings() with no section argument — this is distinct from the application's named track section; see Load section step 5), and reloads every section that was previously recorded in Sections (iterating a snapshot array of the HashSet, calling LoadSettings(section) for each) — this reset is what allows each section to be loaded from the Environment API again, once, in the new cache generation (BR-EC-13). It then releases the semaphore and returns true.
  8. Because Settings is cleared before the reload loop runs, and any one section's reload can throw EnvironmentClientException (see Load section step 6), a mid-reload failure can leave the global cache empty or only partially repopulated — the whole-cache reload is not atomic. LastRefreshed is always set to the start-of-reload timestamp before the loop runs (step 4), regardless of which branch of step 2 triggered the reload. LastCheckForRefresh, however, is only updated inside the server-probe branch of step 2 (TestForRefreshSettings's LastCheckForRefresh = DateTime.Now assignment) — the empty-Settings branch returns true immediately with no probe and leaves LastCheckForRefresh unchanged. So after an empty-cache-triggered reload, the very next call's probe timing is governed by whatever LastCheckForRefresh already held (likely its unset default), not by the reload's start time. This failure has no guaranteed automatic repair on the next call: once LastRefreshed has been advanced, a subsequent due probe only triggers another reload if the server's lastRefreshed timestamp is itself later than this newly-advanced LastRefreshed — a due probe alone does not guarantee a reload. If a mid-reload failure leaves Settings completely empty, the next call bypasses the probe/throttle entirely (via the empty-cache check) and retries the reload immediately; if Settings is left non-empty, the next call is subject to the normal 60 s probe throttle and may return false (no refresh) until a probe comes due and the server timestamp has newly advanced past the already-updated LastRefreshed. Separately, Sections itself is never cleared by this reload (only Settings is) — if a named section's individual LoadSettings(section) call throws partway through the loop in step 4, that section remains recorded in Sections from before the refresh even though its corresponding Settings entries were just cleared and never restored; see Load section step 7 for the resulting loaded-but-empty, un-retried state.
  9. Whether the GET /settings/lastRefreshed call's body actually deserializes as a DateTime and how a malformed/missing body is handled is determined by the injected IHttpJsonClient/IHttpRequestResult implementation, not by this library** .

Process flow

flowchart TD
    A["CheckForUpdates (every LoadSection call)"] --> B[Acquire _refreshSettingsLock]
    B --> C{Settings dictionary empty?}
    C -- Yes --> D["NeedsRefresh = true (no probe)"]
    C -- No --> E{"60s since LastCheckForRefresh?"}
    E -- No --> F["NeedsRefresh = false"]
    E -- Yes --> G["GET /settings/lastRefreshed"]
    G --> H["LastCheckForRefresh = now"]
    H --> I{"Successful and server time > LastRefreshed?"}
    I -- Yes --> D
    I -- No --> F
    F --> J["Return false: no reload"]
    D --> K[Acquire SectionsSemaphore]
    K --> L["LastRefreshed = now (set before loading)"]
    L --> M[Settings.Clear]
    M --> N["LoadSettings() for root section (literal empty string)"]
    N --> O["LoadSettings(section) for each known Sections entry"]
    O --> P["Release semaphore, return true"]

Note: Statements marked with * were inferred from code structure, naming conventions, or contextual evidence where direct confirmation was not available in source artifacts. Statements marked with ** could not be confirmed; treat as unknown until verified.

Sources

  • src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/BaseEnvironmentClient.cs