RimSort / RimSort/RimSort

Clicking a mod whose folder contains a junction cycle permanently freezes the UI (get_dir_size follows reparse points)

Open
#2,450 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
1.2k
Forks
155
Avg merge
16h 10m
Merged PRs (30d)
59

Description

Summary

Clicking a mod in the list permanently freezes the UI if the mod's folder contains a directory junction cycle (a junction/symlink that points back into the same tree). The freeze happens in the folder-size calculation that ModInfoPanel runs synchronously on the GUI thread, because the directory traversal used by get_dir_size() follows Windows junctions (reparse points) as if they were normal directories and has no cycle protection.

Environment
  • RimSort v1.13.2 (code identical on current main)
  • Windows 11, LongPathsEnabled = 1
  • Local mod folder containing a "test game" rig: a nested mini RimWorld install whose Mods/MyMod is a junction pointing back to the real mod folder on disk (a common dev setup). Any junction cycle reproduces this; no special content is required.
Reproduction
  1. Create a local mod with a valid About/About.xml.
  2. Inside the mod folder, create a junction pointing back to the mod root:
    mklink /J "C:\...\Mods\MyMod\loop" "C:\...\Mods\MyMod"
    
  3. Start RimSort. The mod scan completes normally (top-level iterdir() + depth-2 About lookup are bounded, so scanning is unaffected).
  4. Click the mod in either list → UI freezes forever, process has to be killed.
Root cause

Verified by reading v1.13.2 / main source and by running the exact shipped traversal code against such a folder:

  • Click chain, all on the GUI thread:
    mod_clicked (app/views/mods_panel.py:3321) → mod_info_signal__mod_list_slot (app/views/main_content_panel.py:718) → ModInfoPanel.display_mod_info_set_folder_size_info (app/views/mod_info_panel.py:706) → path_to_folder_size (app/sort/mod_sorting.py:96) → get_dir_size (app/sort/mod_sorting.py:223)
  • get_dir_size() walks the tree with a plain stack — no visited set, no depth limit.
  • Enumeration goes through scanpath()scanpath_win32() (app/utils/platform/windows.py:46). Win32DirEntry.is_dir() checks only FILE_ATTRIBUTE_DIRECTORY (0x10) and ignores FILE_ATTRIBUTE_REPARSE_POINT (0x400), so junctions are traversed like ordinary directories.
  • Note Python's os.path.islink() returns False for junctions, so os.scandir/os.walk-based fallbacks (e.g. app/utils/generic.py, app/utils/file_search.py) have the same behavior.
  • With LongPathsEnabled = 1 there is no MAX_PATH cutoff, so the walk never terminates.

Measured with v1.13.2's own get_dir_size + scanpath_win32 against a real mod folder with one such junction: after 18 seconds it had enumerated 92,410 entries, reached a path depth of 1,820 characters, still had 2,548 directories queued, and had not finished. The result cache is only written after completion, so every click restarts the walk.

Other affected paths
  • Sorting the inactive list by folder size triggers the same walk (in a QThread — the sort never completes).
  • Even without a cycle, junctions pointing outside the mod (e.g. to game Data folders) get followed and counted, multiplying both the runtime and the reported size.
Suggested fixes (any combination)
  1. In Win32DirEntry, treat entries with FILE_ATTRIBUTE_REPARSE_POINT as non-traversable (or expose is_reparse_point()), so junctions/symlinks are not descended into during size calculation.
  2. Belt-and-braces: keep a resolved-path visited set in get_dir_size(), which also prevents double-counting when the same physical folder is reachable through several junctions.
  3. Consider computing the folder size off the GUI thread (or lazily) so a pathological folder can never wedge the whole UI.
Workaround for users

Remove or relocate junction cycles inside mod folders (e.g. rmdir the junction — it removes only the link, never the target).

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with get_dir_size() in app/sort/mod_sorting.py and the Windows traversal in app/utils/platform/windows.py, following the click path through app/views/mod_info_panel.py. Check how reparse points are classified and how directories are queued. Done means a mod containing a junction cycle can be selected without freezing, and folder-size calculation terminates without descending through the cycle.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
desktop, operating-systems, performance
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.