mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 21:22:11 -03:00
perf(config): limit symlink scan to first level for faster startup
Replace recursive directory traversal with first-level-only symlink scanning to fix severe performance issues on large model collections (220K+ files). - Rename _scan_directory_links to _scan_first_level_symlinks - Only scan symlinks directly under each root directory - Skip traversal of normal subdirectories entirely - Update tests to reflect first-level behavior - Add test_deep_symlink_not_scanned to document intentional limitation Startup time reduced from 15+ minutes to seconds for affected users. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -230,8 +230,58 @@ def test_new_symlink_triggers_rescan(monkeypatch: pytest.MonkeyPatch, tmp_path):
|
||||
assert normalized_external in second_cfg._path_mappings
|
||||
|
||||
|
||||
def test_removed_deep_symlink_triggers_rescan(monkeypatch: pytest.MonkeyPatch, tmp_path):
|
||||
"""Removing a deep symlink should trigger cache invalidation."""
|
||||
def test_removed_first_level_symlink_triggers_rescan(monkeypatch: pytest.MonkeyPatch, tmp_path):
|
||||
"""Removing a first-level symlink should trigger cache invalidation."""
|
||||
loras_dir, settings_dir = _setup_paths(monkeypatch, tmp_path)
|
||||
|
||||
# Create first-level symlink (directly under loras root)
|
||||
external_dir = tmp_path / "external"
|
||||
external_dir.mkdir()
|
||||
symlink = loras_dir / "external_models"
|
||||
symlink.symlink_to(external_dir, target_is_directory=True)
|
||||
|
||||
# Initial scan finds the symlink
|
||||
first_cfg = config_module.Config()
|
||||
normalized_external = _normalize(str(external_dir))
|
||||
assert normalized_external in first_cfg._path_mappings
|
||||
|
||||
# Remove the symlink
|
||||
symlink.unlink()
|
||||
|
||||
# Second config should detect invalid cached mapping and rescan
|
||||
second_cfg = config_module.Config()
|
||||
assert normalized_external not in second_cfg._path_mappings
|
||||
|
||||
|
||||
def test_retargeted_first_level_symlink_triggers_rescan(monkeypatch: pytest.MonkeyPatch, tmp_path):
|
||||
"""Changing a first-level symlink's target should trigger cache invalidation."""
|
||||
loras_dir, settings_dir = _setup_paths(monkeypatch, tmp_path)
|
||||
|
||||
# Create first-level symlink
|
||||
target_v1 = tmp_path / "external_v1"
|
||||
target_v1.mkdir()
|
||||
target_v2 = tmp_path / "external_v2"
|
||||
target_v2.mkdir()
|
||||
|
||||
symlink = loras_dir / "external_models"
|
||||
symlink.symlink_to(target_v1, target_is_directory=True)
|
||||
|
||||
# Initial scan
|
||||
first_cfg = config_module.Config()
|
||||
assert _normalize(str(target_v1)) in first_cfg._path_mappings
|
||||
|
||||
# Retarget the symlink
|
||||
symlink.unlink()
|
||||
symlink.symlink_to(target_v2, target_is_directory=True)
|
||||
|
||||
# Second config should detect changed target and rescan
|
||||
second_cfg = config_module.Config()
|
||||
assert _normalize(str(target_v2)) in second_cfg._path_mappings
|
||||
assert _normalize(str(target_v1)) not in second_cfg._path_mappings
|
||||
|
||||
|
||||
def test_deep_symlink_not_scanned(monkeypatch: pytest.MonkeyPatch, tmp_path):
|
||||
"""Deep symlinks (below first level) are not scanned to avoid performance issues."""
|
||||
loras_dir, settings_dir = _setup_paths(monkeypatch, tmp_path)
|
||||
|
||||
# Create nested structure with deep symlink
|
||||
@@ -242,46 +292,12 @@ def test_removed_deep_symlink_triggers_rescan(monkeypatch: pytest.MonkeyPatch, t
|
||||
deep_symlink = subdir / "styles"
|
||||
deep_symlink.symlink_to(external_dir, target_is_directory=True)
|
||||
|
||||
# Initial scan finds the deep symlink
|
||||
first_cfg = config_module.Config()
|
||||
# Config should not detect deep symlinks (only first-level)
|
||||
cfg = config_module.Config()
|
||||
normalized_external = _normalize(str(external_dir))
|
||||
assert normalized_external in first_cfg._path_mappings
|
||||
|
||||
# Remove the deep symlink
|
||||
deep_symlink.unlink()
|
||||
|
||||
# Second config should detect invalid cached mapping and rescan
|
||||
second_cfg = config_module.Config()
|
||||
assert normalized_external not in second_cfg._path_mappings
|
||||
assert normalized_external not in cfg._path_mappings
|
||||
|
||||
|
||||
def test_retargeted_deep_symlink_triggers_rescan(monkeypatch: pytest.MonkeyPatch, tmp_path):
|
||||
"""Changing a deep symlink's target should trigger cache invalidation."""
|
||||
loras_dir, settings_dir = _setup_paths(monkeypatch, tmp_path)
|
||||
|
||||
# Create nested structure
|
||||
subdir = loras_dir / "anime"
|
||||
subdir.mkdir()
|
||||
target_v1 = tmp_path / "external_v1"
|
||||
target_v1.mkdir()
|
||||
target_v2 = tmp_path / "external_v2"
|
||||
target_v2.mkdir()
|
||||
|
||||
deep_symlink = subdir / "styles"
|
||||
deep_symlink.symlink_to(target_v1, target_is_directory=True)
|
||||
|
||||
# Initial scan
|
||||
first_cfg = config_module.Config()
|
||||
assert _normalize(str(target_v1)) in first_cfg._path_mappings
|
||||
|
||||
# Retarget the symlink
|
||||
deep_symlink.unlink()
|
||||
deep_symlink.symlink_to(target_v2, target_is_directory=True)
|
||||
|
||||
# Second config should detect changed target and rescan
|
||||
second_cfg = config_module.Config()
|
||||
assert _normalize(str(target_v2)) in second_cfg._path_mappings
|
||||
assert _normalize(str(target_v1)) not in second_cfg._path_mappings
|
||||
def test_legacy_symlink_cache_automatic_cleanup(monkeypatch: pytest.MonkeyPatch, tmp_path):
|
||||
"""Test that legacy symlink cache is automatically cleaned up after migration."""
|
||||
settings_dir = tmp_path / "settings"
|
||||
|
||||
Reference in New Issue
Block a user