---
title: Refresh check and cache reload
summary: OP-04 operation detail page.
status: draft
owner: Framework.NCore maintainers
last_reviewed: 2026-10-02
library: SAIF.NetCore.EnvironmentClient
reference_type: package
---

# 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](load-section.md) (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](initialize-environment-client.md)) |
| 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`:
   - 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.
   - Otherwise, if `LastCheckForRefresh.AddSeconds(TimeToWaitSeconds) < DateTime.Now` (i.e., at least 60 seconds have passed since the last probe) ([BR-EC-14](../business-rules.md#br-ec-14-cache-refresh-is-throttled-to-at-most-every-60-seconds)), 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`.
   - Otherwise (probe not yet due) it returns `false` without any network call.
3. If `NeedsRefresh` returned `false`, `CheckForUpdates` returns `false` immediately — no reload.
4. 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](load-section.md) 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](../business-rules.md#br-ec-13-each-section-is-loaded-from-the-environment-api-at-most-once-per-cache-generation)). It then releases the semaphore and returns `true`.
5. Because `Settings` is cleared before the reload loop runs, and any one section's reload can throw `EnvironmentClientException` (see [Load section](load-section.md) 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](load-section.md) step 7 for the resulting loaded-but-empty, un-retried state.
6. 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

```mermaid
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`
