xiaolongmr commited on
Commit ·
ab88319
1
Parent(s): 34f333f
fix: avoid blocking startup on bundled skill sync
Browse files- api/app.py +29 -3
api/app.py
CHANGED
|
@@ -47,6 +47,34 @@ def _start_image_cleanup() -> Thread:
|
|
| 47 |
return thread
|
| 48 |
|
| 49 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
def _web_cache_headers(full_path: str, asset: Path) -> dict[str, str]:
|
| 51 |
clean_path = full_path.strip("/")
|
| 52 |
suffix = asset.suffix.lower()
|
|
@@ -129,9 +157,6 @@ def create_app() -> FastAPI:
|
|
| 129 |
|
| 130 |
@asynccontextmanager
|
| 131 |
async def lifespan(_: FastAPI):
|
| 132 |
-
bundled_skills = skill_service.sync_bundled_skills()
|
| 133 |
-
if bundled_skills:
|
| 134 |
-
print(f"[skills] synchronized {len(bundled_skills)} bundled Skills")
|
| 135 |
# anyio's worker-thread limiter defaults to 40 and is shared by every
|
| 136 |
# run_in_threadpool() call in the process. Resize it here, inside the
|
| 137 |
# event loop, so the whole app sees the configured capacity.
|
|
@@ -142,6 +167,7 @@ def create_app() -> FastAPI:
|
|
| 142 |
backup_service.start()
|
| 143 |
sqlite_mirror_service.start()
|
| 144 |
pool_pricing_service.start()
|
|
|
|
| 145 |
_start_image_cleanup()
|
| 146 |
try:
|
| 147 |
yield
|
|
|
|
| 47 |
return thread
|
| 48 |
|
| 49 |
|
| 50 |
+
def _start_bundled_skill_sync() -> Thread:
|
| 51 |
+
"""Synchronize bundled Skills without blocking HTTP startup.
|
| 52 |
+
|
| 53 |
+
On Hugging Face the Skills directory can live on a Storage Bucket mount.
|
| 54 |
+
Copying a large bundle to that network filesystem may take minutes on the
|
| 55 |
+
first deployment, so doing it before ``yield`` makes the whole Space appear
|
| 56 |
+
stuck even though database recovery has already completed.
|
| 57 |
+
"""
|
| 58 |
+
|
| 59 |
+
def worker() -> None:
|
| 60 |
+
started_at = time.monotonic()
|
| 61 |
+
print("[skills] bundled Skill synchronization started in background")
|
| 62 |
+
try:
|
| 63 |
+
bundled_skills = skill_service.sync_bundled_skills()
|
| 64 |
+
elapsed = time.monotonic() - started_at
|
| 65 |
+
print(
|
| 66 |
+
f"[skills] synchronized {len(bundled_skills)} bundled Skills "
|
| 67 |
+
f"in {elapsed:.2f}s"
|
| 68 |
+
)
|
| 69 |
+
except Exception as exc:
|
| 70 |
+
elapsed = time.monotonic() - started_at
|
| 71 |
+
print(f"[skills] bundled Skill synchronization failed after {elapsed:.2f}s: {exc}")
|
| 72 |
+
|
| 73 |
+
thread = Thread(target=worker, name="bundled-skill-sync", daemon=True)
|
| 74 |
+
thread.start()
|
| 75 |
+
return thread
|
| 76 |
+
|
| 77 |
+
|
| 78 |
def _web_cache_headers(full_path: str, asset: Path) -> dict[str, str]:
|
| 79 |
clean_path = full_path.strip("/")
|
| 80 |
suffix = asset.suffix.lower()
|
|
|
|
| 157 |
|
| 158 |
@asynccontextmanager
|
| 159 |
async def lifespan(_: FastAPI):
|
|
|
|
|
|
|
|
|
|
| 160 |
# anyio's worker-thread limiter defaults to 40 and is shared by every
|
| 161 |
# run_in_threadpool() call in the process. Resize it here, inside the
|
| 162 |
# event loop, so the whole app sees the configured capacity.
|
|
|
|
| 167 |
backup_service.start()
|
| 168 |
sqlite_mirror_service.start()
|
| 169 |
pool_pricing_service.start()
|
| 170 |
+
_start_bundled_skill_sync()
|
| 171 |
_start_image_cleanup()
|
| 172 |
try:
|
| 173 |
yield
|