refactor: log silenced UI errors, fix timer leak, narrow exception catch
- EventListShell: detach the Elapsed handler before disposing the refresh timer (both StartTimer and Dispose) to stop a leaked subscription firing on a torn-down component; log the two previously-silent catches. - Insights: log the previously-silent report-load catch. - EventOddsParser: narrow catch(Exception) to catch(ArgumentException) so only the OddsRate/OddsValue/Bet guard-clause throws are swallowed. - AnomalyEvidenceData: make the JSON DTOs init-only per the immutability convention. - Settings: remove a dead DialogParameters block.
This commit is contained in:
@@ -111,36 +111,36 @@ public static class AnomalyEvidenceParser
|
||||
private sealed class EvidenceDto
|
||||
{
|
||||
[JsonPropertyName("suspensionGapSeconds")]
|
||||
public int SuspensionGapSeconds { get; set; }
|
||||
public int SuspensionGapSeconds { get; init; }
|
||||
|
||||
[JsonPropertyName("preSuspension")]
|
||||
public EvidenceSideDto? PreSuspension { get; set; }
|
||||
public EvidenceSideDto? PreSuspension { get; init; }
|
||||
|
||||
[JsonPropertyName("postSuspension")]
|
||||
public EvidenceSideDto? PostSuspension { get; set; }
|
||||
public EvidenceSideDto? PostSuspension { get; init; }
|
||||
}
|
||||
|
||||
private sealed class EvidenceSideDto
|
||||
{
|
||||
[JsonPropertyName("capturedAt")]
|
||||
public DateTimeOffset CapturedAt { get; set; }
|
||||
public DateTimeOffset CapturedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("p1")]
|
||||
public decimal? P1 { get; set; }
|
||||
public decimal? P1 { get; init; }
|
||||
|
||||
[JsonPropertyName("pDraw")]
|
||||
public decimal? PDraw { get; set; }
|
||||
public decimal? PDraw { get; init; }
|
||||
|
||||
[JsonPropertyName("p2")]
|
||||
public decimal? P2 { get; set; }
|
||||
public decimal? P2 { get; init; }
|
||||
|
||||
[JsonPropertyName("rate1")]
|
||||
public decimal? Rate1 { get; set; }
|
||||
public decimal? Rate1 { get; init; }
|
||||
|
||||
[JsonPropertyName("rateDraw")]
|
||||
public decimal? RateDraw { get; set; }
|
||||
public decimal? RateDraw { get; init; }
|
||||
|
||||
[JsonPropertyName("rate2")]
|
||||
public decimal? Rate2 { get; set; }
|
||||
public decimal? Rate2 { get; init; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,8 @@ using Marathon.Domain.Entities;
|
||||
using Marathon.Domain.Enums;
|
||||
using Marathon.Domain.ValueObjects;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using DomainEventId = Marathon.Domain.ValueObjects.EventId;
|
||||
using AngleSharpConfig = AngleSharp.Configuration;
|
||||
using DomainEventId = Marathon.Domain.ValueObjects.EventId;
|
||||
|
||||
namespace Marathon.Infrastructure.Scraping.Parsers;
|
||||
|
||||
@@ -517,8 +516,11 @@ public sealed partial class EventOddsParser : IEventOddsParser
|
||||
value.HasValue ? new OddsValue(value.Value) : null,
|
||||
new OddsRate(rate)));
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
// OddsValue / OddsRate / Bet guard clauses throw ArgumentException and its
|
||||
// derivatives (ArgumentNullException, ArgumentOutOfRangeException). Catch
|
||||
// only those — anything else is a real bug that must not be swallowed here.
|
||||
_logger.LogDebug(ex,
|
||||
"Skipping bet ({Type}, {Side}, value={Value}, rate={Rate}) — invariant violation.",
|
||||
type, side, value, rate);
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
@inject IStringLocalizer<SharedResource> L
|
||||
@inject IAnomalyInsightsService InsightsService
|
||||
@inject NavigationManager Nav
|
||||
@inject ILogger<Insights> Logger
|
||||
|
||||
<PageTitle>@L["App.Title"] · @L["Nav.Insights"]</PageTitle>
|
||||
|
||||
@@ -658,8 +659,9 @@
|
||||
_vm = report;
|
||||
}
|
||||
catch (OperationCanceledException) { /* superseded */ }
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Insights: failed to build the anomaly outcome report.");
|
||||
_errored = true;
|
||||
_vm = null;
|
||||
}
|
||||
|
||||
@@ -282,13 +282,6 @@
|
||||
|
||||
private async Task<bool> ConfirmAsync()
|
||||
{
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
["ContentText"] = L["Settings.Confirm.Body"].Value,
|
||||
["ButtonText"] = L["Settings.Action.Save"].Value,
|
||||
["CancelText"] = L["Common.Cancel"].Value,
|
||||
};
|
||||
|
||||
var result = await Dialogs.ShowMessageBox(
|
||||
title: L["Settings.Confirm.Title"],
|
||||
message: L["Settings.Confirm.Body"],
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
@using DomainEventId = Marathon.Domain.ValueObjects.EventId
|
||||
@implements IDisposable
|
||||
@inject IStringLocalizer<SharedResource> L
|
||||
@inject ILogger<EventListShell> Logger
|
||||
|
||||
<section class="m-shell">
|
||||
<header class="m-rise m-rise-1" style="display: grid; gap: var(--m-space-3); max-width: 880px;">
|
||||
@@ -364,7 +365,11 @@
|
||||
|
||||
private void StartTimer()
|
||||
{
|
||||
_refreshTimer?.Dispose();
|
||||
if (_refreshTimer is not null)
|
||||
{
|
||||
_refreshTimer.Elapsed -= OnRefreshTimerElapsed;
|
||||
_refreshTimer.Dispose();
|
||||
}
|
||||
var interval = Math.Max(5, AutoRefreshSeconds) * 1000.0;
|
||||
_refreshTimer = new System.Timers.Timer(interval) { AutoReset = true };
|
||||
_refreshTimer.Elapsed += OnRefreshTimerElapsed;
|
||||
@@ -383,10 +388,11 @@
|
||||
{
|
||||
await InvokeAsync(LoadAsync);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Swallowed — LoadAsync already handles its own errors; this catch
|
||||
// is the last line of defense for InvokeAsync itself.
|
||||
// Last line of defense for InvokeAsync itself — LoadAsync handles its
|
||||
// own errors. Log rather than silently dropping the failure.
|
||||
Logger.LogError(ex, "EventListShell ({Surface}): auto-refresh tick failed.", Surface);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -414,9 +420,10 @@
|
||||
{
|
||||
// Swallow — superseded by a newer load.
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Hide errors from the UI; Phase 9 will add a snackbar.
|
||||
// Degrade gracefully (clear the rows) but record the failure for diagnosis.
|
||||
Logger.LogError(ex, "EventListShell ({Surface}): failed to load event rows.", Surface);
|
||||
_rows = new List<EventListItem>();
|
||||
}
|
||||
finally
|
||||
@@ -523,7 +530,11 @@
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_refreshTimer?.Dispose();
|
||||
if (_refreshTimer is not null)
|
||||
{
|
||||
_refreshTimer.Elapsed -= OnRefreshTimerElapsed;
|
||||
_refreshTimer.Dispose();
|
||||
}
|
||||
_searchCts?.Cancel();
|
||||
_searchCts?.Dispose();
|
||||
_loadCts?.Cancel();
|
||||
|
||||
Reference in New Issue
Block a user