Skip to content

Commit 43e393d

Browse files
author
Project Team
committed
Replace per-process semaphore with cross-process flock for Ollama gate
The semaphore was module-level (per-process), so all 4 uvicorn workers had independent copies and could each send a request to Ollama simultaneously — exactly the GPU saturation problem it was meant to prevent. Replace with a non-blocking exclusive flock on /tmp/ollama_inference.lock. All worker processes share the same container filesystem, so only one worker can hold the lock at a time. Workers that cannot acquire it immediately return 503 + Retry-After rather than queuing.
1 parent 781d1ed commit 43e393d

1 file changed

Lines changed: 22 additions & 24 deletions

File tree

app/ocr_backends.py

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
1-
"""
2-
OCR Backend using Ollama vision models.
3-
4-
Provides OCR extraction using Ollama vision models (llama3.2-vision, llava)
5-
for accurate text extraction from alcohol beverage labels.
6-
"""
7-
1+
import fcntl
82
import logging
9-
import threading
3+
import os
104
import time
115
from abc import ABC, abstractmethod
126
from pathlib import Path
@@ -44,19 +38,19 @@ def extract_text(self, image_path: str) -> Dict[str, Any]:
4438

4539

4640
# ---------------------------------------------------------------------------
47-
# Module-level semaphore: Ollama is single-threaded and processes one vision
48-
# inference at a time. Without a limit here, concurrent requests pile up
49-
# inside Ollama's queue; each waits for all previous ones to finish, so the
50-
# N-th request waits N×inference_time seconds — quickly blowing past the
51-
# CloudFront 60-second origin timeout and causing a cascade of 504 errors.
41+
# Cross-process Ollama concurrency gate
5242
#
53-
# The semaphore allows exactly one in-flight Ollama call at a time. Any
54-
# request that cannot acquire it immediately gets a fast 503 + Retry-After
55-
# rather than a guaranteed timeout. Adjust _OLLAMA_MAX_CONCURRENCY if you
56-
# deploy a multi-GPU setup where Ollama can run parallel inferences.
43+
# uvicorn runs with --workers 4, so each worker is a separate OS process with
44+
# its own memory space. A threading.Semaphore is per-process and invisible to
45+
# the other three workers — all four can call Ollama simultaneously, which
46+
# saturates the single T4 GPU and causes every request to time out.
47+
#
48+
# We use a non-blocking exclusive flock on a shared file so that exactly one
49+
# worker process holds the lock at any time. Workers that cannot acquire it
50+
# immediately return a fast 503 + Retry-After to the caller rather than
51+
# queuing and guaranteeing a timeout.
5752
# ---------------------------------------------------------------------------
58-
_OLLAMA_MAX_CONCURRENCY = 1
59-
_ollama_semaphore = threading.Semaphore(_OLLAMA_MAX_CONCURRENCY)
53+
_OLLAMA_LOCK_PATH = "/tmp/ollama_inference.lock"
6054

6155

6256
class OllamaOCR(OCRBackend):
@@ -188,11 +182,14 @@ def extract_text(self, image_path: str) -> Dict[str, Any]:
188182
}
189183
}
190184

191-
# --- concurrency gate ---
192-
acquired = _ollama_semaphore.acquire(blocking=False)
193-
if not acquired:
185+
# --- cross-process concurrency gate (flock) ---
186+
lock_fd = open(_OLLAMA_LOCK_PATH, 'w')
187+
try:
188+
fcntl.flock(lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
189+
except BlockingIOError:
190+
lock_fd.close()
194191
logger.warning(
195-
"Ollama semaphore busy — rejecting request to prevent queue buildup"
192+
"Ollama lock busy (another worker is running inference) — rejecting request"
196193
)
197194
return {
198195
'success': False,
@@ -208,7 +205,8 @@ def extract_text(self, image_path: str) -> Dict[str, Any]:
208205
try:
209206
return self._do_extract(image_path, start_time)
210207
finally:
211-
_ollama_semaphore.release()
208+
fcntl.flock(lock_fd, fcntl.LOCK_UN)
209+
lock_fd.close()
212210

213211
def _do_extract(self, image_path: str, start_time: float) -> Dict[str, Any]:
214212
"""Inner extraction with timeout classification."""

0 commit comments

Comments
 (0)