Skip to content

Fix GUI window freeze on Windows during operations - #121

Open
RenanGBarreto wants to merge 2 commits into
mainfrom
fix/gui-windows-freeze-subprocess
Open

Fix GUI window freeze on Windows during operations#121
RenanGBarreto wants to merge 2 commits into
mainfrom
fix/gui-windows-freeze-subprocess

Conversation

@RenanGBarreto

Copy link
Copy Markdown
Contributor

🤔 Why?

On Windows, starting any operation (pack folder, pack file, verify) made the entire GUI window freeze — "Not responding" in the title bar until the command finished. The window couldn't be moved or interacted with.

The root cause: multiprocessing.Pool uses spawn on Windows, which re-imports __main__ in each child process. When this happens from a non-main thread inside a --windowed PyInstaller binary, the spawn handshake blocks the Windows message pump. macOS uses fork and was never affected.

🔧 What changed

  • Replaced the in-process cli_mkpfs_main() call in BasePanel._run_mkpfs with a subprocess.Popen child that streams output back to the log pane
  • Added a --gui-subprocess router in mkpfs/gui/__main__.py so the frozen binary runs the CLI instead of the GUI when the marker is present
  • Stored the Popen handle on self._proc for a future stop/cancel button
  • Suppressed Windows console-window flash with CREATE_NO_WINDOW (essential for dev mode, harmless on frozen exe)
  • Auto-confirmed overwrite prompts by piping "y\n" to stdin (replacing the old builtins.input patch)

🧪 How to test

# Verify the subprocess routing works (dev mode):
python -m mkpfs.gui --gui-subprocess -V

# Run full test suite:
bash run-tests.sh

💬 Notes

On Windows, multiprocessing.Pool uses 'spawn' which re-imports
__main__ in each child. When this happens from a non-main thread
inside a --windowed PyInstaller binary, the spawn handshake
blocks the Windows message pump, making the window unresponsive.

This replaces the in-process cli_mkpfs_main() call with a
subprocess.Popen child, keeping the GUI thread free. The
subprocess handle is stored for a future stop/cancel button.

Output is streamed line-by-line to the log pane. A --gui-subprocess
router in the entry point allows the frozen binary to serve both
GUI and CLI roles. Windows console-window flash is suppressed via
CREATE_NO_WINDOW. Overwrite prompts are auto-confirmed via stdin.

Closes #120.
@github-code-quality

github-code-quality Bot commented Aug 31, 2026

Copy link
Copy Markdown

Code Coverage Overview

Languages: Python

Python / code-coverage/pytest

The overall line coverage in commit 2bbf86a in the fix/gui-windows-free... branch remains at 72%, unchanged from commit 2abb9cd in the main branch.


Updated August 31, 2026 02:47 UTC

Comment thread mkpfs/gui/panels/base.py Fixed
@RenanGBarreto RenanGBarreto added the bug Something isn't working label Aug 31, 2026
Address code review feedback on PR #121: the bare pass in the
BrokenPipeError/OSError handler was flagged. Added a comment
explaining why it is safe to ignore (child crashed before reading
stdin; the streaming loop will surface the error).
@RenanGBarreto

Copy link
Copy Markdown
Contributor Author

Code Review

Overview

The subprocess approach is sound — it avoids multiprocessing.Pool's spawn re-importing __main__ and blocking the Windows message pump. The freeze_support()--gui-subprocess routing is correctly placed. However, there are three confirmed bugs (two reported in #122) plus dead code from the progress listener.


🔴 Bug 1 — Progress bar spam: text=True universal newlines split \r-delimited progress into separate lines

base.py:375text=True enables universal newline mode, which translates \r\n. pbar.py writes \r-delimited progress updates (e.g. \r[####--] 50% compress). With universal newlines, each progress tick becomes its own line in the parent — the \r-stripping logic at lines 405-406 is dead code because \r is already consumed.

Verified empirically: a child writing \r[----] 0%\r[#---] 20%...done\n yields 6 separate lines to the parent.

Fix: Use binary pipes + io.TextIOWrapper(proc.stdout, encoding="utf-8", errors="replace", newline=""). newline="" preserves \r so the existing rsplit("\r", 1) logic works.


🔴 Bug 2 — UnicodeEncodeError on Windows (#122)

Two compounding problems:

  1. cli.py:1640 hardcodes 🎉 directly, bypassing the icon()/supports_utf8() ASCII fallback. The sibling call site at cli.py:1281 does it correctly: info("Image created successfully!", icon_name="success").

  2. logging.py:83print() has no UnicodeEncodeError guard. When the pipe encoding is cp1252, any non-ASCII output crashes.

Fix: Use icon_name="success" at cli.py:1640. Harden logging.log() with a try/except UnicodeEncodeError fallback. Reconfigure stdout/stderr to UTF-8 in the --gui-subprocess branch of __main__.py.


🔴 Bug 3 — Dev-mode subprocess invocation is broken

base.py:365cmd = [sys.executable, "--gui-subprocess", *args] passes --gui-subprocess directly to the Python interpreter, which rejects it:

$ python3 --gui-subprocess -V
unknown option --gui-subprocess

This only works in a frozen PyInstaller binary where sys.executable is the exe itself. In dev mode, sys.executable is python3 and --gui-subprocess is an unknown Python option.

Fix: Branch on getattr(sys, "frozen", False) — in dev mode, use [sys.executable, "-m", "mkpfs.gui", "--gui-subprocess", *args].


🟡 Dead code — QueuedProgress, _progress_queue, _queued_progress, _drain_progress_events

With the listener never set in the child process, nothing pushes to _progress_queue. _drain_progress_events() polls an empty queue every 80ms — these are now dead.

Progress can be tracked by parsing the \r-delimited progress lines from the subprocess output (the [bar] N% phase format from pbar.py) to drive the determinate progress bar. This eliminates the spam and restores real progress tracking, so the dead code can be removed entirely.


🟢 Minor — self._proc thread safety

self._proc is written from the worker thread and would be read from the UI thread by a future stop button. A threading.Lock would prevent a TOCTOU race. Not a blocker since the stop button isn't wired up yet, but worth noting before it is.

RenanGBarreto added a commit that referenced this pull request Aug 31, 2026
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] GUI window becomes unresponsive (Not responding) on Windows after starting any operation

1 participant