Skip to content

Commit 111724e

Browse files
authored
fix: prevent two concurrent requests processing the same source (#130)
2 parents d1db8a1 + d97f4f6 commit 111724e

2 files changed

Lines changed: 43 additions & 12 deletions

File tree

context_chat_backend/controller.py

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ async def lifespan(app: FastAPI):
8787
# sequential prompt processing for in-house LLMs (non-nc_texttotext)
8888
llm_lock = threading.Lock()
8989

90+
# lock to update the sources dict currently being processed
91+
index_lock = threading.Lock()
92+
_indexing = {}
93+
9094
# limit the number of concurrent document parsing
9195
doc_parse_semaphore = mp.Semaphore(app_config.doc_parser_worker_limit)
9296

@@ -286,10 +290,25 @@ def _(userId: str = Body(embed=True)):
286290
@app.put('/loadSources')
287291
@enabled_guard(app)
288292
def _(sources: list[UploadFile]):
293+
global _indexing
294+
289295
if len(sources) == 0:
290296
return JSONResponse('No sources provided', 400)
291297

292298
for source in sources:
299+
if not value_of(source.filename):
300+
return JSONResponse(f'Invalid source filename for: {source.headers.get("title")}', 400)
301+
302+
with index_lock:
303+
if source.filename in _indexing:
304+
# this request will be retried by the client
305+
return JSONResponse(
306+
f'Source already being processed: {source.filename}',
307+
503,
308+
headers={'cc-retry': 'true'},
309+
)
310+
_indexing[source.filename] = True
311+
293312
if not (
294313
value_of(source.headers.get('userIds'))
295314
and value_of(source.headers.get('title'))
@@ -300,13 +319,21 @@ def _(sources: list[UploadFile]):
300319
):
301320
return JSONResponse(f'Invaild/missing headers for: {source.filename}', 400)
302321

303-
if not value_of(source.filename):
304-
return JSONResponse(f'Invalid source filename for: {source.headers.get("title")}', 400)
305-
306-
doc_parse_semaphore.acquire(block=True, timeout=29*60) # ~29 minutes
322+
# wait for 10 minutes before failing the request
323+
semres = doc_parse_semaphore.acquire(block=True, timeout=10*60)
324+
if not semres:
325+
return JSONResponse(
326+
'Document parser worker limit reached, try again in some time',
327+
503,
328+
headers={'cc-retry': 'true'}
329+
)
307330
added_sources = exec_in_proc(target=embed_sources, args=(vectordb_loader, app.extra['CONFIG'], sources))
308331
doc_parse_semaphore.release()
309332

333+
for source in sources:
334+
with index_lock:
335+
_indexing.pop(source.filename)
336+
310337
if len(added_sources) != len(sources):
311338
print(
312339
'Count of newly loaded sources:', len(added_sources),

context_chat_backend/vectordb/pgvector.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -198,14 +198,18 @@ def decl_update_access(self, user_ids: list[str], source_id: str, session_: orm.
198198
session.execute(stmt)
199199
session.commit()
200200

201-
access = [
202-
AccessListStore(
203-
uid=user_id,
204-
source_id=source_id,
205-
)
206-
for user_id in user_ids
207-
]
208-
session.add_all(access)
201+
stmt = (
202+
sa.dialects.postgresql.insert(AccessListStore)
203+
.values([
204+
{
205+
'uid': user_id,
206+
'source_id': source_id,
207+
}
208+
for user_id in user_ids
209+
])
210+
.on_conflict_do_nothing(index_elements=['uid', 'source_id'])
211+
)
212+
session.execute(stmt)
209213
session.commit()
210214

211215
if session_ is None:

0 commit comments

Comments
 (0)