xiaolongmr commited on
Commit
ab88319
·
1 Parent(s): 34f333f

fix: avoid blocking startup on bundled skill sync

Browse files
Files changed (1) hide show
  1. 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