{"record":{"id":"e168568ca4da1cd8","repo":"TechnitiumSoftware/DnsServer","slug":"unable-to-load-daily-stats","errorCode":null,"errorMessage":"Unable to load daily stats.","messagePattern":"Unable to load daily stats\\.","errorType":"exception","errorClass":"DnsServerException","httpStatus":null,"severity":"error","filePath":"DnsServerCore/Dns/StatsManager.cs","lineNumber":573,"sourceCode":"\n                    for (int hour = 0; hour < 24; hour++) //hours\n                    {\n                        HourlyStats hourlyStats = LoadHourlyStats(dailyDateTime.AddHours(hour), ifNotExistsReturnEmptyHourlyStats: true);\n                        dailyStats.Merge(hourlyStats.HourStat);\n                    }\n\n                    if (dailyStats.TotalQueries > 0)\n                    {\n                        _ = dailyStats.Truncate(DAILY_STATS_FILE_TOP_LIMIT);\n                        SaveDailyStats(dailyDateTime, dailyStats);\n                        GC.Collect();\n                    }\n                }\n\n                if (!_dailyStatsCache.TryAdd(dailyDateTime, dailyStats))\n                {\n                    if (!_dailyStatsCache.TryGetValue(dailyDateTime, out dailyStats))\n                        throw new DnsServerException(\"Unable to load daily stats.\");\n                }\n            }\n\n            return dailyStats;\n        }\n\n        private void SaveHourlyStats(DateTime dateTime, HourlyStats hourlyStats)\n        {\n            string hourlyStatsFile = Path.Combine(_statsFolder, dateTime.ToString(\"yyyyMMddHH\", CultureInfo.InvariantCulture) + \".stat\");\n\n            try\n            {\n                using (FileStream fS = new FileStream(hourlyStatsFile, FileMode.Create, FileAccess.Write))\n                {\n                    hourlyStats.WriteTo(new BinaryWriter(fS));\n                }\n            }\n            catch (Exception ex)","sourceCodeStart":555,"sourceCodeEnd":591,"githubUrl":"https://github.com/TechnitiumSoftware/DnsServer/blob/d0484b6c1e7439cdc53d67d81e9c876cda2ad756/DnsServerCore/Dns/StatsManager.cs#L555-L591","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Retry the stats read once; transient cache races usually resolve on the second call.","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.","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.","Report as a bug if reproducible, since both TryAdd and TryGetValue failing implies concurrent cache mutation that LoadDailyStats does not currently lock against."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Retry once; the race is transient.\nStatCounter daily;\nfor (int attempt = 0; attempt < 2; attempt++)\n{\n    try { daily = statsManager.LoadDailyStats(date); break; }\n    catch (DnsServerException ex) when (ex.Message == \"Unable to load daily stats.\" && attempt == 0)\n    { continue; }\n}","preventionTips":["Do not toggle MaxStatFileDays between 0 and non-zero while the dashboard polls.","Avoid concurrent stats resets during dashboard refreshes.","Restart the server if the error persists; .stat files on disk are the source of truth."],"tags":["stats","cache","race-condition","concurrency","daily-stats"],"backgroundTag":null,"analyzedSha":"d0484b6c1e7439cdc53d67d81e9c876cda2ad756","analyzedAt":"2026-08-13T22:57:35.508Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}