Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix possible null reference when query is cancelled & Add support when image format is not support & Other improvements #3099

Open
wants to merge 8 commits into
base: dev
Choose a base branch
from

Conversation

Jack251970
Copy link
Contributor

@Jack251970 Jack251970 commented Nov 27, 2024

This PR contains those fixes and improvements

  1. Fix possible null reference when query is cancelled.
    It will happen when one query is cancelled and can cause query bar not working like this:
    https://github.com/user-attachments/assets/8a0a2c0f-6db6-4061-8ff6-a979408fc6ff
    Throw exception from VS:
Screenshot 2024-11-25 182150 Screenshot 2024-11-25 182242
  1. Add support when image format is not support.
    It add support for images whose formats are not support for preview like this:
Screenshot 2024-11-27 193044

Before it is catch in outer try catch sentences, and the icon of images will be AppMissingIcon.png, which is quite strange for .png extension.
Screenshot 2024-11-27 193035

Now just do not show the preview like this:
image

  1. Introduce concurrent directionary for topmost record.
    Make sure TopMostRecord is thread safe.

  2. Fix modified collection enumeration issue when updating results, which can also cause the same problem like 1.

  3. Fix xaml convert issue and use icon loader for PluginActivationIcon.

Screenshot 2024-11-27 220729

@prlabeler prlabeler bot added the bug Something isn't working label Nov 27, 2024

This comment has been minimized.

Copy link
Contributor

coderabbitai bot commented Nov 27, 2024

📝 Walkthrough
📝 Walkthrough

Walkthrough

The pull request introduces changes to the error handling logic within the LoadThumbnailResult method of the ImageLoader class, implementing a try-catch block to manage NotSupportedException during image loading. The TopMostRecord class has been modified to use a ConcurrentDictionary for improved thread safety, and various methods have been updated to accommodate this change. The MainWindow.xaml file's image source binding has been updated, and a null check has been added in the GenerateQueryAndResultHashCode method of the UserSelectedRecord class. The MainViewModel class has also been enhanced with new image handling capabilities and improved error management.

Changes

File Path Change Summary
Flow.Launcher.Infrastructure/Image/ImageLoader.cs Modified LoadThumbnailResult to include error handling for LoadFullImage(path) using a try-catch block.
Flow.Launcher/Storage/TopMostRecord.cs Changed records from Dictionary<string, Record> to ConcurrentDictionary<string, Record> for thread safety; updated methods for concurrent access.
Flow.Launcher/MainWindow.xaml Updated Source binding from PluginIconPath to PluginIconSource.
Flow.Launcher/Storage/UserSelectedRecord.cs Added null check in GenerateQueryAndResultHashCode method to handle null Query.
Flow.Launcher/ViewModel/MainViewModel.cs Added PluginIconSource property; updated QueryResults for async image loading and enhanced error handling in UpdateResultView.

Possibly related PRs

  • Fix System.OperationCanceledException Issue #3092: The changes in this PR involve adding error handling for OperationCanceledException, which is similar in nature to the error handling improvements made in the main PR for the LoadThumbnailResult method, both enhancing robustness against exceptions.

Suggested reviewers

  • jjw24
  • taooceros

🐇 In the code we hop and play,
With error checks to save the day.
Nulls are caught, and images load,
A safer path along the road.
So let us celebrate this feat,
With happy hearts and nimble feet! 🥕


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (5)
Flow.Launcher/Storage/TopMostRecord.cs (3)

Line range hint 1-8: Critical: Address thread safety concerns

The TODO comment indicates that this class is accessed from multiple threads but lacks thread safety mechanisms. This could lead to race conditions and data corruption when concurrent operations modify the records dictionary.

Consider these thread-safety options:

  1. Use ConcurrentDictionary<string, Record> instead of Dictionary<string, Record>
  2. Add lock mechanisms around dictionary operations
  3. Implement the double-checked locking pattern for read operations

Example implementation using ConcurrentDictionary:

- public Dictionary<string, Record> records { get; private set; } = new Dictionary<string, Record>();
+ public ConcurrentDictionary<string, Record> records { get; private set; } = new ConcurrentDictionary<string, Record>();

Line range hint 15-21: Enhance null-safety in IsTopMost method

While the added null check for result.OriginQuery is good, the method still has potential null reference issues:

  1. No null check for the result parameter itself
  2. The second return statement could throw if result.OriginQuery becomes null between checks

Consider this safer implementation:

 internal bool IsTopMost(Result result)
 {
-    if (records.Count == 0 || (result.OriginQuery != null && !records.ContainsKey(result.OriginQuery.RawQuery)))
+    if (result?.OriginQuery == null || records.Count == 0 || !records.ContainsKey(result.OriginQuery.RawQuery))
     {
         return false;
     }
 
     // since this dictionary should be very small (or empty) going over it should be pretty fast.
     return records[result.OriginQuery.RawQuery].Equals(result);
 }

Line range hint 23-39: Add null checks to Remove and AddOrUpdate methods

Both methods directly access result.OriginQuery.RawQuery without null checks, which could lead to null reference exceptions when handling cancelled queries.

Suggested implementation:

 internal void Remove(Result result)
 {
+    if (result?.OriginQuery == null) return;
     records.Remove(result.OriginQuery.RawQuery);
 }

 internal void AddOrUpdate(Result result)
 {
+    if (result?.OriginQuery == null) return;
     var record = new Record
     {
         PluginID = result.PluginID,
         Title = result.Title,
         SubTitle = result.SubTitle
     };
     records[result.OriginQuery.RawQuery] = record;
 }
Flow.Launcher.Infrastructure/Image/ImageLoader.cs (2)

218-227: Good addition of specific exception handling for unsupported formats.

The try-catch block appropriately handles NotSupportedException for unsupported image formats, preventing crashes and improving robustness.

However, consider these improvements:

  1. Instead of setting image = null, use the MissingImage as fallback
  2. Consider catching other potential exceptions (e.g., OutOfMemoryException) that could occur during full image loading
 try
 {
     image = LoadFullImage(path);
     type = ImageType.FullImageFile;
 }
-catch (NotSupportedException)
+catch (NotSupportedException ex)
 {
-    image = null;
+    Log.Debug($"|ImageLoader.GetThumbnailResult|Unsupported image format for {path}: {ex.Message}");
+    image = ImageCache[Constant.MissingImgIcon, false];
     type = ImageType.Error;
 }
+catch (Exception ex) when (ex is OutOfMemoryException or InvalidOperationException)
+{
+    Log.Exception($"|ImageLoader.GetThumbnailResult|Failed to load full image {path}", ex);
+    image = ImageCache[Constant.MissingImgIcon, false];
+    type = ImageType.Error;
+}

220-220: Consider optimizing image format validation.

The LoadFullImage method makes multiple attempts to load and resize the image. For unsupported formats, this could mean multiple unnecessary attempts before failing.

Consider validating the image format before attempting to load:

+private static bool IsFormatSupported(string path)
+{
+    try
+    {
+        using var stream = File.OpenRead(path);
+        using var decoder = BitmapDecoder.Create(
+            stream,
+            BitmapCreateOptions.IgnoreColorProfile,
+            BitmapCacheOption.None);
+        return true;
+    }
+    catch (NotSupportedException)
+    {
+        return false;
+    }
+}

 try
 {
+    if (!IsFormatSupported(path))
+    {
+        image = ImageCache[Constant.MissingImgIcon, false];
+        type = ImageType.Error;
+        return;
+    }
     image = LoadFullImage(path);
     type = ImageType.FullImageFile;
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 14f1a5a and 756e718.

📒 Files selected for processing (2)
  • Flow.Launcher.Infrastructure/Image/ImageLoader.cs (1 hunks)
  • Flow.Launcher/Storage/TopMostRecord.cs (1 hunks)

Flow.Launcher/Storage/TopMostRecord.cs Outdated Show resolved Hide resolved

This comment has been minimized.

Copy link

@check-spelling-bot Report

🔴 Please review

See the 📂 files view, the 📜action log, or 📝 job summary for details.

❌ Errors Count
❌ forbidden-pattern 22
❌ ignored-expect-variant 1
⚠️ non-alpha-in-dictionary 19

See ❌ Event descriptions for more information.

If the flagged items are 🤯 false positives

If items relate to a ...

  • binary file (or some other file you wouldn't want to check at all).

    Please add a file path to the excludes.txt file matching the containing file.

    File paths are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your files.

    ^ refers to the file's path from the root of the repository, so ^README\.md$ would exclude README.md (on whichever branch you're using).

  • well-formed pattern.

    If you can write a pattern that would match it,
    try adding it to the patterns.txt file.

    Patterns are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your lines.

    Note that patterns can't match multiline strings.

Copy link

gitstream-cm bot commented Nov 27, 2024

🥷 Code experts: onesounds

onesounds has most 👩‍💻 activity in the files.
onesounds has most 🧠 knowledge in the files.

See details

Flow.Launcher.Infrastructure/Image/ImageLoader.cs

Activity based on git-commit:

onesounds
NOV
OCT
SEP
AUG
JUL
JUN

Knowledge based on git-blame:
onesounds: 8%

Flow.Launcher/MainWindow.xaml

Activity based on git-commit:

onesounds
NOV
OCT
SEP
AUG
JUL
JUN 131 additions & 144 deletions

Knowledge based on git-blame:
onesounds: 91%

Flow.Launcher/Storage/TopMostRecord.cs

Activity based on git-commit:

onesounds
NOV
OCT
SEP
AUG
JUL
JUN

Knowledge based on git-blame:

Flow.Launcher/Storage/UserSelectedRecord.cs

Activity based on git-commit:

onesounds
NOV
OCT
SEP
AUG
JUL
JUN

Knowledge based on git-blame:

Flow.Launcher/ViewModel/MainViewModel.cs

Activity based on git-commit:

onesounds
NOV
OCT
SEP
AUG
JUL
JUN 3 additions & 3 deletions

Knowledge based on git-blame:
onesounds: 12%

To learn more about /:\ gitStream - Visit our Docs

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (5)
Flow.Launcher/Storage/TopMostRecord.cs (2)

Line range hint 30-37: Add null check and refactor record creation

Two issues to address:

  1. Missing null check for result.OriginQuery
  2. Record creation logic could be extracted for reusability

Apply these changes:

+ private static Record CreateRecord(Result result) => new Record
+ {
+     PluginID = result.PluginID,
+     Title = result.Title,
+     SubTitle = result.SubTitle
+ };

internal void AddOrUpdate(Result result)
{
-    var record = new Record
-    {
-        PluginID = result.PluginID,
-        Title = result.Title,
-        SubTitle = result.SubTitle
-    };
-    records.AddOrUpdate(result.OriginQuery.RawQuery, record, (key, oldValue) => record);
+    if (result.OriginQuery?.RawQuery == null) return;
+    var record = CreateRecord(result);
+    records.AddOrUpdate(result.OriginQuery.RawQuery, record, (_, _) => record);
}

42-42: Add parameter validation and optimize initialization

Consider adding parameter validation and capacity planning for better performance.

Apply this change:

public void Load(Dictionary<string, Record> dictionary)
{
-    records = new ConcurrentDictionary<string, Record>(dictionary);
+    if (dictionary == null)
+        throw new ArgumentNullException(nameof(dictionary));
+
+    var concurrencyLevel = Environment.ProcessorCount;
+    var capacity = dictionary.Count;
+    records = new ConcurrentDictionary<string, Record>(concurrencyLevel, capacity);
+    foreach (var kvp in dictionary)
+    {
+        records.TryAdd(kvp.Key, kvp.Value);
+    }
}
Flow.Launcher/Storage/UserSelectedRecord.cs (1)

Line range hint 1-109: Consider using ConcurrentDictionary for thread safety.

The class uses regular Dictionary for storing records which isn't thread-safe. Since the PR introduces thread safety improvements elsewhere using ConcurrentDictionary, consider applying the same pattern here to prevent potential race conditions in Add() and GetSelectedCount() methods.

Here's the suggested implementation:

- public Dictionary<int, int> recordsWithQuery { get; private set; }
+ public ConcurrentDictionary<int, int> recordsWithQuery { get; private set; }

  public UserSelectedRecord()
  {
-     recordsWithQuery = new Dictionary<int, int>();
+     recordsWithQuery = new ConcurrentDictionary<int, int>();
  }

  public void Add(Result result)
  {
      TransformOldRecords();
      var keyWithQuery = GenerateQueryAndResultHashCode(result.OriginQuery, result);
-     if (!recordsWithQuery.TryAdd(keyWithQuery, 1))
-         recordsWithQuery[keyWithQuery]++;
+     recordsWithQuery.AddOrUpdate(keyWithQuery, 1, (_, count) => count + 1);

      var keyWithoutQuery = GenerateResultHashCode(result);
-     if (!recordsWithQuery.TryAdd(keyWithoutQuery, 1))
-         recordsWithQuery[keyWithoutQuery]++;
+     recordsWithQuery.AddOrUpdate(keyWithoutQuery, 1, (_, count) => count + 1);
  }
Flow.Launcher/ViewModel/MainViewModel.cs (2)

1450-1476: Consider enhancing error handling and logging.

While the addition of error handling is good, there are a few improvements that could be made:

  1. Consider catching specific exceptions rather than using a general catch-all
  2. The debug log could be enhanced to include the full exception details including stack trace for better troubleshooting

Here's a suggested improvement:

 try
 {
     foreach (var metaResults in resultsForUpdates)
     {
         foreach (var result in metaResults.Results)
         {
             if (_topMostRecord.IsTopMost(result))
             {
                 result.Score = int.MaxValue;
             }
             else
             {
                 var priorityScore = metaResults.Metadata.Priority * 150;
                 result.Score += _userSelectedRecord.GetSelectedCount(result) + priorityScore;
             }
         }
     }

     bool reSelect = resultsForUpdates.First().ReSelectFirstResult;
     Results.AddResults(resultsForUpdates, token, reSelect);
 }
-catch (Exception ex)
+catch (InvalidOperationException ex)
 {
-    Log.Debug("MainViewModel", $"Error in UpdateResultView: {ex.Message}");
+    Log.Error("MainViewModel", "Error in UpdateResultView", ex);
+}
+catch (Exception ex)
+{
+    Log.Error("MainViewModel", "Unexpected error in UpdateResultView", ex);
 }

1456-1464: Consider extracting magic numbers into constants.

The scoring logic uses a magic number (150) for priority score calculation. This should be extracted into a named constant for better maintainability and clarity.

+private const int PRIORITY_SCORE_MULTIPLIER = 150;
+
 if (_topMostRecord.IsTopMost(result))
 {
     result.Score = int.MaxValue;
 }
 else
 {
-    var priorityScore = metaResults.Metadata.Priority * 150;
+    var priorityScore = metaResults.Metadata.Priority * PRIORITY_SCORE_MULTIPLIER;
     result.Score += _userSelectedRecord.GetSelectedCount(result) + priorityScore;
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 0720779 and c3c6126.

📒 Files selected for processing (4)
  • Flow.Launcher/MainWindow.xaml (1 hunks)
  • Flow.Launcher/Storage/TopMostRecord.cs (2 hunks)
  • Flow.Launcher/Storage/UserSelectedRecord.cs (2 hunks)
  • Flow.Launcher/ViewModel/MainViewModel.cs (6 hunks)
🔇 Additional comments (6)
Flow.Launcher/Storage/TopMostRecord.cs (3)

1-1: LGTM: Good improvement for thread safety

The switch to ConcurrentDictionary is a solid improvement that will prevent potential race conditions in a multi-threaded environment.

Also applies to: 11-11


15-21: ⚠️ Potential issue

Strengthen null check protection

While the initial null check is good, the return statement still directly accesses result.OriginQuery.RawQuery without a null check, which could throw a NullReferenceException.

Apply this fix:

- return result.OriginQuery != null && records[result.OriginQuery.RawQuery].Equals(result);
+ if (result.OriginQuery?.RawQuery == null) return false;
+ return records.TryGetValue(result.OriginQuery.RawQuery, out var record) && record.Equals(result);

9-11: Verify concurrent access patterns

Let's verify how this class is accessed concurrently to ensure our thread safety improvements are sufficient.

✅ Verification successful

Let me gather more information about how TopMostRecord is used in MainViewModel.cs to understand the concurrent access patterns better.


Based on the analysis of the code, I can now generate the final response:

Thread safety implementation is appropriate

The ConcurrentDictionary usage in TopMostRecord is well-justified. The evidence shows that:

  • TopMostRecord is accessed from multiple contexts including async query operations and UI operations
  • The access patterns are simple CRUD operations (AddOrUpdate, Remove, IsTopMost) which are all thread-safe with ConcurrentDictionary
  • The class is used in result scoring during async query operations while also being modified through UI actions
  • All operations on _topMostRecord are atomic and don't require additional synchronization
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for usages of TopMostRecord class
rg "TopMostRecord" -B 2 -A 2

# Search for async/await patterns near TopMostRecord usage
ast-grep --pattern 'async $_ $_($$$) {
  $$$
  TopMostRecord
  $$$
}'

Length of output: 1863


Script:

#!/bin/bash
# Look for methods in MainViewModel that use _topMostRecord
rg "_topMostRecord\." -B 5 -A 5 "Flow.Launcher/ViewModel/MainViewModel.cs"

# Search for any async methods in MainViewModel
ast-grep --pattern 'async $_ $_($$$) {
  $$$
}' "Flow.Launcher/ViewModel/MainViewModel.cs"

# Look for any thread-related code near TopMostRecord usage
rg "Task|Thread|Parallel|async|await" -B 2 -A 2 "Flow.Launcher/ViewModel/MainViewModel.cs"

Length of output: 11432

Flow.Launcher/Storage/UserSelectedRecord.cs (1)

54-58: LGTM! Null check prevents NullReferenceException.

The added null check properly handles cancelled queries by falling back to result-only hash code generation, preventing the NullReferenceException when accessing query properties.

Flow.Launcher/MainWindow.xaml (1)

311-311: LGTM! Verify ViewModel property binding.

The change from PluginIconPath to PluginIconSource aligns with the PR objectives to improve image format support and null reference handling.

Let's verify the corresponding ViewModel property exists:

✅ Verification successful

The previous command might have failed due to the pattern syntax. Let's try a broader search to:

  1. Find any occurrence of PluginIconSource property
  2. Locate the MainViewModel file to verify the property exists

Property binding verified and implemented correctly

The PluginIconSource property exists in MainViewModel.cs with proper implementation:

  • Defined as public ImageSource PluginIconSource { get; private set; }
  • Properly initialized and managed alongside PluginIconPath
  • Used with ImageLoader.LoadAsync() for loading the image source
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the PluginIconSource property exists in MainViewModel
ast-grep --pattern 'class MainViewModel {
  $$$
  public ImageSource PluginIconSource { get; set; }
  $$$
}'

Length of output: 110


Script:

#!/bin/bash
# Search for PluginIconSource property in all files
rg "PluginIconSource" -A 2 -B 2

# Search for files containing MainViewModel to ensure we're looking in the right places
fd MainViewModel

Length of output: 2148

Flow.Launcher/ViewModel/MainViewModel.cs (1)

26-27: LGTM: Image handling changes look good!

The addition of PluginIconSource property and related imports properly supports asynchronous image loading while maintaining null safety.

Also applies to: 727-728

Flow.Launcher/Storage/TopMostRecord.cs Show resolved Hide resolved
@Jack251970 Jack251970 changed the title Fix possible null reference when query is cancelled & Add support when image format is not support Fix possible null reference when query is cancelled & Add support when image format is not support & Other improvements Nov 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
1 min review bug Something isn't working
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant