Fix resource leaks and disposal issues in fleet alert tray

- Dispose GraphicsPath after creating Region to prevent GDI handle leak
- Track and dispose all Font objects to prevent GDI+ resource leak
- Join worker thread before disposal to ensure client not used after disposal
- Fix Screen.PrimaryScreen null dereference with fallback rectangle
- Propagate cancellation token to FetchAlertsAsync to stop orphaned requests
- Bump tray to 1.6.5, agent to 1.13.4
This commit is contained in:
Proudlock
2026-09-08 18:22:24 -04:00
parent 57f7225195
commit deb2bbe9df
4 changed files with 47 additions and 11 deletions

View File

@@ -16,8 +16,10 @@ internal sealed class FleetAlertForm : Form
private readonly string? hostname;
private System.Windows.Forms.Timer? countdownTimer;
private System.Windows.Forms.Timer? statusCheckTimer;
private readonly CancellationTokenSource statusCheckCancellation = new();
private int remainingSeconds;
private Label? countdownLabel;
private readonly List<Font> fonts = new();
public event EventHandler? OnAcknowledgeClicked;
@@ -35,7 +37,7 @@ internal sealed class FleetAlertForm : Form
ClientSize = new Size(400, 280);
BackColor = Color.White;
var screen = Screen.PrimaryScreen?.WorkingArea ?? Screen.PrimaryScreen!.Bounds;
var screen = Screen.PrimaryScreen?.WorkingArea ?? Screen.PrimaryScreen?.Bounds ?? new Rectangle(0, 0, 1920, 1080);
Location = new Point(screen.Right - Width - 20, screen.Top + 20);
BuildLayout();
@@ -49,6 +51,7 @@ internal sealed class FleetAlertForm : Form
{
countdownTimer?.Stop();
statusCheckTimer?.Stop();
statusCheckCancellation.Cancel();
}
private void FleetAlertForm_Load(object? sender, EventArgs e)
@@ -77,7 +80,7 @@ internal sealed class FleetAlertForm : Form
{
try
{
var alerts = await client.FetchAlertsAsync(hostname, CancellationToken.None)
var alerts = await client.FetchAlertsAsync(hostname, statusCheckCancellation.Token)
.ConfigureAwait(false);
var stillActive = alerts?.Any(a => a.NotificationId == alert.NotificationId) ?? false;
@@ -90,6 +93,10 @@ internal sealed class FleetAlertForm : Form
}));
}
}
catch (OperationCanceledException)
{
// Form is closing, ignore
}
catch
{
// Ignore errors - keep the form open if we can't check status
@@ -99,7 +106,7 @@ internal sealed class FleetAlertForm : Form
private void ApplyRoundedCorners()
{
var path = new GraphicsPath();
using var path = new GraphicsPath();
int radius = 8;
var rect = new Rectangle(0, 0, Width, Height);
@@ -146,6 +153,13 @@ internal sealed class FleetAlertForm : Form
}
}
private Font CreateFont(string family, float size, FontStyle style = FontStyle.Regular)
{
var font = new Font(family, size, style);
fonts.Add(font);
return font;
}
private void BuildLayout()
{
var borderPanel = new Panel
@@ -182,7 +196,7 @@ internal sealed class FleetAlertForm : Form
Left = 16,
Top = 14,
Width = 300,
Font = new Font("Segoe UI", 10, FontStyle.Bold),
Font = CreateFont("Segoe UI", 10, FontStyle.Bold),
ForeColor = Color.FromArgb(51, 51, 51),
};
headerPanel.Controls.Add(typeLabel);
@@ -195,7 +209,7 @@ internal sealed class FleetAlertForm : Form
Width = 24,
Height = 24,
FlatStyle = FlatStyle.Flat,
Font = new Font("Segoe UI", 14),
Font = CreateFont("Segoe UI", 14),
ForeColor = Color.FromArgb(102, 102, 102),
BackColor = Color.Transparent,
Cursor = Cursors.Hand,
@@ -212,7 +226,7 @@ internal sealed class FleetAlertForm : Form
Top = 58,
Width = 360,
Height = 50,
Font = new Font("Segoe UI", 11, FontStyle.Bold),
Font = CreateFont("Segoe UI", 11, FontStyle.Bold),
ForeColor = Color.FromArgb(51, 51, 51),
AutoSize = false,
};
@@ -225,7 +239,7 @@ internal sealed class FleetAlertForm : Form
Top = 115,
Width = 360,
Height = 80,
Font = new Font("Segoe UI", 9.75f),
Font = CreateFont("Segoe UI", 9.75f),
ForeColor = Color.FromArgb(85, 85, 85),
AutoSize = false,
};
@@ -241,7 +255,7 @@ internal sealed class FleetAlertForm : Form
BackColor = Color.FromArgb(76, 175, 80),
ForeColor = Color.White,
FlatStyle = FlatStyle.Flat,
Font = new Font("Segoe UI", 9, FontStyle.Bold),
Font = CreateFont("Segoe UI", 9, FontStyle.Bold),
Cursor = Cursors.Hand,
};
acknowledgeButton.FlatAppearance.BorderSize = 0;
@@ -277,7 +291,7 @@ internal sealed class FleetAlertForm : Form
Width = 395,
Height = 20,
TextAlign = ContentAlignment.MiddleCenter,
Font = new Font("Segoe UI", 8.25f),
Font = CreateFont("Segoe UI", 8.25f),
ForeColor = Color.FromArgb(153, 153, 153),
};
footerPanel.Controls.Add(countdownLabel);
@@ -329,6 +343,13 @@ internal sealed class FleetAlertForm : Form
countdownTimer?.Dispose();
statusCheckTimer?.Stop();
statusCheckTimer?.Dispose();
statusCheckCancellation.Cancel();
statusCheckCancellation.Dispose();
foreach (var font in fonts)
{
font.Dispose();
}
fonts.Clear();
}
base.Dispose(disposing);
}

View File

@@ -183,6 +183,15 @@ internal sealed class FleetAlertWorker : IDisposable
public void Dispose()
{
cancellation.Cancel();
if (workerThread.IsAlive)
{
if (!workerThread.Join(TimeSpan.FromSeconds(5)))
{
logger.LogWarning("Fleet alert worker thread did not exit within timeout.");
}
}
cancellation.Dispose();
}
}

View File

@@ -82,7 +82,11 @@
<!-- 1.6.4: fix UI thread deadlock. Acknowledgment and status checks now run async
properly without blocking the UI thread. Acknowledgment is marked locally first,
then posted to server in background to prevent lockups. -->
<Version>1.6.4</Version>
<!-- 1.6.5: fix resource leaks and disposal issues. GraphicsPath properly disposed,
Font objects tracked and disposed, worker thread joined before client disposal,
Screen.PrimaryScreen null dereference prevented, status check cancellation token
propagated to prevent orphaned HTTP requests. -->
<Version>1.6.5</Version>
<InvariantGlobalization>true</InvariantGlobalization>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained>

View File

@@ -177,7 +177,9 @@
No agent code changes, but deployed together for version consistency. -->
<!-- 1.13.3: coordinated release with tray 1.6.4 (deadlock fix).
No agent code changes, but deployed together for version consistency. -->
<Version>1.13.3</Version>
<!-- 1.13.4: coordinated release with tray 1.6.5 (resource leak fixes).
No agent code changes, but deployed together for version consistency. -->
<Version>1.13.4</Version>
<InvariantGlobalization>true</InvariantGlobalization>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained>