Tengo Gzirishvili Claude Opus 4.8 commited on
Commit
843e792
·
1 Parent(s): 1259937

Reliability: bake ESM-2 weights + bound in-memory jobs/rate-limit

Browse files

Three audit quick-wins:
- Dockerfile: HF_HOME moved to a BAKED /opt/hf-cache and the default ESM-2 (35M)
weights are prefetched at build time, so a cold start no longer re-downloads
them (HF_HOME used to live under ephemeral /tmp). Best-effort prefetch (|| echo)
so a build-time hiccup can't break the image — it just lazy-loads at runtime.
- server.py: cap _JOBS at 2000 and evict the oldest (with their edit locks) so a
long-lived worker can't grow unbounded.
- server.py: the rate-limit table now evicts the STALEST buckets when it gets
large, instead of clear()-ing the whole thing (which reset every honest user's
window and could be abused by a unique-IP flood).

Verified: 324 tests pass; _JOBS cap evicts oldest + prunes locks; rate-limit
drops stalest buckets (recent kept), no global wipe.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Files changed (2) hide show
  1. Dockerfile +12 -3
  2. dee/server.py +21 -2
Dockerfile CHANGED
@@ -35,15 +35,24 @@ RUN pip install --no-cache-dir -e .
35
  # non-root user a writable /tmp but not /app/.dee).
36
  ENV HOME=/tmp \
37
  XDG_CACHE_HOME=/tmp/.cache \
38
- HF_HOME=/tmp/.cache/huggingface \
 
39
  HOST=0.0.0.0 \
40
  PORT=7860 \
41
  PYTHONUNBUFFERED=1
42
- RUN mkdir -p /tmp/.cache/huggingface /tmp/.dee/output /tmp/.dee/state \
43
- && chown -R app:app /tmp/.dee /tmp/.cache
 
 
44
 
45
  USER app
46
 
 
 
 
 
 
 
47
  EXPOSE 7860
48
 
49
  # Production WSGI server. ONE gthread worker (state lives in-process), threads
 
35
  # non-root user a writable /tmp but not /app/.dee).
36
  ENV HOME=/tmp \
37
  XDG_CACHE_HOME=/tmp/.cache \
38
+ HF_HOME=/opt/hf-cache \
39
+ HF_HUB_DISABLE_TELEMETRY=1 \
40
  HOST=0.0.0.0 \
41
  PORT=7860 \
42
  PYTHONUNBUFFERED=1
43
+ # HF_HOME now points at a BAKED, image-resident dir (not ephemeral /tmp) so the
44
+ # default model's weights persist across Space restarts instead of re-downloading.
45
+ RUN mkdir -p /tmp/.cache /tmp/.dee/output /tmp/.dee/state /opt/hf-cache \
46
+ && chown -R app:app /tmp/.dee /tmp/.cache /opt/hf-cache
47
 
48
  USER app
49
 
50
+ # Bake the default ESM-2 (35M) weights into the image so a cold start doesn't pay
51
+ # the re-download cost. Best-effort: if the prefetch fails at build time, the
52
+ # build still succeeds and the app simply lazy-loads the model at runtime.
53
+ RUN python -c "from transformers import AutoTokenizer, AutoModelForMaskedLM; m='facebook/esm2_t12_35M_UR50D'; AutoTokenizer.from_pretrained(m); AutoModelForMaskedLM.from_pretrained(m)" \
54
+ || echo 'ESM-2 prefetch skipped — model will lazy-load at runtime'
55
+
56
  EXPOSE 7860
57
 
58
  # Production WSGI server. ONE gthread worker (state lives in-process), threads
dee/server.py CHANGED
@@ -268,9 +268,19 @@ def _get_job(job_id: str) -> Optional[JobState]:
268
  return _JOBS.get(job_id)
269
 
270
 
 
 
 
271
  def _put_job(job: JobState) -> None:
272
  with _JOBS_LOCK:
273
  _JOBS[job.job_id] = job
 
 
 
 
 
 
 
274
 
275
 
276
  # ----------------------------------------------------------------- preview cache
@@ -812,9 +822,18 @@ def _check_rate_limit(path: str, key: str):
812
  now = time.monotonic()
813
  try:
814
  with _RL_LOCK:
815
- # Cheap protection against an IP-rotating flood bloating the table.
 
 
 
816
  if len(_RL_HITS) > _RL_MAX_KEYS:
817
- _RL_HITS.clear()
 
 
 
 
 
 
818
  dq = _RL_HITS.get(bucket)
819
  if dq is None:
820
  dq = deque()
 
268
  return _JOBS.get(job_id)
269
 
270
 
271
+ _JOBS_MAX = 2000 # cap retained jobs so a long-lived process can't grow forever
272
+
273
+
274
  def _put_job(job: JobState) -> None:
275
  with _JOBS_LOCK:
276
  _JOBS[job.job_id] = job
277
+ # Bound memory: previously every job + its edit lock was kept for the
278
+ # process lifetime. Dicts preserve insertion order, so evict the oldest
279
+ # (long-finished) jobs once we're over the cap.
280
+ while len(_JOBS) > _JOBS_MAX:
281
+ old_id = next(iter(_JOBS))
282
+ _JOBS.pop(old_id, None)
283
+ _EDIT_LOCKS.pop(old_id, None)
284
 
285
 
286
  # ----------------------------------------------------------------- preview cache
 
822
  now = time.monotonic()
823
  try:
824
  with _RL_LOCK:
825
+ # Protect against an IP-rotating flood bloating the table — but evict
826
+ # the STALEST buckets (oldest last-hit), not the whole table. A blunt
827
+ # clear() would reset every honest user's window, and a unique-IP
828
+ # flood could exploit that to wipe limits on demand.
829
  if len(_RL_HITS) > _RL_MAX_KEYS:
830
+ try:
831
+ victims = sorted(_RL_HITS.items(),
832
+ key=lambda kv: (kv[1][-1] if kv[1] else 0.0))
833
+ for vk, _dq in victims[: max(1, len(_RL_HITS) // 5)]:
834
+ _RL_HITS.pop(vk, None)
835
+ except Exception: # noqa: BLE001
836
+ _RL_HITS.clear() # last-ditch fallback
837
  dq = _RL_HITS.get(bucket)
838
  if dq is None:
839
  dq = deque()