8000 Boot performance · Issue #176 · amirlehmam/wmux · GitHub
[go: up one dir, main page]

Skip to content
  • Boot performance #176

    Closed
    Closed

    Description

    @arnonuem

    What are you trying to do?

    When i start wmux and it is loading the sessions or when i manually load the sessions it takes very long until all the gray dots ( i have 26 sessions open) becoming green.

    What would you like wmux to do?

    Whatever is done when loading sessions, i suggest to do that in parallel (extra threads) - obviously currently it goes through time in a simple for loop and thus loads them one by one.

    What do you do today instead?

    waiting :/

    Activity

    1. amirlehmam commented on Aug 17, 2026

      @amirlehmam
      Owner

      Fixed in 1.3.1. You were right that something was serialised, and wrong about where — which turned out to be the useful part, because the actual culprit was work that did not need doing at all.

      Where it wasn't

      There is no for-loop over sessions. Restore is a single replaceAllWorkspaces call — one store update, all 26 workspaces at once. React then mounts them all (wmux keeps every workspace mounted so PTYs survive session switching), and each pane fires its own pty.create IPC independently. From the renderer's side that is already concurrent.

      Where it was

      The queue was in the main process, and it was this, running on every pane create:

      const requested = resolveExistingShellPath(spec.command);

      which ends in execFileSync('where', [shell]) — a synchronous child process, on the Electron main thread, with no memoization. On the miss path it runs twice per pane, because resolveShell's fallback resolves the same spec again.

      I measured it rather than eyeballing it:

      per call
      where lookup ~51 ms
      pty.spawn (the actual ConPTY) ~30 ms

      So wmux was spending roughly twice as long asking Windows where your shell is as it spent starting your shell — and asking 26 times for the same answer.

      For your 26 workspaces that is 1.3 s if the shell resolves and ~2.7 s if it does not. And because execFileSync blocks the main event loop, during that window nothing else in wmux ran either — not the remaining pane creates, and not the pipe server that receives the report_shell_state messages your dots are waiting for. That is the direct link to the symptom you described: the dots are gray until each shell reports its first prompt, and main was too busy to listen.

      The fix, and why it isn't threads

      Your instinct was extra threads. That does not work here and, more usefully, is not needed: PTY handles have to live in the process that owns the IPC, so moving pty.spawn to a worker would mean marshalling every keystroke and every byte of output across a thread boundary — a large change for a modest win.

      The where lookups are a different matter. They are pure, and 26 of them were computing the same value. So they are now memoized, and the answer is simply not recomputed.

      The precedent was already in the same file, three functions below the code that needed it:

      // `hasWsl` is cached because it shells out to `where`, and this runs on every pane create.
      let cachedWsl: boolean | null = null;

      Someone had already worked out exactly this problem — and cached it for wsl.exe only. The reasoning was always equally true of the shell you actually use.

      Measured on the shipped function, 26 resolutions:

      pwsh.exe                  1232ms -> 44ms   (28x)
      powershell.exe            1425ms -> 48ms   (30x)
      a shell that doesn't exist 1325ms -> 53ms   (25x)
      

      That is ~1.2–1.4 s of blocked main thread removed on a hit, and around 2.5 s on the miss path since create() resolves twice there. It also folds in a readdirSync over C:\Program Files\WindowsApps that the Store-PowerShell fallback was repeating — an ACL-heavy directory that is slow to enumerate.

      Two details, since caching is where bugs hide. Negative results are cached too, deliberately: a miss is the expensive branch (where scans the whole PATH before failing) and it is what an uninstalled or mistyped shell hits on every single pane. The cost is that installing a shell mid-session is not noticed until wmux restarts, which is already how the existing default-shell cache behaves. And a cached hit is re-validated with an existsSync before being handed back, so a shell uninstalled while wmux is running re-probes instead of feeding a dead path to pty.spawn and killing the pane with node-pty's opaque File not found:.

      What is still slow, stated plainly

      This does not make 26 workspaces instant, and I would rather set the expectation than have you discover it:

      • ~30 ms × 26 ≈ 0.8 s of ConPTY creation remains. That is real work and it has to happen.
      • Your shells' own startup — PowerShell profile, oh-my-posh, module imports — dominates after that. wmux does not control it, but those 26 shells now initialise concurrently, which they could not do while main was blocked. If your profile takes two seconds, 26 of them still take about two seconds, not fifty.

      So the ceiling should now be roughly "one shell's startup plus a second", instead of that plus several seconds of wmux blocking itself.

      Please tell me what you actually see. If 26 workspaces still take much longer than a single one, there is a second bottleneck and I would like the issue reopened — a rough count of seconds before and after is enough, and it beats anything I can infer from my own machine, where I do not have 26 sessions to restore.

      Thanks for filing it. The wrong mechanism pointed straight at the right symptom, and "obviously it goes through them one by one" was correct about the behaviour even though the loop was in a place neither of us would have guessed from the outside.


      1.3.1 is out — https://github.com/amirlehmam/wmux/releases/tag/v1.3.1

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

    Metadata

    Metadata

    Assignees

    No one assigned

      Labels

      enhancementNew feature or request

      Projects

      No projects

        Milestone

        No milestone

        Relationships

        None yet

        Development

        No branches or pull requests

        Issue actions

        0