TechnitiumSoftware/DnsServer · error · DnsServerException

Unable to load daily stats.

Error message

Unable to load daily stats.

What it means

Thrown inside LoadDailyStats after a race: the code just built a fresh dailyStats, TryAdd failed (another thread inserted one first), and the follow-up TryGetValue also failed to retrieve it. Both the add and the lookup losing means the cache is being mutated concurrently in a way that removed the entry between the two calls. It is a defensive guard against an impossible-under-normal-locking cache state rather than a user-configurable error.

Source

Thrown at DnsServerCore/Dns/StatsManager.cs:573

                    for (int hour = 0; hour < 24; hour++) //hours
                    {
                        HourlyStats hourlyStats = LoadHourlyStats(dailyDateTime.AddHours(hour), ifNotExistsReturnEmptyHourlyStats: true);
                        dailyStats.Merge(hourlyStats.HourStat);
                    }

                    if (dailyStats.TotalQueries > 0)
                    {
                        _ = dailyStats.Truncate(DAILY_STATS_FILE_TOP_LIMIT);
                        SaveDailyStats(dailyDateTime, dailyStats);
                        GC.Collect();
                    }
                }

                if (!_dailyStatsCache.TryAdd(dailyDateTime, dailyStats))
                {
                    if (!_dailyStatsCache.TryGetValue(dailyDateTime, out dailyStats))
                        throw new DnsServerException("Unable to load daily stats.");
                }
            }

            return dailyStats;
        }

        private void SaveHourlyStats(DateTime dateTime, HourlyStats hourlyStats)
        {
            string hourlyStatsFile = Path.Combine(_statsFolder, dateTime.ToString("yyyyMMddHH", CultureInfo.InvariantCulture) + ".stat");

            try
            {
                using (FileStream fS = new FileStream(hourlyStatsFile, FileMode.Create, FileAccess.Write))
                {
                    hourlyStats.WriteTo(new BinaryWriter(fS));
                }
            }
            catch (Exception ex)

View on GitHub (pinned to d0484b6c1e)

Solutions

  1. Retry the stats read once; transient cache races usually resolve on the second call.
  2. Avoid toggling MaxStatFileDays between 0 and non-zero while the dashboard is polling, since that enables/disables the cleanup timer that can clear the cache.
  3. If it persists, restart the server to reset the stats cache; the underlying .stat files on disk are the source of truth and will be re-read.
  4. Report as a bug if reproducible, since both TryAdd and TryGetValue failing implies concurrent cache mutation that LoadDailyStats does not currently lock against.
Defensive patterns

Strategy: retry

Try / catch

// Retry once; the race is transient.
StatCounter daily;
for (int attempt = 0; attempt < 2; attempt++)
{
    try { daily = statsManager.LoadDailyStats(date); break; }
    catch (DnsServerException ex) when (ex.Message == "Unable to load daily stats." && attempt == 0)
    { continue; }
}

Prevention

When it happens

Trigger: Concurrent calls to LoadDailyStats for the same date where one thread's TryAdd succeeds, the entry is then evicted/cleared (e.g. by MaxStatFileDays cleanup or a cache Clear), and the second thread's TryGetValue returns false. Extremely rare; indicates a cache-clear racing with load.

Common situations: Stats cleanup (MaxStatFileDays) clearing _dailyStatsCache while the dashboard requests stats for a day being loaded; concurrent dashboard refreshes during a stats reset; high concurrency plus aggressive MaxStatFileDays=0 toggling.

Related errors


AI-assisted analysis of TechnitiumSoftware/DnsServer@d0484b6c1e (2026-08-13). Data as JSON: /api/errors/e168568ca4da1cd8. Report an issue: GitHub.