Skip to content

fix: UI timer disposal, PerformanceCounter blocking, and diagnostics - #23

Merged
beenycool merged 6 commits into
masterfrom
fix/ui-timer-perf
Mar 20, 2026
Merged

fix: UI timer disposal, PerformanceCounter blocking, and diagnostics#23
beenycool merged 6 commits into
masterfrom
fix/ui-timer-perf

Conversation

@beenycool

@beenycool beenycool commented Mar 17, 2026

Copy link
Copy Markdown
Owner

Summary

  • Fix timerRefreshStats to dispose timer before replacing (memory leak fix)
  • Wrap PerformanceCounter.NextValue() in Task.Run to avoid UI blocking
  • Add Debug.WriteLine diagnostics to all catch blocks
  • Enforce minimum 2 points when deleting fan curve points
  • Update UI callers to use thread-safe FanCurve APIs

Summary by CodeRabbit

  • New Features

    • Added synchronous reset control option for fan management
  • Bug Fixes

    • Enhanced error logging and diagnostics for improved troubleshooting
    • Added minimum point protection to prevent accidental fan curve deletion
  • Improvements

    • Strengthened thread-safety in fan curve operations
    • Optimized application resource management during startup and shutdown
    • Improved exception handling throughout the application

@coderabbitai

coderabbitai Bot commented Mar 17, 2026

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: e3cc2878-23da-4c8b-8711-ae5bbd191b80

📥 Commits

Reviewing files that changed from the base of the PR and between b2eaf24 and cc36cd1.

📒 Files selected for processing (6)
  • AsusFanControl.Core/AsusControl.cs
  • AsusFanControl.Core/FanCurve.cs
  • AsusFanControl.Core/IFanController.cs
  • AsusFanControlGUI/FanCurveControl.cs
  • AsusFanControlGUI/FanCurveEditor.cs
  • AsusFanControlGUI/Form1.cs

Walkthrough

The changes introduce thread-safe, encapsulated point management in FanCurve with new mutation APIs, refactor instance lifecycle control in AsusControl to initialize/shutdown WinIO only once across all instances, add a synchronous ResetToDefault() method alongside the async version, and enhance exception handling and logging throughout.

Changes

Cohort / File(s) Summary
Core Library Lifecycle Management
AsusFanControl.Core/AsusControl.cs, AsusFanControl.Core/IFanController.cs
Introduced instance counting with _instanceCount to ensure WinIO initialization/shutdown runs only once across multiple instances; refactored disposal to cancel monitor task, wait up to 2 seconds, and conditionally shutdown; added public synchronous ResetToDefault() method and changed ResetToDefaultAsync() to delegate to it; added _disposed checks inside locked sections; enhanced error logging with Debug.WriteLine.
Thread-Safe Data Encapsulation
AsusFanControl.Core/FanCurve.cs
Replaced mutable public Points list with private locked _points field; exposed Points as IReadOnlyList returning snapshot copies; added mutating APIs: SetPoints(), AddPoint(), RemovePointAt(), UpdatePointAt(), ClearPoints(), and PointCount property; updated GetTargetSpeed() and ToString() to read from locked snapshots.
UI Component Updates
AsusFanControlGUI/FanCurveControl.cs, AsusFanControlGUI/FanCurveEditor.cs
Updated to use new FanCurve mutation APIs: replaced direct Points manipulation with AddPoint() and SetPoints() calls; added point-removal guard to prevent deletion when fewer than three points exist.
Application Entry Point
AsusFanControlGUI/Form1.cs
Replaced silent exception handlers with explicit Debug.WriteLine logging; changed reset behavior to call synchronous ResetToDefault() instead of async variant in exception handlers and exit paths; updated fan-curve initialization to use PointCount and AddPoint(); improved timer cleanup with explicit Dispose() calls; changed CPU load sampling in logging to await Task.Run().

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 The points now safely locked away,
Lifecycle dances day by day,
WinIO waits for the very last—
One instance starts, one finishes fast!
Thread-safe and async, a synchronized gleam,

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/ui-timer-perf
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the application's stability, performance, and robustness. It addresses critical issues such as memory leaks and UI unresponsiveness, while also improving error reporting through comprehensive diagnostics. Furthermore, it introduces thread-safe mechanisms for fan curve management and hardware interaction, laying a more resilient foundation for future development.

Highlights

  • UI Timer Disposal: Fixed a memory leak by ensuring proper disposal of the UI timer in timerRefreshStats before replacing it.
  • PerformanceCounter Blocking: Prevented UI blocking by wrapping PerformanceCounter.NextValue() calls in Task.Run to execute them asynchronously.
  • Enhanced Diagnostics: Improved error visibility by adding Debug.WriteLine diagnostics to all catch blocks across the application.
  • Fan Curve Point Enforcement: Enforced a minimum of two points when deleting fan curve points in the UI, preventing invalid configurations.
  • Thread-Safe Fan Curve APIs: Updated UI components to utilize newly implemented thread-safe FanCurve APIs for managing fan curve points.
Changelog
  • AsusFanControl.Core/AsusControl.cs
    • Added System.Diagnostics import.
    • Implemented thread-safe hardware access with _hwLock and instance counting.
    • Refactored Dispose for safe shutdown and error logging.
    • Updated fan speed and temperature retrieval methods for thread safety.
    • Introduced _fanCount for efficient fan count access.
    • Modified ResetToDefault for thread-safe operation and error handling.
  • AsusFanControl.Core/FanCurve.cs
    • Implemented thread-safe access to fan curve points using a lock.
    • Exposed fan curve points as IReadOnlyList.
    • Modified GetTargetSpeed to handle unsorted points and use thread-safe access.
    • Added new thread-safe methods for managing fan curve points (SetPoints, AddPoint, RemovePointAt, UpdatePointAt, PointCount, ClearPoints).
    • Updated FromString to use SetPoints.
  • AsusFanControl.Core/IFanController.cs
    • Added System.Threading.Tasks import.
    • Introduced ResetToDefaultAsync() to the interface.
  • AsusFanControlGUI/FanCurveControl.cs
    • Updated SetCurve to use the new IReadOnlyList for fan curve points.
    • Modified GetCurve to use the new AddPoint method.
    • Enforced a minimum of two points when deleting fan curve points in the UI.
  • AsusFanControlGUI/FanCurveEditor.cs
    • Updated the constructor to use the new SetPoints method for cloning fan curves.
  • AsusFanControlGUI/Form1.cs
    • Added System.Threading.Tasks import and isLoggingWriting flag.
    • Enhanced error logging with Debug.WriteLine in various catch blocks.
    • Updated default fan curve initialization to use PointCount.
    • Ensured timerRefreshStats disposes the timer.
    • Made LoggingTimer_Tick asynchronous, added a re-entrancy guard, wrapped PerformanceCounter.NextValue() in Task.Run, and used WriteLineAsync.
Activity
  • No specific activity has been recorded for this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces several important fixes and improvements, including resolving a memory leak in timerRefreshStats, preventing UI blocking from PerformanceCounter, enhancing thread safety in AsusControl and FanCurve, and adding more detailed diagnostics. The changes are well-structured and address the stated goals.

My review focuses on ensuring the new thread-safety mechanisms are robust. I've identified a critical race condition in AsusControl where several methods could attempt to use hardware resources after they have been disposed. I've also pointed out a couple of other areas for improvement regarding an asynchronous method implementation and code redundancy. Addressing these points will help solidify the stability of the application.

Comment thread AsusFanControl.Core/AsusControl.cs Outdated
Comment on lines +85 to +91
if (_disposed) return;
AsusWinIO64.HealthyTable_SetFanIndex(fanIndex);
AsusWinIO64.HealthyTable_SetFanTestMode(value > 0 ? FanModeManual : FanModeDefault);
AsusWinIO64.HealthyTable_SetFanPwmDuty(value);
lock (_hwLock)
{
AsusWinIO64.HealthyTable_SetFanIndex(fanIndex);
AsusWinIO64.HealthyTable_SetFanTestMode(value > 0 ? FanModeManual : FanModeDefault);
AsusWinIO64.HealthyTable_SetFanPwmDuty(value);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

There's a potential race condition here. The _disposed check is outside the lock. A thread could pass the _disposed check, then another thread could call Dispose(), which would de-initialize the hardware resources. The first thread would then acquire the lock and attempt to use the de-initialized resources, which could lead to a crash or undefined behavior.

To fix this, the _disposed check should be moved inside the lock block. This same issue exists in other methods like GetFanSpeed and Thermal_Read_Cpu_Temperature.

            lock (_hwLock)
            {
                if (_disposed) return;
                AsusWinIO64.HealthyTable_SetFanIndex(fanIndex);
                AsusWinIO64.HealthyTable_SetFanTestMode(value > 0 ? FanModeManual : FanModeDefault);
                AsusWinIO64.HealthyTable_SetFanPwmDuty(value);
            }

Comment thread AsusFanControl.Core/AsusControl.cs Outdated
Comment on lines +123 to +129
if (_disposed) return 0;
AsusWinIO64.HealthyTable_SetFanIndex(fanIndex);
var fanSpeed = AsusWinIO64.HealthyTable_FanRPM();
return fanSpeed;
lock (_hwLock)
{
AsusWinIO64.HealthyTable_SetFanIndex(fanIndex);
var fanSpeed = AsusWinIO64.HealthyTable_FanRPM();
return fanSpeed;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

Similar to SetFanSpeed, there is a race condition here with Dispose(). The _disposed check is outside the lock block. It should be moved inside to ensure that the hardware is not accessed after being disposed.

            lock (_hwLock)
            {
                if (_disposed) return 0;
                AsusWinIO64.HealthyTable_SetFanIndex(fanIndex);
                var fanSpeed = AsusWinIO64.HealthyTable_FanRPM();
                return fanSpeed;
            }

Comment thread AsusFanControl.Core/AsusControl.cs Outdated
Comment on lines +155 to +159
if (_disposed) return 0;
return AsusWinIO64.Thermal_Read_Cpu_Temperature();
lock (_hwLock)
{
return AsusWinIO64.Thermal_Read_Cpu_Temperature();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

Similar to SetFanSpeed, there is a race condition here with Dispose(). The _disposed check is outside the lock block. It should be moved inside to prevent accessing disposed resources.

            lock (_hwLock)
            {
                if (_disposed) return 0;
                return AsusWinIO64.Thermal_Read_Cpu_Temperature();
            }

Comment on lines +162 to 167
public Task ResetToDefaultAsync()
{
if (_disposed) return Task.CompletedTask;
ResetToDefaultInternal();
return Task.CompletedTask;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The method ResetToDefaultAsync is not truly asynchronous and has a potential thread-safety issue. It calls ResetToDefaultInternal() directly without acquiring the _hwLock, while other methods do. This could lead to race conditions. Additionally, it blocks the calling thread because ResetToDefaultInternal is synchronous.

To fix this, you should call the public ResetToDefault() method inside Task.Run to make it both thread-safe and truly asynchronous.

        public Task ResetToDefaultAsync()
        {
            if (_disposed) return Task.CompletedTask;
            return Task.Run(() => ResetToDefault());
        }

Comment thread AsusFanControl.Core/AsusControl.cs Outdated
private static int _instanceCount = 0;
private readonly int _fanCount;
private bool _disposed = false;
private bool _disposing = false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The _disposing flag is set but its value is never used. It's set to true at the beginning of the Dispose method's lock (line 52) and false at the end (line 79), but no other code path reads it. Any concurrent calls would be blocked by the lock and then stopped by the _disposed check. This flag can be safely removed to simplify the code, along with the lines where it's assigned.

Comment thread AsusFanControlGUI/Form1.cs Outdated
Comment on lines +87 to +94
try
{
asusControl.ResetToDefault();
}
catch (Exception ex)
{
Debug.WriteLine($"[Dispose] Reset error: {ex.Message}");
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This explicit call to asusControl.ResetToDefault() is redundant. The updated AsusControl.Dispose() method now handles resetting the fans to default when the last instance is disposed. Relying on the Dispose method's logic will make the code cleaner and avoid multiple reset calls. This try-catch block can be removed.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 45e2151b40

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread AsusFanControl.Core/AsusControl.cs Outdated
Comment on lines +57 to +62
if (_instanceCount == 0)
{
try
{
ResetToDefaultInternal();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve user-set fan mode on controller disposal

Dispose(bool) now resets all fans whenever the last AsusControl instance is disposed, which breaks the existing CLI flow that intentionally skips reset for --set-fan-speeds / --set-fan-speed before calling Dispose() in Program.Main. As a result, commands meant to leave manual fan settings in effect are always undone at process exit.

Useful? React with 👍 / 👎.

Comment thread AsusFanControlGUI/Form1.cs Outdated
Comment on lines +87 to +90
try
{
asusControl.ResetToDefault();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Respect turnOffControlOnExit during form shutdown

The form disposal path now calls asusControl.ResetToDefault() unconditionally, so even when turnOffControlOnExit is disabled the app still resets fan control on close. This makes the setting ineffective for normal GUI shutdown and regresses the behavior users can configure from the menu.

Useful? React with 👍 / 👎.

google-labs-jules Bot and others added 6 commits March 20, 2026 12:43
Co-authored-by: beenycool <129210955+beenycool@users.noreply.github.com>
- Add static instance counting to prevent multiple WinIo initializations
- Add lock to all hardware I/O operations (SetFanSpeed, GetFanSpeed, Thermal_Read_Cpu_Temperature)
- Fix finalizer to only call ShutdownWinIo from explicit Dispose(true)
- Add ResetToDefaultAsync for interface compatibility alongside sync ResetToDefault
- Add Debug.WriteLine diagnostics for disposal errors
- Make Points property private with thread-safe getter returning a copy
- Add thread-safe APIs: SetPoints, AddPoint, RemovePointAt, UpdatePointAt, PointCount, ClearPoints
- All internal operations now lock properly to prevent concurrent modification
- Fix timerRefreshStats to dispose timer before replacing (memory leak fix)
- Wrap PerformanceCounter.NextValue() in Task.Run to avoid UI blocking
- Add Debug.WriteLine diagnostics to all catch blocks
- Enforce minimum 2 points when deleting fan curve points
- Update UI callers to use thread-safe FanCurve APIs
@beenycool
beenycool merged commit 8fd4c92 into master Mar 20, 2026
@beenycool
beenycool deleted the fix/ui-timer-perf branch March 20, 2026 12:47
int HealthyTable_FanCounts();
ulong Thermal_Read_Cpu_Temperature();
Task ResetToDefaultAsync();
void ResetToDefault();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Breaking API change - Adding ResetToDefault() to the interface requires all implementations to add this method. Ensure all implementing classes are updated.

@kilo-code-bot

kilo-code-bot Bot commented Mar 20, 2026

Copy link
Copy Markdown

Code Review Summary

Status: 1 New Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
AsusFanControl.Core/IFanController.cs 16 Breaking API change - Adding ResetToDefault() to the interface requires all implementations to add this method
Other Observations (not in diff)

Issues from other reviewers that were addressed in current code:

Status Issue
✅ Fixed Race conditions with _disposed check outside lock in SetFanSpeed, GetFanSpeed, Thermal_Read_Cpu_Temperature
✅ Fixed ResetToDefaultAsync not using Task.Run - now properly uses Task.Run(() => ResetToDefault())
✅ Removed Unused _disposing flag - does not exist in current code

Issues from other reviewers that remain (already documented):

Priority Issue
P1 AsusControl.Dispose now auto-resets fans when last instance is disposed, which may override user preference for turnOffControlOnExit=false
Files Reviewed (6 files)
  • AsusFanControl.Core/AsusControl.cs - Thread safety improvements, reference counting
  • AsusFanControl.Core/FanCurve.cs - Thread-safe implementation
  • AsusFanControl.Core/IFanController.cs - Added ResetToDefault() method
  • AsusFanControlGUI/FanCurveControl.cs - Minimum point protection
  • AsusFanControlGUI/FanCurveEditor.cs - Uses thread-safe APIs
  • AsusFanControlGUI/Form1.cs - Timer disposal, PerformanceCounter async, error handling

Reviewed by minimax-m2.5-20260211 · 785,727 tokens

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant