Fix corrupt cache detection (lexists) and suppress asyncio GC noise
Browse filesTwo robustness fixes in the HF Spaces startup block:
1. Use os.path.lexists() instead of os.path.exists() to detect corrupt
cache entries. exists() follows symlinks and returns False for broken
symlinks; lexists() checks the path itself β catches regular files,
working symlinks, AND broken symlinks. Also log exactly what was found
(dir / file / symlink / absent) to make future issues diagnosable.
Use os.unlink() which handles both files and symlinks (os.remove()
only handles files).
2. Install sys.unraisablehook to silently drop the Python 3.10 GC bug
(BaseEventLoop.__del__ β "Invalid file descriptor: -1"). This is the
correct Python 3.8+ API for intercepting exceptions raised in __del__
and other un-raisable contexts β cleaner than monkey-patching __del__.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- image_generator.py +27 -10
|
@@ -34,28 +34,45 @@ if not hasattr(torch, "xpu"):
|
|
| 34 |
IS_HF_SPACE = os.environ.get("SPACE_ID") is not None
|
| 35 |
if IS_HF_SPACE:
|
| 36 |
import spaces
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
# Download model files to persistent storage at startup.
|
| 38 |
# /data survives rebuilds β only downloads once ever.
|
| 39 |
_hf_token = os.environ.get("HF_TOKEN") or os.environ.get("HUGGING_FACE_HUB_TOKEN")
|
| 40 |
_cache_dir = os.environ.get("HF_HUB_CACHE")
|
| 41 |
print(f"Pre-caching FLUX.1-schnell β {_cache_dir} (token={'set' if _hf_token else 'NOT SET'})")
|
| 42 |
try:
|
| 43 |
-
#
|
| 44 |
-
#
|
| 45 |
-
|
| 46 |
_model_cache = os.path.join(_cache_dir, "models--black-forest-labs--FLUX.1-schnell")
|
| 47 |
-
if os.path.
|
| 48 |
-
|
| 49 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
|
| 51 |
from huggingface_hub import snapshot_download
|
| 52 |
-
import threading, asyncio
|
| 53 |
|
| 54 |
_result = {}
|
| 55 |
def _download():
|
| 56 |
-
#
|
| 57 |
-
#
|
| 58 |
-
# BaseEventLoop.__del__ from crashing with "Invalid file descriptor: -1".
|
| 59 |
loop = asyncio.new_event_loop()
|
| 60 |
asyncio.set_event_loop(loop)
|
| 61 |
try:
|
|
|
|
| 34 |
IS_HF_SPACE = os.environ.get("SPACE_ID") is not None
|
| 35 |
if IS_HF_SPACE:
|
| 36 |
import spaces
|
| 37 |
+
import sys, threading, asyncio
|
| 38 |
+
|
| 39 |
+
# ββ Suppress Python 3.10 asyncio GC bug ββββββββββββββββββββββββββββββ
|
| 40 |
+
# BaseEventLoop.__del__ crashes with "Invalid file descriptor: -1" when
|
| 41 |
+
# an event loop is collected by the GC. sys.unraisablehook is the correct
|
| 42 |
+
# Python 3.8+ way to intercept these GC-triggered exceptions.
|
| 43 |
+
_orig_unraisable = sys.unraisablehook
|
| 44 |
+
def _unraisable_hook(args):
|
| 45 |
+
if args.exc_type is ValueError and "Invalid file descriptor" in str(args.exc_value):
|
| 46 |
+
return # silently drop β harmless Python 3.10 GC bug
|
| 47 |
+
_orig_unraisable(args)
|
| 48 |
+
sys.unraisablehook = _unraisable_hook
|
| 49 |
+
|
| 50 |
# Download model files to persistent storage at startup.
|
| 51 |
# /data survives rebuilds β only downloads once ever.
|
| 52 |
_hf_token = os.environ.get("HF_TOKEN") or os.environ.get("HUGGING_FACE_HUB_TOKEN")
|
| 53 |
_cache_dir = os.environ.get("HF_HUB_CACHE")
|
| 54 |
print(f"Pre-caching FLUX.1-schnell β {_cache_dir} (token={'set' if _hf_token else 'NOT SET'})")
|
| 55 |
try:
|
| 56 |
+
# Diagnose and fix corrupt cache entry.
|
| 57 |
+
# os.path.exists() follows symlinks β returns False for broken symlinks.
|
| 58 |
+
# os.path.lexists() checks the path itself β catches files AND broken symlinks.
|
| 59 |
_model_cache = os.path.join(_cache_dir, "models--black-forest-labs--FLUX.1-schnell")
|
| 60 |
+
if os.path.lexists(_model_cache):
|
| 61 |
+
if os.path.isdir(_model_cache):
|
| 62 |
+
print(f"Cache dir OK: {_model_cache}")
|
| 63 |
+
else:
|
| 64 |
+
kind = "symlink" if os.path.islink(_model_cache) else "file"
|
| 65 |
+
print(f"β οΈ Removing corrupt cache entry ({kind}): {_model_cache}")
|
| 66 |
+
os.unlink(_model_cache) # works for both files and symlinks
|
| 67 |
+
else:
|
| 68 |
+
print(f"Cache entry absent β will be created by snapshot_download")
|
| 69 |
|
| 70 |
from huggingface_hub import snapshot_download
|
|
|
|
| 71 |
|
| 72 |
_result = {}
|
| 73 |
def _download():
|
| 74 |
+
# Own an explicit event loop so it can be closed cleanly before the
|
| 75 |
+
# thread exits β prevents the GC from seeing an open loop.
|
|
|
|
| 76 |
loop = asyncio.new_event_loop()
|
| 77 |
asyncio.set_event_loop(loop)
|
| 78 |
try:
|