Skip to content

Commit 9021967

Browse files
committed
feat(cli): Refactor lock manager and implement inter-thread locks for ChromaDB connector
1 parent 29cb6aa commit 9021967

File tree

2 files changed

+53
-11
lines changed

2 files changed

+53
-11
lines changed

src/vectorcode/cli_utils.py

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,22 @@
44
import logging
55
import os
66
import sys
7+
from asyncio import Lock
78
from dataclasses import dataclass, field, fields
89
from datetime import datetime
910
from enum import Enum, StrEnum
1011
from pathlib import Path
11-
from typing import Any, Generator, Iterable, Optional, Sequence, Union
12+
from typing import (
13+
Any,
14+
Generator,
15+
Iterable,
16+
Literal,
17+
Optional,
18+
Sequence,
19+
Type,
20+
Union,
21+
overload,
22+
)
1223

1324
import json5
1425
import shtab
@@ -644,12 +655,15 @@ def config_logging(
644655
)
645656

646657

658+
LockType = AsyncFileLock | Lock
659+
660+
647661
class LockManager:
648662
"""
649663
A class that manages file locks that protects the database files in daemon processes (LSP, MCP).
650664
"""
651665

652-
__locks: dict[str, AsyncFileLock]
666+
__locks: dict[tuple[str, Type[LockType]], LockType]
653667
singleton: Optional["LockManager"] = None
654668

655669
def __new__(cls) -> "LockManager":
@@ -658,7 +672,23 @@ def __new__(cls) -> "LockManager":
658672
cls.singleton.__locks = {}
659673
return cls.singleton
660674

661-
def get_lock(self, path: str | os.PathLike) -> AsyncFileLock:
675+
@overload
676+
def get_lock(
677+
self, path: str | os.PathLike, lock_type_name: Literal["asyncio"]
678+
) -> Lock: ...
679+
680+
@overload
681+
def get_lock(
682+
self,
683+
path: str | os.PathLike,
684+
lock_type_name: Literal["filelock"] | None,
685+
) -> AsyncFileLock: ...
686+
687+
def get_lock(
688+
self,
689+
path: str | os.PathLike,
690+
lock_type_name: Literal["filelock"] | Literal["asyncio"] | None = "filelock",
691+
):
662692
path = str(expand_path(str(path), True))
663693
if os.path.isdir(path):
664694
lock_file = os.path.join(path, "vectorcode.lock")
@@ -667,9 +697,19 @@ def get_lock(self, path: str | os.PathLike) -> AsyncFileLock:
667697
with open(lock_file, mode="w") as fin:
668698
fin.write("")
669699
path = lock_file
670-
if self.__locks.get(path) is None:
671-
self.__locks[path] = AsyncFileLock(path) # pyright: ignore[reportArgumentType]
672-
return self.__locks[path]
700+
lock: LockType
701+
match lock_type_name:
702+
case "filelock":
703+
lock = AsyncFileLock(path) # pyright: ignore[reportAssignmentType]
704+
case "asyncio":
705+
lock = Lock()
706+
case _: # pragma: nocover
707+
raise ValueError(f"Unsupported lock type: {lock_type_name}")
708+
709+
cache_key = (path, type(lock))
710+
if self.__locks.get(cache_key) is None:
711+
self.__locks[cache_key] = lock
712+
return self.__locks[cache_key]
673713

674714

675715
class SpecResolver:

src/vectorcode/database/chroma.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -139,11 +139,13 @@ async def get_client(self) -> ClientAPI:
139139
self._create_client()
140140
assert self._client is not None
141141
if self._client_type == "persistent":
142-
async with LockManager().get_lock(
143-
self._configs.db_params["db_path"]
144-
) as lock:
145-
self._file_lock = lock
146-
self._thread_lock = Lock()
142+
lock_manager = LockManager()
143+
self._file_lock = lock_manager.get_lock(
144+
str(self._configs.db_params["db_path"]), "filelock"
145+
)
146+
self._thread_lock = lock_manager.get_lock(
147+
str(self._configs.db_params["db_path"]), "asyncio"
148+
)
147149
return self._client
148150

149151
@contextlib.asynccontextmanager

0 commit comments

Comments
 (0)