Skip to content

Windows: Severe input lag (~15s) after window regains focus due to working set trimming #191

Description

@richardpowellus

Bug Description

On Windows, OpenBubbles becomes extremely laggy (10-15 seconds of input lag) every time the window regains focus after being in the background. Typing, scrolling, and all UI interactions freeze. The app fixes itself after ~15 seconds, but the lag returns every time focus is lost and regained.

This happens on high-end hardware (RTX 3080 Ti, 64GB RAM, NVMe SSD) and is not a resource limitation.

Root Cause

Windows aggressively trims the working set (resident memory) of unfocused applications. When OpenBubbles loses focus, its ~400-500MB working set gets paged out. When focus returns, the OS spends ~15 seconds faulting all those pages back into RAM before the app becomes responsive.

The core issue is in lib/main.dart — the DesktopWindowListener calls ls.close() on onWindowBlur() and ls.open() on onWindowFocus(), and lifecycle_service.dart sets windowFocused = false on close. This signals to the OS that the app is inactive, compounding the trimming.

Additionally:

  • The default logLevel is 4 (INFO), which logs every single keystroke and text prediction to disk. For example, typing the word "test" produces 8+ log lines recording each character and annotation change. Over a typical session this creates tens of megabytes of log I/O, adding constant disk pressure.
  • highPerfMode defaults to false
  • keepAppAlive has no effect on desktop — it only controls Android foreground services

Suggested Fix

In the Windows runner (windows/runner/flutter_window.cpp or win32_window.cpp), handle WM_ACTIVATE to call SetProcessWorkingSetSizeEx with a hard minimum floor:

// In FlutterWindow::MessageHandler or Win32Window::MessageHandler
case WM_ACTIVATE: {
    // Prevent Windows from trimming our working set when losing focus
    SIZE_T minSize = 512 * 1024 * 1024;  // 512 MB minimum
    SIZE_T maxSize = 1536 * 1024 * 1024; // 1.5 GB maximum  
    SetProcessWorkingSetSizeEx(
        GetCurrentProcess(), minSize, maxSize,
        QUOTA_LIMITS_HARDWS_MIN_ENABLE);
    if (child_content_ != nullptr) {
        SetFocus(child_content_);
    }
    return 0;
}

Alternatively, this could be done once at startup in main.cpp after the window is created.

Other Improvements

  1. Default logLevel should be 2 (ERROR) or 3 (WARN) on desktop, not 4 (INFO) — logging every keystroke and text prediction annotation is extremely wasteful and generates tens of MB of logs per session
  2. Consider making highPerfMode default to true on desktop — desktop users rarely need inline media rendering throttled

Workaround

Users can work around this by running the following PowerShell after OpenBubbles starts:

Add-Type -TypeDefinition 'using System; using System.Runtime.InteropServices; public class WS { [DllImport("kernel32.dll", SetLastError=true)] public static extern bool SetProcessWorkingSetSizeEx(IntPtr h, IntPtr min, IntPtr max, uint flags); public const uint HARD_MIN = 0x1; }'
$proc = Get-Process bluebubbles_app
$proc.PriorityClass = [System.Diagnostics.ProcessPriorityClass]::AboveNormal
[WS]::SetProcessWorkingSetSizeEx($proc.Handle, [IntPtr](512MB), [IntPtr]([long]1536*1024*1024), [WS]::HARD_MIN)

Environment

  • OS: Windows 11
  • OpenBubbles version: 1.18.800.0 (Microsoft Store)
  • Hardware: RTX 3080 Ti, 64GB RAM, NVMe SSD
  • Process stats: ~470MB working set, 461k DB entries in ObjectBox/LMDB

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions