Skip to content

Apply PR-8 review fixes: logging & cleanup - #9

Closed
beenycool wants to merge 0 commit into
masterfrom
fix/pr8
Closed

Apply PR-8 review fixes: logging & cleanup#9
beenycool wants to merge 0 commit into
masterfrom
fix/pr8

Conversation

@beenycool

@beenycool beenycool commented Jan 31, 2026

Copy link
Copy Markdown
Owner

Apply automated fixes requested in PR #8: dispose cpuCounter on shutdown; improve logging error handling and user messages; pluralize CSV header; simplify logging cleanup; marshal UI updates when stopping logging; instantiate Start Logging menu item. Changes committed on branch fix/pr8.

Summary by CodeRabbit

Release Notes

  • New Features

    • Fan speed control now accepts percentage values (0-100%) for more intuitive configuration.
    • Added ResetToDefault() function to safely restore all fans to default settings.
  • Bug Fixes

    • Improved application stability with better resource cleanup during crashes and shutdown.
    • Enhanced input validation with informative error messages for invalid fan speeds and IDs.
  • Refactor

    • Reorganized codebase structure for improved maintainability.

✏️ Tip: You can customize this high-level summary in your review settings.

@beenycool beenycool self-assigned this Jan 31, 2026
@coderabbitai

coderabbitai Bot commented Jan 31, 2026

Copy link
Copy Markdown

Walkthrough

The pull request introduces a modular core library architecture by extracting fan control logic into AsusFanControl.Core namespace. It establishes a new IFanController interface, refactors AsusControl with proper disposal patterns and lifecycle management, adds a ResetToDefault() method for safe fan shutdown, and updates Program and Form1 entry points with cleanup handlers and null-safety guards.

Changes

Cohort / File(s) Summary
Core Library Extraction
AsusFanControl.Core/AsusControl.cs, AsusFanControl.Core/IFanController.cs
New interface IFanController defines fan controller contract. AsusControl implements IFanController and IDisposable with disposal pattern (_disposed flag, guarded method access), adds public SetFanSpeed(int percent, byte fanIndex) and SetFanSpeeds(int percent) overloads, introduces ResetToDefault() for safe fan shutdown, and guards critical methods with disposal checks.
Namespace Reorganization
AsusFanControl.Core/AsusWinIO64.cs, AsusFanControl.Core/FanCurve.cs
Namespace moved from AsusSystemAnalysis/AsusFanControlGUI to AsusFanControl.Core; BOM character removed from AsusWinIO64.cs.
Project Configuration
AsusFanControl.Core/AsusFanControl.Core.csproj
New .NET Standard 2.0 library project file; configures AsusWinIO64.dll inclusion in output directory.
Program Lifecycle Management
AsusFanControl/Program.cs
Added static disposal/reset flags (skipResetOnExit, isDisposed); enhanced ProcessExit and UnhandledException handlers to conditionally reset and dispose controller; added input validation for fan speed arguments with explicit error messages; guarded reset/dispose calls in try-catch blocks; added [STAThread] attribute to Main; changed fan controller to IFanController interface type.
GUI Lifecycle Management
AsusFanControlGUI/Form1.cs, AsusFanControlGUI/Form1.Designer.cs
Updated namespace import to AsusFanControl.Core; added global exception handlers for crashes; implemented Dispose(bool disposing) override for safe cleanup; added null-safety checks to UI/update paths; adjusted OnProcessExit to only reset when controller exists; added new toolStripMenuItemStartLogging designer field.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 A burrow restructured, so neat and so tidy,
With interfaces clean and disposal as guide,
Reset the fans safe when the shutdown is nigh,
No leaks, no crashes—just graceful goodbye! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.09% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title refers to applying PR-8 review fixes including logging and cleanup, which aligns with the final commit's focus on disposing cpuCounter, improving logging, and UI updates, but does not fully capture the broader refactoring context (Core library extraction, IFanController implementation, disposal patterns) that dominates the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/pr8

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 @beenycool, 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 refactors the application's fan control mechanisms by introducing a dedicated core library and an interface for fan control. It also enhances the robustness of the application by implementing proper resource disposal patterns and comprehensive error handling during shutdown and unexpected crashes. Furthermore, it improves the user experience for the command-line interface with better argument parsing and clearer error messages, and adds null checks in the GUI to prevent runtime issues.

Highlights

  • Core Library Refactoring: The core fan control logic has been extracted into a new AsusFanControl.Core library, improving modularity and separation of concerns. This includes renaming several files and updating their namespaces.
  • Robust Resource Management: Implemented the IDisposable pattern with a _disposed flag and Dispose(bool disposing) method in AsusControl to ensure proper cleanup of unmanaged resources. Null and disposed checks have been added across various methods to prevent NullReferenceExceptions.
  • Enhanced Error Handling and Shutdown Logic: Improved application shutdown and crash handling in both the console (Program.cs) and GUI (Form1.cs) applications. ProcessExit, UnhandledException, and ThreadException handlers now ensure that fans are reset to default and resources are disposed safely, even in unexpected termination scenarios.
  • Improved Command-Line Interface: The command-line argument parsing for fan speed settings (--set-fan-speeds, --set-fan-speed=) has been made more robust with better validation and user-friendly error messages. A skipResetOnExit flag was introduced to respect user intent when setting fan speeds via the command line.
  • Abstraction with IFanController: A new IFanController interface has been introduced, abstracting the fan control operations and allowing for more flexible and testable code.
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 significantly improves the codebase by refactoring shared logic into a Core library, correctly implementing the IDisposable pattern for resource management, and enhancing the robustness of both the console and GUI applications. The improvements to argument parsing and error handling in the command-line tool are particularly noteworthy. I've identified a few areas for improvement: a newly added menu item in the GUI is not actually displayed, some exceptions are silently swallowed where logging would be beneficial, and there are minor coding style inconsistencies.

Comment thread AsusFanControlGUI/Form1.Designer.cs Outdated
this.toolStripMenuItemForbidUnsafeSettings = new System.Windows.Forms.ToolStripMenuItem();
this.toolStripMenuItemMinimizeToTrayOnClose = new System.Windows.Forms.ToolStripMenuItem();
this.toolStripMenuItemAutoRefreshStats = new System.Windows.Forms.ToolStripMenuItem();
this.toolStripMenuItemStartLogging = new System.Windows.Forms.ToolStripMenuItem();

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 toolStripMenuItemStartLogging is instantiated here, but it is not added to any menu's DropDownItems collection in the designer code. As a result, it will not be visible in the UI. Based on the context, it seems it should be added to the toolStripMenuItem1 (the "Advanced" menu).

{
if (_disposed) return;
var fanCount = AsusWinIO64.HealthyTable_FanCounts();
for(byte fanIndex = 0; fanIndex < fanCount; fanIndex++)

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

For consistency with C# coding conventions and the rest of the codebase, a space should be added after the for keyword. This improves readability.

for (byte fanIndex = 0; fanIndex < fanCount; fanIndex++)

This minor style issue also appears on line 125.

            for (byte fanIndex = 0; fanIndex < fanCount; fanIndex++)

Comment thread AsusFanControl/Program.cs Outdated
// and if we haven't set the flag to skip reset.
if (!isDisposed && !skipResetOnExit)
{
try { asusControl.ResetToDefault(); } catch {}

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 empty catch {} block silently swallows any exception from ResetToDefault(). While this prevents the shutdown process from crashing, it also hides potential issues that might be important for diagnostics. Since this is a console application and a goal of this PR is to improve logging, it would be better to log the exception to Console.Error. This recommendation also applies to the other empty catch blocks in the exception handlers.

                    try { asusControl.ResetToDefault(); } catch (Exception ex) { Console.Error.WriteLine($"[ProcessExit] Failed to reset fans: {ex.Message}"); }

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
AsusFanControl.Core/AsusControl.cs (1)

65-74: ⚠️ Potential issue | 🟡 Minor

Potential race condition: disposed state may change during async iteration.

The _disposed check at line 67 occurs only at method entry. If Dispose() is called while the loop is iterating, subsequent SetFanSpeed calls will silently return (due to their own guards), but AsusWinIO64.HealthyTable_FanCounts() at line 68 could access disposed resources.

🛡️ Suggested improvement: capture fan count before the disposed check
 private async Task SetFanSpeeds(byte value)
 {
-    if (_disposed) return;
     var fanCount = AsusWinIO64.HealthyTable_FanCounts();
+    if (_disposed) return;
     for(byte fanIndex = 0; fanIndex < fanCount; fanIndex++)
     {
+        if (_disposed) return;
         SetFanSpeed(value, fanIndex);
         await Task.Delay(20);
     }
 }
AsusFanControlGUI/Form1.Designer.cs (1)

14-21: ⚠️ Potential issue | 🟠 Major

Duplicate Dispose(bool) definition exists in Form1.Designer.cs and Form1.cs.

This designer-generated Dispose(bool disposing) at lines 14-21 will conflict with the override in Form1.cs (lines 48-83). This typically causes a compilation error due to duplicate member definition.

🔧 Recommended fix: Remove the designer-generated Dispose

The designer file should not contain the Dispose method when a custom implementation exists in the partial class. Either:

  1. Remove lines 14-21 from the designer file, or
  2. Move the custom disposal logic from Form1.cs into the designer's Dispose method (not recommended as it mixes concerns)
-        protected override void Dispose(bool disposing)
-        {
-            if (disposing && (components != null))
-            {
-                components.Dispose();
-            }
-            base.Dispose(disposing);
-        }
🤖 Fix all issues with AI agents
In `@AsusFanControl/Program.cs`:
- Around line 65-88: The code sets skipResetOnExit before validating or applying
the requested speed, which can prevent the safety reset on invalid input; move
the assignment of skipResetOnExit so it only runs after a successful change is
applied (i.e., after asusControl.SetFanSpeeds(newSpeed) returns/executes and
int.TryParse(parts[1], out int newSpeed) succeeded). Update the branch handling
"--set-fan-speeds" (variables arg, parts, newSpeed and the call
asusControl.SetFanSpeeds) to set skipResetOnExit = true only inside the
successful-parsing/after-SetFanSpeeds path (and not on parse failure or format
error), and apply the same fix to the analogous block referenced at lines
128-165.
🧹 Nitpick comments (2)
AsusFanControl.Core/AsusControl.cs (2)

76-82: Fire-and-forget async task may silently swallow exceptions.

SetFanSpeeds(int percent) discards the Task returned by the async method. Any exceptions in SetFanSpeeds(byte) will be silently lost, and there's no guarantee the operation completes before subsequent calls or disposal.

Consider either:

  1. Making the public method async Task and awaiting properly
  2. Using .ConfigureAwait(false).GetAwaiter().GetResult() if synchronous behavior is intended
  3. Adding exception handling within the async method
♻️ Option 1: Make public method async
-public void SetFanSpeeds(int percent)
+public async Task SetFanSpeedsAsync(int percent)
 {
     if (_disposed) return;
     percent = ClampPercentage(percent);
     var value = (byte)(percent / 100.0f * 255);
-    _ = SetFanSpeeds(value);
+    await SetFanSpeeds(value);
 }

Note: This would require updating IFanController interface and all callers.


32-40: Dispose pattern does not handle managed resources distinctly.

The Dispose(bool disposing) method doesn't differentiate between the disposing parameter cases. The disposing flag indicates whether managed resources should be cleaned up (true when called from Dispose(), false from finalizer). Currently, ShutdownWinIo() is called in both cases, which is correct for unmanaged cleanup, but the pattern could be clearer.

♻️ Clearer dispose pattern structure
 protected virtual void Dispose(bool disposing)
 {
     if (!_disposed)
     {
+        if (disposing)
+        {
+            // Dispose managed resources here if any are added later
+        }
+
         // Unmanaged resources
         AsusWinIO64.ShutdownWinIo();
         _disposed = true;
     }
 }

Comment thread AsusFanControl/Program.cs Outdated
Comment on lines +65 to +88
if (arg.StartsWith("--set-fan-speeds"))
{
var newSpeedStr = arg.Split('=')[1];
var newSpeed = int.Parse(newSpeedStr);
asusControl.SetFanSpeeds(newSpeed);
skipResetOnExit = true; // User wants this speed to stick

var parts = arg.Split('=');
if (parts.Length < 2 || string.IsNullOrWhiteSpace(parts[1]))
{
Console.WriteLine("Error: Invalid format for --set-fan-speeds. Usage: --set-fan-speeds=50");
continue;
}

if (int.TryParse(parts[1], out int newSpeed))
{
asusControl.SetFanSpeeds(newSpeed);

if (newSpeed == 0)
Console.WriteLine("Test mode turned off");
if(newSpeed == 0)
Console.WriteLine("Test mode turned off");
else
Console.WriteLine($"New fan speeds: {newSpeed}%");
}
else
Console.WriteLine($"New fan speeds: {newSpeed}%");
{
Console.WriteLine($"Error: Invalid number format for speed: {parts[1]}");
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don’t skip reset on invalid set-fan arguments.

skipResetOnExit is set before validating input, so invalid args can suppress the safety reset even though no speed was applied. Move the flag to only set after at least one successful change.

Suggested fix
-                    if (arg.StartsWith("--set-fan-speeds"))
-                    {
-                        skipResetOnExit = true; // User wants this speed to stick
+                    if (arg.StartsWith("--set-fan-speeds"))
+                    {
                         var parts = arg.Split('=');
                         if (parts.Length < 2 || string.IsNullOrWhiteSpace(parts[1]))
                         {
                             Console.WriteLine("Error: Invalid format for --set-fan-speeds. Usage: --set-fan-speeds=50");
                             continue;
                         }

                         if (int.TryParse(parts[1], out int newSpeed))
                         {
                             asusControl.SetFanSpeeds(newSpeed);
+                            skipResetOnExit = true; // only after a successful set

                             if(newSpeed == 0)
                                 Console.WriteLine("Test mode turned off");
                             else
                                 Console.WriteLine($"New fan speeds: {newSpeed}%");
                         }
                         else
                         {
                             Console.WriteLine($"Error: Invalid number format for speed: {parts[1]}");
                         }
                     }
@@
-                    if (arg.StartsWith("--set-fan-speed="))
-                    {
-                        skipResetOnExit = true; // User wants this speed to stick
+                    if (arg.StartsWith("--set-fan-speed="))
+                    {
                         var parts = arg.Split('=');
                         if (parts.Length < 2 || string.IsNullOrWhiteSpace(parts[1]))
                         {
                              Console.WriteLine("Error: Invalid format. Usage: --set-fan-speed=0:50,1:100");
                              continue;
                         }

+                        var anyApplied = false;
                         var fanSettings = parts[1].Split(',');
                         foreach (var fanSetting in fanSettings)
                         {
                             var settingParts = fanSetting.Split(':');
                             if (settingParts.Length == 2 &&
                                 int.TryParse(settingParts[0], out int fanId) &&
                                 int.TryParse(settingParts[1], out int fanSpeed))
                             {
                                 if (fanId >= 0 && fanId <= 255)
                                 {
                                     asusControl.SetFanSpeed(fanSpeed, (byte)fanId);
+                                    anyApplied = true;

                                     if (fanSpeed == 0)
                                         Console.WriteLine($"Test mode turned off for fan {fanId}");
                                     else
                                         Console.WriteLine($"New fan speed for fan {fanId}: {fanSpeed}%");
                                 }
                                 else
                                 {
                                     Console.WriteLine($"Error: fan id must be between 0 and 255: {fanId}");
                                 }
                             }
                             else
                             {
                                 Console.WriteLine($"Error: Invalid fan setting format: {fanSetting}");
                             }
                         }
+
+                        if (anyApplied)
+                            skipResetOnExit = true; // only if at least one valid setting applied
                     }

Also applies to: 128-165

🤖 Prompt for AI Agents
In `@AsusFanControl/Program.cs` around lines 65 - 88, The code sets
skipResetOnExit before validating or applying the requested speed, which can
prevent the safety reset on invalid input; move the assignment of
skipResetOnExit so it only runs after a successful change is applied (i.e.,
after asusControl.SetFanSpeeds(newSpeed) returns/executes and
int.TryParse(parts[1], out int newSpeed) succeeded). Update the branch handling
"--set-fan-speeds" (variables arg, parts, newSpeed and the call
asusControl.SetFanSpeeds) to set skipResetOnExit = true only inside the
successful-parsing/after-SetFanSpeeds path (and not on parse failure or format
error), and apply the same fix to the analogous block referenced at lines
128-165.

@beenycool
beenycool deleted the fix/pr8 branch March 20, 2026 21:43
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