Skip to content

Conversation

Jack251970
Copy link
Member

Fix #2785

@prlabeler prlabeler bot added the bug Something isn't working label Sep 18, 2025
@github-actions github-actions bot added this to the 2.1.0 milestone Sep 18, 2025
Copy link

gitstream-cm bot commented Sep 18, 2025

🥷 Code experts: no user but you matched threshold 10

Jack251970, jjw24 have most 👩‍💻 activity in the files.
Jack251970 has most 🧠 knowledge in the files.

See details

Flow.Launcher.Core/Resource/Internationalization.cs

Activity based on git-commit:

Jack251970 jjw24
SEP
AUG
JUL 143 additions & 98 deletions 33 additions & 14 deletions
JUN
MAY 31 additions & 13 deletions
APR 34 additions & 30 deletions

Knowledge based on git-blame:
Jack251970: 67%

✨ Comment /gs review for LinearB AI review. Learn how to automate it here.

Copy link

gitstream-cm bot commented Sep 18, 2025

Be a legend 🏆 by adding a before and after screenshot of the changes you made, especially if they are around UI/UX.

Copy link
Contributor

coderabbitai bot commented Sep 18, 2025

Warning

Rate limit exceeded

@Jack251970 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 8 minutes and 15 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 820304c and 0f6245a.

📒 Files selected for processing (2)
  • Flow.Launcher.Core/Resource/Internationalization.cs (6 hunks)
  • Flow.Launcher/App.xaml.cs (4 hunks)

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

Serialized language-change flow by adding a semaphore guard, moving language-load/cleanup and plugin metadata updates inside the critical section, clearing stale resource tracking after cleanup, and adding a UTF‑8 BOM to Internationalization.cs. No public signatures changed.

Changes

Cohort / File(s) Summary of Changes
Internationalization core
Flow.Launcher.Core/Resource/Internationalization.cs
Inserted UTF‑8 BOM at file start. Added private readonly SemaphoreSlim _langChangeLock = new(1,1) and updated ChangeLanguageAsync to await the semaphore and release it in finally, serializing language changes. Inside the lock: RemoveOldLanguageFiles(), conditional LoadLanguage(language) when not English, ChangeCultureInfo(language.LanguageCode), and (if updateMetadata) call UpdatePluginMetadataTranslations() (executed via Task.Run in the current change). RemoveOldLanguageFiles() now clears _oldResources after removing merged dictionaries. Public signatures unchanged.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Caller as Caller (ChangeLanguageAsync)
  participant Intl as Internationalization
  participant Old as _oldResources
  participant Merged as App.Resources.MergedDictionaries
  participant Plugins as Plugin metadata updater

  Caller->>Intl: ChangeLanguageAsync(language, updateMetadata)
  note right of Intl #DFF0D8: acquire semaphore
  Intl->>Intl: await _langChangeLock.WaitAsync()
  Intl->>Old: RemoveOldLanguageFiles()
  Intl->>Merged: remove each resource in _oldResources
  Intl->>Old: _oldResources.Clear()
  alt language != English
    Intl->>Intl: LoadLanguage(language)
  end
  Intl->>Intl: ChangeCultureInfo(language.LanguageCode)
  alt updateMetadata == true
    Intl->>Plugins: UpdatePluginMetadataTranslations() (Task.Run)
  end
  note right of Intl #F8F0D8: release semaphore
  Intl->>Intl: _langChangeLock.Release()
  Intl-->>Caller: return
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • taooceros
  • jjw24

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title Check ✅ Passed The title accurately and concisely summarizes the primary change—removing references to old resource dictionaries to fix a memory leak—and directly matches the Internationalization.cs modifications described in the PR summary; it is specific and free of noisy or unrelated details.
Linked Issues Check ✅ Passed The changes implement removal of stale ResourceDictionary references (RemoveOldLanguageFiles now clears _oldResources) and serialize language changes with a SemaphoreSlim, which directly addresses the memory-leak behavior described in issue #2785; public signatures remain unchanged. There is no evidence of unrelated coding changes, but the PR does not include automated tests or profiling evidence, so verification by memory-regression testing or profiling is recommended.
Out of Scope Changes Check ✅ Passed All functional changes are confined to Internationalization.cs and relate to language resource cleanup and concurrency; no unrelated feature or public API changes were introduced. The only out-of-scope artifact is a cosmetic BOM insertion at the top of the file, which has no runtime effect but could be removed to avoid noisy diffs.
Description Check ✅ Passed The description "Fix #2785" directly links this PR to the reported memory-leak issue and is therefore on-topic, though minimal.

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.

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: 0

Caution

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

⚠️ Outside diff range comments (3)
Flow.Launcher.Core/Resource/Internationalization.cs (3)

253-261: Critical: Do not mutate _oldResources while enumerating; clear after loop to actually fix the leak.

Removing from the list you’re iterating will throw InvalidOperationException and still risks leaving references around. Remove from MergedDictionaries, then Clear() the tracking list.

Apply this diff:

         private void RemoveOldLanguageFiles()
         {
             var dicts = Application.Current.Resources.MergedDictionaries;
-            foreach (var r in _oldResources)
-            {
-                dicts.Remove(r);
-                _oldResources.Remove(r);
-            }
+            foreach (var r in _oldResources)
+            {
+                dicts.Remove(r);
+            }
+            _oldResources.Clear();
         }

56-70: Bug: SystemLanguageCode is always reset to 'en'.

The final assignment overwrites any match found in the loop, forcing “System Language” to English.

Suggested fix:

         public static void InitSystemLanguageCode()
         {
             var availableLanguages = AvailableLanguages.GetAvailableLanguages();
@@
-            // Try to find a match in the available languages list
-            foreach (var language in availableLanguages)
-            {
-                var languageCode = language.LanguageCode;
-
-                if (string.Equals(languageCode, twoLetterCode, StringComparison.OrdinalIgnoreCase) ||
-                    string.Equals(languageCode, threeLetterCode, StringComparison.OrdinalIgnoreCase) ||
-                    string.Equals(languageCode, fullName, StringComparison.OrdinalIgnoreCase))
-                {
-                    SystemLanguageCode = languageCode;
-                }
-            }
-
-            SystemLanguageCode = DefaultLanguageCode;
+            // Default, then override on first match
+            SystemLanguageCode = DefaultLanguageCode;
+            foreach (var language in availableLanguages)
+            {
+                var languageCode = language.LanguageCode;
+                if (string.Equals(languageCode, twoLetterCode, StringComparison.OrdinalIgnoreCase) ||
+                    string.Equals(languageCode, threeLetterCode, StringComparison.OrdinalIgnoreCase) ||
+                    string.Equals(languageCode, fullName, StringComparison.OrdinalIgnoreCase))
+                {
+                    SystemLanguageCode = languageCode;
+                    break;
+                }
+            }
         }

355-358: Don’t return from inside the foreach; use continue.

Returning exits the whole method on the first non‑IPluginI18n plugin, skipping updates for the rest.

-                if (p.Plugin is not IPluginI18n pluginI18N) return;
+                if (p.Plugin is not IPluginI18n pluginI18N) continue;
🧹 Nitpick comments (1)
Flow.Launcher.Core/Resource/Internationalization.cs (1)

1-1: UTF‑8 BOM slipped in.

A BOM was introduced; keep file encoding consistent with the repo (usually UTF‑8 without BOM).

-using System;
+using System;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 820304c and b05c2c1.

📒 Files selected for processing (1)
  • Flow.Launcher.Core/Resource/Internationalization.cs (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: gitStream.cm
  • GitHub Check: build

@jjw24
Copy link
Member

jjw24 commented Sep 18, 2025

@coderabbitai full review

Copy link
Contributor

coderabbitai bot commented Sep 18, 2025

✅ Actions performed

Full review triggered.

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

Caution

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

⚠️ Outside diff range comments (2)
Flow.Launcher.Core/Resource/Internationalization.cs (2)

56-70: Bug: SystemLanguageCode is always reset to “en”.

Line 69 overwrites any detected match. Initialize to default first, then break on first match.

Proposed fix (outside changed lines):

public static void InitSystemLanguageCode()
{
    var availableLanguages = AvailableLanguages.GetAvailableLanguages();

    var currentCulture = CultureInfo.CurrentCulture;
    var two = currentCulture.TwoLetterISOLanguageName;
    var three = currentCulture.ThreeLetterISOLanguageName;
    var full = currentCulture.Name;

    SystemLanguageCode = DefaultLanguageCode;
    foreach (var language in availableLanguages)
    {
        var code = language.LanguageCode;
        if (code.Equals(two, StringComparison.OrdinalIgnoreCase) ||
            code.Equals(three, StringComparison.OrdinalIgnoreCase) ||
            code.Equals(full, StringComparison.OrdinalIgnoreCase))
        {
            SystemLanguageCode = code;
            break;
        }
    }
}

355-359: Bug: early return stops processing remaining plugins.

Return inside the foreach exits the entire method if a plugin is not IPluginI18n; should be continue.

Proposed change (outside changed lines):

foreach (var p in PluginManager.GetTranslationPlugins())
{
    if (p.Plugin is not IPluginI18n pluginI18N)
        continue;

    try
    {
        p.Metadata.Name = pluginI18N.GetTranslatedPluginTitle();
        p.Metadata.Description = pluginI18N.GetTranslatedPluginDescription();
        pluginI18N.OnCultureInfoChanged(CultureInfo.CurrentCulture);
    }
    catch (Exception e)
    {
        API.LogException(ClassName, $"Failed for <{p.Metadata.Name}>", e);
    }
}
🧹 Nitpick comments (2)
Flow.Launcher.Core/Resource/Internationalization.cs (2)

165-169: Serialize language changes and marshal resource ops to UI thread.

Rapid selection changes can overlap ChangeLanguageAsync, racing on _oldResources and Application.Current.Resources.

Suggested pattern (outside changed lines):

// Field
private readonly SemaphoreSlim _langChangeGate = new(1, 1);

// In ChangeLanguageAsync
await _langChangeGate.WaitAsync();
try
{
    await Application.Current.Dispatcher.InvokeAsync(RemoveOldLanguageFiles);
    if (language != AvailableLanguages.English)
        await Application.Current.Dispatcher.InvokeAsync(() => LoadLanguage(language));

    ChangeCultureInfo(language.LanguageCode);

    if (updateMetadata)
        await Task.Run(UpdatePluginMetadataTranslations);
}
finally
{
    _langChangeGate.Release();
}

Also applies to: 186-203


1-1: Unintended UTF‑8 BOM.

A BOM was added. If the repo targets utf-8 without BOM (common in .editorconfig), please remove to avoid noisy diffs.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 820304c and b05c2c1.

📒 Files selected for processing (1)
  • Flow.Launcher.Core/Resource/Internationalization.cs (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: gitStream.cm
  • GitHub Check: gitStream.cm
  • GitHub Check: gitStream.cm

@Jack251970 Jack251970 closed this Sep 18, 2025
auto-merge was automatically disabled September 18, 2025 09:21

Pull request was closed

@Jack251970
Copy link
Member Author

Still need to apply some adjustments

@Jack251970
Copy link
Member Author

@coderabbitai full review

@Jack251970 Jack251970 requested a review from Copilot September 18, 2025 10:10
Copy link
Contributor

coderabbitai bot commented Sep 18, 2025

✅ Actions performed

Full review triggered.

Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

Fixes a memory leak issue by adding synchronization to language changes and clearing old resource references properly.

  • Adds thread-safe synchronization for language change operations using a semaphore
  • Ensures old resource dictionaries are properly cleared from memory after removal

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

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

Caution

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

⚠️ Outside diff range comments (2)
Flow.Launcher.Core/Resource/Internationalization.cs (2)

57-71: System language detection always falls back to English.

SystemLanguageCode is overwritten to "en" unconditionally at Line 70. Only set the default if no match was found.

Apply this diff:

-            SystemLanguageCode = DefaultLanguageCode;
+            if (string.IsNullOrEmpty(SystemLanguageCode))
+                SystemLanguageCode = DefaultLanguageCode;

365-378: Don’t exit early when a plugin lacks IPluginI18n.

return aborts the entire update loop; use continue so other plugins still update.

Apply this diff:

-                if (p.Plugin is not IPluginI18n pluginI18N) return;
+                if (p.Plugin is not IPluginI18n pluginI18N) continue;
🧹 Nitpick comments (2)
Flow.Launcher.Core/Resource/Internationalization.cs (2)

1-1: Avoid committing a UTF‑8 BOM in C# files.

It creates noisy diffs and can confuse some tooling. Prefer UTF‑8 without BOM.


310-319: Fix log message variable.

It logs <{path}> when the missing file is actually english. Improves diagnostics.

Apply this diff:

-                        API.LogError(ClassName, $"Default English Language path can't be found <{path}>");
+                        API.LogError(ClassName, $"Default English Language path can't be found <{english}>");
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6c695f0 and 330e6c0.

📒 Files selected for processing (1)
  • Flow.Launcher.Core/Resource/Internationalization.cs (4 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (2)
Flow.Launcher.Core/Resource/Internationalization.cs (2)

33-33: Good call adding a semaphore to serialize language changes.

This should prevent overlapping resource mutations.


263-271: LGTM: safe removal pattern.

Iterating _oldResources without mutating, then Clear() eliminates the previous enumeration/mutation issue.

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: 0

♻️ Duplicate comments (2)
Flow.Launcher.Core/Resource/Internationalization.cs (2)

33-33: Lock scope: make the semaphore static (or confirm singleton) and consider disposal.

If multiple Internationalization instances can exist, per‑instance locks won’t serialize language changes globally. Prefer a static semaphore or confirm there’s only one instance for app lifetime. Disposal is optional for app‑lifetime objects but silences analyzers.

Apply if needed:

-        private readonly SemaphoreSlim _langChangeLock = new(1, 1);
+        private static readonly SemaphoreSlim _langChangeLock = new(1, 1);

To verify instance count, run:

#!/bin/bash
# Find construction sites of Internationalization
rg -nP --type=cs -C2 '\bnew\s+Internationalization\s*\('

189-212: Catch/log exceptions to avoid unobserved task faults; avoid Task.Run here.

ChangeLanguage fire‑and‑forgets this Task (Line 166). Without a catch, faults become unobserved. Also, UpdatePluginMetadataTranslations is synchronous; calling it via Task.Run adds overhead and makes thread affinity unclear.

         private async Task ChangeLanguageAsync(Language language, bool updateMetadata = true)
         {
-            await _langChangeLock.WaitAsync();
+            await _langChangeLock.WaitAsync();
             try
             {
                 // Remove old language files and load language
                 RemoveOldLanguageFiles();
                 if (language != AvailableLanguages.English)
                 {
                     LoadLanguage(language);
                 }

                 // Change culture info
                 ChangeCultureInfo(language.LanguageCode);

                 if (updateMetadata)
                 {
-                    // Raise event for plugins after culture is set
-                    await Task.Run(UpdatePluginMetadataTranslations);
+                    // Raise event for plugins after culture is set
+                    UpdatePluginMetadataTranslations(); // run on caller thread for predictable affinity
                 }
             }
+            catch (Exception e)
+            {
+                API.LogException(ClassName, "ChangeLanguageAsync failed", e);
+            }
             finally
             {
                 _langChangeLock.Release();
             }
         }

Also confirm UpdatePluginMetadataTranslations is safe on the calling thread (likely UI).

🧹 Nitpick comments (3)
Flow.Launcher.Core/Resource/Internationalization.cs (3)

1-1: Nit: UTF‑8 BOM churn — confirm repo policy.

If the repo doesn’t mandate BOM for C# files, consider removing it to avoid diff noise.

Do we have an .editorconfig rule requiring BOM for .cs files?


203-207: Update loop exits early on non‑i18n plugins — fix premature return.

UpdatePluginMetadataTranslations currently returns on the first plugin that isn’t IPluginI18n, skipping the rest. Use continue.

-                if (p.Plugin is not IPluginI18n pluginI18N) return;
+                if (p.Plugin is not IPluginI18n pluginI18N) continue;

57-71: System language detection overwrites matches with default — set default before the loop and don’t reset after.

As written, any match found is immediately overridden by the final assignment to DefaultLanguageCode.

-            // Try to find a match in the available languages list
+            // Default to English; try to find a match in the available languages list
+            SystemLanguageCode = DefaultLanguageCode;
             foreach (var language in availableLanguages)
             {
                 var languageCode = language.LanguageCode;
                 if (string.Equals(languageCode, twoLetterCode, StringComparison.OrdinalIgnoreCase) ||
                     string.Equals(languageCode, threeLetterCode, StringComparison.OrdinalIgnoreCase) ||
                     string.Equals(languageCode, fullName, StringComparison.OrdinalIgnoreCase))
                 {
                     SystemLanguageCode = languageCode;
+                    break;
                 }
             }
-
-            SystemLanguageCode = DefaultLanguageCode;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 820304c and 330e6c0.

📒 Files selected for processing (1)
  • Flow.Launcher.Core/Resource/Internationalization.cs (4 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-06T12:21:37.947Z
Learnt from: Jack251970
PR: Flow-Launcher/Flow.Launcher#3572
File: Flow.Launcher/App.xaml.cs:214-216
Timestamp: 2025-07-06T12:21:37.947Z
Learning: In Flow Launcher, the UpdatePluginManifestAsync method in PluginsManifest.cs already has comprehensive internal try-catch handling that logs exceptions and returns false on failure rather than throwing, making external try-catch wrappers unnecessary.

Applied to files:

  • Flow.Launcher.Core/Resource/Internationalization.cs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: gitStream.cm
  • GitHub Check: gitStream.cm
🔇 Additional comments (1)
Flow.Launcher.Core/Resource/Internationalization.cs (1)

270-270: LGTM: clearing _oldResources after removals prevents lingering references.

This directly addresses the memory‑leak risk when switching languages repeatedly.

@Jack251970 Jack251970 requested a review from Copilot September 18, 2025 10:18
@Jack251970
Copy link
Member Author

@coderabbitai full review

Copy link
Contributor

coderabbitai bot commented Sep 18, 2025

✅ Actions performed

Full review triggered.

Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

@jjw24
Copy link
Member

jjw24 commented Sep 18, 2025

@Jack251970 were you able to confirm that this fixes memory leak? Is the issue still reproducible?

@Jack251970
Copy link
Member Author

Jack251970 commented Sep 18, 2025

@Jack251970 were you able to confirm that this fixes memory leak? Is the issue still reproducible?

I cannot reproduce this issue from my end. But this reference issue is a noticeable error that we should take into account.

I have asked Yusyuriv to help me check this in #2785.

@Jack251970 Jack251970 requested a review from Yusyuriv September 19, 2025 04:34
@Jack251970 Jack251970 merged commit 5d843ec into dev Sep 19, 2025
6 checks passed
@Jack251970 Jack251970 deleted the internalization_memory_leak branch September 19, 2025 04:41
@jjw24 jjw24 modified the milestones: 2.1.0, 2.0.1 Sep 21, 2025
jjw24 pushed a commit that referenced this pull request Sep 21, 2025
Remove old dictionaries references to fix possible memory leak
@jjw24 jjw24 mentioned this pull request Sep 21, 2025
TBM13 pushed a commit to TBM13/Flow.Launcher that referenced this pull request Sep 23, 2025
…ion_memory_leak

Remove old dictionaries references to fix possible memory leak
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging this pull request may close these issues.

BUG: Memory leak during language change
3 participants