Skip to content

Commit c6cb450

Browse files
author
Zhe Yu
committed
refactor(cli): implement filelock to protect db_path.
1 parent 323a48a commit c6cb450

5 files changed

Lines changed: 51 additions & 12 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ dependencies = [
2020
"colorlog",
2121
"charset-normalizer>=3.4.1",
2222
"json5",
23+
"filelock>=3.15.0",
2324
]
2425
requires-python = ">=3.11,<3.14"
2526
readme = "README.md"

src/vectorcode/cli_utils.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import json5
1414
import shtab
15+
from filelock import AsyncFileLock
1516

1617
from vectorcode import __version__
1718

@@ -610,3 +611,18 @@ def config_logging(
610611
handlers=handlers,
611612
level=level,
612613
)
614+
615+
616+
class LockManager:
617+
"""
618+
A class that manages file locks that protects the database files in daemon processes (LSP, MCP).
619+
"""
620+
621+
def __init__(self) -> None:
622+
self.__locks: dict[str, AsyncFileLock] = {}
623+
624+
def get(self, path: str | os.PathLike) -> AsyncFileLock:
625+
path = str(expand_path(str(path), True))
626+
if self.__locks.get(path) is None:
627+
self.__locks[path] = AsyncFileLock(path) # pyright: ignore[reportArgumentType]
628+
return self.__locks[path]

src/vectorcode/common.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import socket
66
import subprocess
77
import sys
8+
from asyncio.subprocess import Process
89
from typing import Any, AsyncGenerator
910
from urllib.parse import urlparse
1011

@@ -261,3 +262,30 @@ async def list_collection_files(collection: AsyncCollection) -> list[str]:
261262
or []
262263
)
263264
)
265+
266+
267+
class ClientManager:
268+
__singleton = None
269+
270+
# keys: project roots
271+
# values: clients
272+
__clients: dict[str, AsyncClientAPI] = {}
273+
__server_processes = []
274+
275+
@classmethod
276+
def get_instance(cls) -> "ClientManager":
277+
if cls.__singleton is None:
278+
cls.__singleton = ClientManager()
279+
return cls.__singleton
280+
281+
async def get(self, configs: Config) -> AsyncClientAPI:
282+
project_root = str(expand_path(str(configs.project_root), True))
283+
if self.__clients.get(project_root) is None:
284+
if not await try_server(configs.db_url):
285+
self.__server_processes.append(await start_server(configs))
286+
287+
self.__clients[project_root] = await get_client(configs)
288+
return self.__clients[project_root]
289+
290+
def get_processes(self) -> list[Process]:
291+
return self.__server_processes

src/vectorcode/main.py

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
get_project_config,
1313
parse_cli_args,
1414
)
15+
from vectorcode.common import ClientManager
1516

1617
logger = logging.getLogger(name=__name__)
1718

@@ -63,12 +64,6 @@ async def async_main():
6364

6465
return await chunks(final_configs)
6566

66-
from vectorcode.common import start_server, try_server
67-
68-
server_process = None
69-
if not await try_server(final_configs.db_url):
70-
server_process = await start_server(final_configs)
71-
7267
if final_configs.pipe: # pragma: nocover
7368
# NOTE: NNCF (intel GPU acceleration for sentence transformer) keeps showing logs.
7469
# This disables logs below ERROR so that it doesn't hurt the `pipe` output.
@@ -105,10 +100,9 @@ async def async_main():
105100
return_val = 1
106101
logger.error(traceback.format_exc())
107102
finally:
108-
if server_process is not None:
109-
logger.info("Shutting down the bundled Chromadb instance.")
110-
server_process.terminate()
111-
await server_process.wait()
103+
for p in ClientManager.get_instance().get_processes():
104+
p.terminate()
105+
await p.wait()
112106
return return_val
113107

114108

src/vectorcode/subcommands/query/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
expand_path,
1818
)
1919
from vectorcode.common import (
20-
get_client,
20+
ClientManager,
2121
get_collection,
2222
verify_ef,
2323
)
@@ -160,7 +160,7 @@ async def query(configs: Config) -> int:
160160
"Having both chunk and document in the output is not supported!",
161161
)
162162
return 1
163-
client = await get_client(configs)
163+
client = await ClientManager.get_instance().get(configs)
164164
try:
165165
collection = await get_collection(client, configs, False)
166166
if not verify_ef(collection, configs):

0 commit comments

Comments
 (0)