diff --git a/.clang-format b/.clang-format index 0344f940d..e9b5c5711 100644 --- a/.clang-format +++ b/.clang-format @@ -1,4 +1,4 @@ -BasedOnStyle: LLVM +BasedOnStyle: Google IndentWidth: 4 ColumnLimit: 100 AccessModifierOffset: -4 diff --git a/.github/workflows/cpp-linter.yml b/.github/workflows/cpp-linter.yml index e013a62c7..7e9525b6c 100644 --- a/.github/workflows/cpp-linter.yml +++ b/.github/workflows/cpp-linter.yml @@ -4,7 +4,7 @@ on: push: branches: [ "*" ] pull_request: - branches: [ "dev*", "main", "*release" ] + branches: [ "dev*", "main", "*release", "feature*" ] jobs: @@ -25,7 +25,7 @@ jobs: files-changed-only: true lines-changed-only: diff format-review: true - thread-comments: ${{ github.event_name == 'pull_request' && 'update' }} + version: 20 - name: Fail fast?! if: steps.linter.outputs.checks-failed != 0 diff --git a/.github/workflows/ucmstore.yml b/.github/workflows/ucmstore.yml index da1e0460b..5d6ce9b47 100644 --- a/.github/workflows/ucmstore.yml +++ b/.github/workflows/ucmstore.yml @@ -6,14 +6,14 @@ on: push: branches: [ "*" ] pull_request: - branches: [ "dev*", "main", "*release" ] + branches: [ "dev*", "main", "*release", "feature*" ] env: # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) BUILD_TYPE: Debug jobs: - ci: + cc_gtest: # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. # You can convert this to a matrix build if you need cross-platform coverage. # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix @@ -24,20 +24,12 @@ jobs: - name: Install googletest run: | - git clone https://github.com/google/googletest.git --depth=1 --branch=v1.12.0 + git clone https://github.com/google/googletest.git --depth=1 --branch=v1.17.0 cd googletest mkdir build && cd build cmake -DCMAKE_CXX_FLAGS="-fPIC" -DCMAKE_C_FLAGS="-fPIC" -DCMAKE_CXX_STANDARD=17 -DCMAKE_CXX_STANDARD_REQUIRED=True .. sudo make install -j - - name: Install mockcpp - run: | - git clone https://github.com/sinojelly/mockcpp.git --depth=1 - cd mockcpp - mkdir build && cd build - cmake -DCMAKE_CXX_FLAGS="-fPIC" -DCMAKE_C_FLAGS="-fPIC" -DCMAKE_CXX_STANDARD=17 -DCMAKE_CXX_STANDARD_REQUIRED=True -DMOCKCPP_XUNIT="gtest" -DMOCKCPP_XUNIT_HOME=/usr/local/ .. - sudo make install -j - - name: Configure CMake # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type @@ -45,7 +37,7 @@ jobs: - name: Build # Build your program with the given configuration - run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} -j - name: Test working-directory: ${{github.workspace}}/build diff --git a/.github/workflows/unifiedcache_test.yml b/.github/workflows/unifiedcache_test.yml index ead608ba8..9fa8d1a84 100644 --- a/.github/workflows/unifiedcache_test.yml +++ b/.github/workflows/unifiedcache_test.yml @@ -6,11 +6,13 @@ on: - 'main' - 'dev*' - '*release' + - 'feature*' pull_request: branches: - 'main' - 'dev*' - '*release' + - 'feature*' jobs: # gpu-test: diff --git a/setup.py b/setup.py index 0f22da523..362da1aa4 100644 --- a/setup.py +++ b/setup.py @@ -102,7 +102,7 @@ def build_cmake(self, ext: CMakeExtension): setup( name="uc-manager", - version="0.1.2", + version="0.2.0rc1", description="Unified Cache Management", author="Unified Cache Team", packages=find_packages(), diff --git a/ucm/integration/vllm/ucm_connector.py b/ucm/integration/vllm/ucm_connector.py index 66216a255..ee8558e84 100644 --- a/ucm/integration/vllm/ucm_connector.py +++ b/ucm/integration/vllm/ucm_connector.py @@ -4,7 +4,7 @@ import pickle import time from dataclasses import dataclass, field -from typing import TYPE_CHECKING, Callable, List, Optional +from typing import TYPE_CHECKING, Callable, List, Optional, Tuple import torch from vllm.config import VllmConfig @@ -20,8 +20,8 @@ from ucm.logger import init_logger from ucm.shared.metrics import ucmmonitor from ucm.shared.metrics.observability import UCMStatsLogger -from ucm.store.factory import UcmConnectorFactory -from ucm.store.ucmstore import Task, UcmKVStoreBase +from ucm.store.factory_v1 import UcmConnectorFactoryV1 +from ucm.store.ucmstore_v1 import Task, UcmKVStoreBaseV1 from ucm.utils import Config if TYPE_CHECKING: @@ -35,7 +35,7 @@ @dataclass class RequestMeta: - ucm_block_ids: list[str] = field(default_factory=list) + ucm_block_ids: list[bytes] = field(default_factory=list) hbm_hit_block_num: int = 0 # local_computed_block + external_computed_block total_hit_block_num: int = 0 @@ -47,9 +47,9 @@ class RequestMeta: @dataclass class RequestDispatchMeta: load_block_ids: tuple[ - list[str], list[int] + list[bytes], list[int] ] # [0] mean ucm_block_ids, [1] means vllm_block_ids - dump_block_ids: tuple[list[str], list[int]] + dump_block_ids: tuple[list[bytes], list[int]] @dataclass @@ -69,14 +69,14 @@ def __init__(self, vllm_config, rank_id): if RequestHasher._SEED_HASH is None: RequestHasher._SEED_HASH = self("UCM_HASH_SEED") - def __call__(self, input_data) -> int: - if isinstance(input_data, str): - input_bytes = input_data.encode("utf-8") + def __call__(self, input_data) -> bytes: + if isinstance(input_data, bytes): + input_bytes = input_data else: input_bytes = pickle.dumps(input_data, protocol=pickle.HIGHEST_PROTOCOL) h = hashlib.md5(self.meta_bytes + input_bytes) - return int.from_bytes(h.digest(), byteorder="big") + return h.digest() class UCMDirectConnector(KVConnectorBase_V1): @@ -95,6 +95,10 @@ def __init__(self, vllm_config: "VllmConfig", role: KVConnectorRole): self.block_size = self._vllm_config.cache_config.block_size self.is_mla = self._vllm_config.model_config.is_deepseek_mla self.is_dsa = False + self.num_layers = self._vllm_config.model_config.get_num_layers( + self._vllm_config.parallel_config + ) + self.tp_size = self._vllm_config.parallel_config.tensor_parallel_size self.kv_cache_dtype: torch.dtype = None if current_platform.is_cuda_alike(): @@ -110,21 +114,19 @@ def __init__(self, vllm_config: "VllmConfig", role: KVConnectorRole): if self.local_rank >= 0: self.device = torch_dev.device(f"{dev_name}:{self.local_rank}") - self._layer_offset_cache = {} - - self.store: UcmKVStoreBase - if role == KVConnectorRole.SCHEDULER: - self.request_hasher = RequestHasher(vllm_config, 0) - else: - self.request_hasher = RequestHasher(vllm_config, self.global_rank) + self.k_store: UcmKVStoreBaseV1 + self.v_store: Optional[UcmKVStoreBaseV1] = None # save block info, avoid hash request twice, and track them until request finished self.requests_meta: dict[str, RequestMeta] = {} ucm_config = Config(vllm_config.kv_transfer_config) + self.engine_id = vllm_config.kv_transfer_config.engine_id self.launch_config = ucm_config.get_config() - + logger.info(f"self.launch_config: {self.launch_config}") + self.connector_configs = self.launch_config.get("ucm_connectors", []) + assert len(self.connector_configs) > 0, "no storage connector name in config." self.load_only_first_rank: bool = ( self.launch_config.get("load_only_first_rank", self.is_mla) and self.is_mla ) @@ -134,42 +136,28 @@ def __init__(self, vllm_config: "VllmConfig", role: KVConnectorRole): self.broadcast_fn = self.group_coordinator.broadcast self.broadcast_stream = torch.cuda.Stream() - logger.info(f"self.launch_config: {self.launch_config}") - connector_configs = self.launch_config.get("ucm_connectors", []) - assert len(connector_configs) > 0, "no storage connector name in config." - - name = connector_configs[0].get("ucm_connector_name") - config = connector_configs[0].get("ucm_connector_config") or {} - config["device"] = self.local_rank - config["role"] = "scheduler" if role == KVConnectorRole.SCHEDULER else "worker" - element_size = vllm_config.model_config.dtype.itemsize - single_head_dim = vllm_config.model_config.get_head_size() - num_head_per_tp = vllm_config.model_config.get_num_kv_heads( - vllm_config.parallel_config - ) - total_tp_size = vllm_config.parallel_config.tensor_parallel_size - num_layers = vllm_config.model_config.get_num_layers( - vllm_config.parallel_config - ) - block_size_per_layer = self.block_size * element_size * single_head_dim - config["kv_block_size"] = ( - block_size_per_layer - * num_layers - * (1 if self.is_mla else num_head_per_tp * 2) - ) - config["io_size"] = block_size_per_layer * ( - 1 if self.is_mla else num_head_per_tp - ) - self.store = UcmConnectorFactory.create_connector(name, config) - self.block_data_size = config["kv_block_size"] - - logger.info("init UCConnectorImpl, connector: %s", name) + name = self.connector_configs[0].get("ucm_connector_name") + config = self.connector_configs[0].get("ucm_connector_config") or {} + storage_backends = [ + path for path in config["storage_backends"].split(":") if path + ] + self.k_storage_backends = [os.path.join(p, "k") for p in storage_backends] + self.v_storage_backends = [os.path.join(p, "v") for p in storage_backends] + os.makedirs(self.k_storage_backends[0], exist_ok=True) + os.makedirs(self.v_storage_backends[0], exist_ok=True) logger.info( - "single file size = %d MB, io_size = %d KB,", - config["kv_block_size"] / 1024 / 1024, - config["io_size"] / 1024, + f"Created subdirectories: {self.k_storage_backends}, {self.v_storage_backends}" ) + if role == KVConnectorRole.SCHEDULER: + self.request_hasher = RequestHasher(vllm_config, 0) + # init scheduler-size connector + config["storage_backends"] = ":".join(self.k_storage_backends) + config["role"] = "scheduler" + self.k_store = UcmConnectorFactoryV1.create_connector(name, config) + else: + self.request_hasher = RequestHasher(vllm_config, self.global_rank) + self.metrics_config = self.launch_config.get("metrics_config_path", "") if self.metrics_config: self.stats_logger = UCMStatsLogger( @@ -188,7 +176,7 @@ def __init__(self, vllm_config: "VllmConfig", role: KVConnectorRole): # invlalid block ids due to load errors self._invalid_block_ids: set[int] = set() - def generate_hash(self, block_size: int, request: "Request") -> list[str]: + def generate_hash(self, block_size: int, request: "Request") -> list[bytes]: token_ids = request.all_token_ids ret = [] @@ -205,10 +193,81 @@ def generate_hash(self, block_size: int, request: "Request") -> list[str]: (parent_block_hash_value, block_token_ids_tuple) ) parent_block_hash_value = hash_value - ret.append(str(hash_value)) + ret.append(hash_value) return ret + def register_kv_caches(self, kv_caches: dict[str, torch.Tensor]): + self.kv_caches = kv_caches + sample_kv_layer = next(iter(self.kv_caches.values())) + if self.kv_cache_dtype is None: + self.kv_cache_dtype = sample_kv_layer[0].dtype + if isinstance(sample_kv_layer, torch.Tensor): + logger.info(f"kv cache shape {sample_kv_layer.shape}") + elif isinstance(sample_kv_layer, Tuple): + # Since vllm_ascend >= 0.10.0, the MLA model's tensor shape has changed to Tuple + # [(num_blocks, block_size, num_kv_heads, nope_dim/rope_dim)] + # Currently, we treat it as GQA, and use is_dsa to mark it + for i, tensor in enumerate(sample_kv_layer): + logger.info(f"kv cache shape {i}: {tensor.shape}") + if self.is_mla: + self.is_mla = False + self.is_dsa = True + logger.info(f"use mla: {self.is_mla}, use dsa: {self.is_dsa}") + + # init work-side connector + # When handling the GQA case, we will separately dump the k_cache and v_cache. + name = self.connector_configs[0].get("ucm_connector_name") + config = self.connector_configs[0].get("ucm_connector_config") or {} + config["device"] = self.local_rank + config["role"] = "worker" + config["local_rank_size"] = self.tp_size if self.is_mla or self.is_dsa else 1 + if len(sample_kv_layer) == 2: + k_io_size = ( + sample_kv_layer[0][0].numel() * sample_kv_layer[0][0].element_size() + ) + config["io_size"] = k_io_size + config["kv_block_size"] = k_io_size * self.num_layers + config["storage_backends"] = ":".join(self.k_storage_backends) + config["unique_id"] = self.engine_id + "k" + self.k_store = UcmConnectorFactoryV1.create_connector(name, config) + logger.info("init UCConnectorImpl, k_connector: %s", name) + logger.info( + "single file size = %.3f MB, io_size = %d KB,", + config["kv_block_size"] / 1024 / 1024, + config["io_size"] / 1024, + ) + + v_io_size = ( + sample_kv_layer[1][0].numel() * sample_kv_layer[1][0].element_size() + ) + config["io_size"] = v_io_size + config["kv_block_size"] = v_io_size * self.num_layers + config["storage_backends"] = ":".join(self.v_storage_backends) + config["unique_id"] = self.engine_id + "v" + self.v_store = UcmConnectorFactoryV1.create_connector(name, config) + logger.info("init UCConnectorImpl, v_connector: %s", name) + logger.info( + "single file size = %.3f MB, io_size = %d KB,", + config["kv_block_size"] / 1024 / 1024, + config["io_size"] / 1024, + ) + self.block_data_size = (k_io_size + v_io_size) * self.num_layers + else: + k_io_size = sample_kv_layer[0].numel() * sample_kv_layer[0].element_size() + config["io_size"] = k_io_size + config["kv_block_size"] = k_io_size * self.num_layers + config["storage_backends"] = ":".join(self.k_storage_backends) + config["unique_id"] = self.engine_id + "k" + self.k_store = UcmConnectorFactoryV1.create_connector(name, config) + logger.info("init UCConnectorImpl, k_connector: %s", name) + logger.info( + "single file size = %.3f MB, io_size = %d KB,", + config["kv_block_size"] / 1024 / 1024, + config["io_size"] / 1024, + ) + self.block_data_size = k_io_size * self.num_layers + def get_num_new_matched_tokens( self, request: "Request", @@ -223,7 +282,7 @@ def get_num_new_matched_tokens( if not external_block_ids: return 0, False - lookup_results = self.store.lookup(external_block_ids) + lookup_results = self.k_store.lookup(external_block_ids) external_hit_blocks = 0 for i, hit in enumerate(lookup_results): if not hit: @@ -361,30 +420,6 @@ def build_connector_meta( return UCMConnectorMetadata(requests_dispatch_meta) - def _init_kv_caches_from_forward_context(self, forward_context: "ForwardContext"): - if len(self.kv_caches) > 0: - return - for layer_name in forward_context.no_compile_layers: - attn_layer = forward_context.no_compile_layers[layer_name] - if not hasattr(attn_layer, "kv_cache"): - continue - - if layer_name not in self.kv_caches: - self.kv_caches[layer_name] = attn_layer.kv_cache[ - forward_context.virtual_engine - ] - # Since vllm_ascend >= 0.10.0, the MLA model's tensor shape has changed to - # (2, num_blocks, block_size, num_kv_heads, nope_dim/rope_dim). - # Currently, we treat it as GQA, and use is_dsa to mark it, - # which works but leads to space inefficiency. - # TODO: Optimize this to avoid unnecessary space usage. - sample_kv_layer = next(iter(self.kv_caches.values())) - if self.is_mla and len(sample_kv_layer) == 2: - self.is_mla = False - self.is_dsa = True - if self.kv_cache_dtype is None: - self.kv_cache_dtype = sample_kv_layer[0].dtype - @staticmethod def _extract_layer_index(layer_name: str) -> Optional[int]: """ @@ -395,70 +430,36 @@ def _extract_layer_index(layer_name: str) -> Optional[int]: return int(chunk) return None - def _precompute_layer_offsets(self): - if not self.kv_caches: - return - - sample_kv_layer = next(iter(self.kv_caches.values())) - elem_size = sample_kv_layer[0].element_size() - block_data_size = ( - sample_kv_layer[0].numel() if self.is_mla else sample_kv_layer[0][0].numel() - ) * elem_size - layer_data_size = block_data_size if self.is_mla else block_data_size * 2 - - # precompute all layers offset - for layer_name, _ in self.kv_caches.items(): - layer_id = self._extract_layer_index(layer_name) - assert layer_id is not None - k_offset = layer_data_size * layer_id - v_offset = k_offset + block_data_size if not self.is_mla else 0 - self._layer_offset_cache[layer_name] = (k_offset, v_offset) - - def _get_tensor_and_offset( - self, vllm_block_ids: list[int], kv_layer: torch.Tensor, layer_name: str - ) -> tuple[list[torch.Tensor], list[int]]: + def _get_tensors( + self, vllm_block_id: int + ) -> Tuple[List[torch.Tensor], List[torch.Tensor]]: """ GQA/MHA: one layer shape is (2, num_blocks, block_size, num_kv_heads, head_size) MLA: one layer shape is (num_blocks, block_size, head_size) """ - k_tensors, k_offsets = [], [] - v_tensors, v_offsets = [], [] - k_offset, v_offset = self._layer_offset_cache[layer_name] - - for vllm_block_id in vllm_block_ids: + k_tensors, v_tensors = [], [] + for _, kv_layer in self.kv_caches.items(): k_tensors.append( kv_layer[vllm_block_id] if self.is_mla else kv_layer[0][vllm_block_id] ) - k_offsets.append(k_offset) if not self.is_mla: v_tensors.append(kv_layer[1][vllm_block_id]) - v_offsets.append(v_offset) - return k_tensors + v_tensors, k_offsets + v_offsets - - def _generate_task(self, vllm_block_ids: List[int], ucm_block_ids: List[str]): - if not self._layer_offset_cache: - self._precompute_layer_offsets() - - num_layers = len(self.kv_caches) - num_blocks_per_layer = len(vllm_block_ids) - num_tensors_per_layer = num_blocks_per_layer * (1 if self.is_mla else 2) - dst_tensor_addr = [None] * (num_layers * num_tensors_per_layer) - ucm_offsets = [0] * (num_layers * num_tensors_per_layer) - - idx = 0 - for layer_name, one_layer_kv_cache in self.kv_caches.items(): - tensors, offsets = self._get_tensor_and_offset( - vllm_block_ids, one_layer_kv_cache, layer_name - ) - dst_tensor_addr[idx : idx + len(tensors)] = tensors - ucm_offsets[idx : idx + len(offsets)] = offsets - idx += len(tensors) - - repeat_times = len(self.kv_caches) * (1 if self.is_mla else 2) - ucm_total_block_ids = ucm_block_ids * repeat_times - - assert len(ucm_total_block_ids) == len(ucm_offsets) == len(dst_tensor_addr) - return ucm_total_block_ids, ucm_offsets, dst_tensor_addr + return k_tensors, v_tensors + + def _generate_task( + self, vllm_block_ids: List[int], ucm_block_ids: List[bytes] + ) -> Tuple[ + List[bytes], List[int], List[List[torch.Tensor]], List[List[torch.Tensor]] + ]: + block_ids, shard_indexs, total_k_tensors, total_v_tensors = [], [], [], [] + for i, vllm_block_id in enumerate(vllm_block_ids): + k_tensors, v_tensors = self._get_tensors(vllm_block_id) + block_ids.append(ucm_block_ids[i]) + total_k_tensors.append(k_tensors) + total_v_tensors.append(v_tensors) + shard_indexs.append(0) + + return block_ids, shard_indexs, total_k_tensors, total_v_tensors def _broadcast(self, dst_tensor_addr: list[torch.Tensor]): rec_tensor: torch.Tensor = None @@ -483,9 +484,7 @@ def start_load_kv(self, forward_context: "ForwardContext", **kwargs) -> None: metadata = self._get_connector_metadata() assert isinstance(metadata, UCMConnectorMetadata) - self._init_kv_caches_from_forward_context(forward_context) - - request_to_task: dict[str, Optional[Task]] = {} + request_to_task: dict[str, Optional[List[Task]]] = {} req_broadcast_addr = {} is_load = False num_loaded_block = 0 @@ -501,26 +500,34 @@ def start_load_kv(self, forward_context: "ForwardContext", **kwargs) -> None: ucm_block_ids, vllm_block_ids = request.load_block_ids if self.global_rank != 0 and not self.is_mla and not self.is_dsa: for i, ucm_block_id in enumerate(ucm_block_ids): - ucm_block_ids[i] = str(self.request_hasher(ucm_block_id)) - ucm_total_block_ids, ucm_offsets, dst_tensor_addr = self._generate_task( + ucm_block_ids[i] = self.request_hasher(ucm_block_id) + block_ids, shard_indexs, k_tensors, v_tensors = self._generate_task( vllm_block_ids, ucm_block_ids ) if self.global_rank == 0 or not self.load_only_first_rank: - request_to_task[request_id] = self.store.load( - ucm_total_block_ids, ucm_offsets, dst_tensor_addr - ) + k_task = self.k_store.load(block_ids, shard_indexs, k_tensors) + request_to_task[request_id] = [k_task] + if v_tensors and self.v_store: + v_task = self.v_store.load(block_ids, shard_indexs, v_tensors) + request_to_task[request_id].append(v_task) else: request_to_task[request_id] = None - req_broadcast_addr[request_id] = dst_tensor_addr + req_broadcast_addr[request_id] = [t for row in k_tensors for t in row] + [ + t for row in v_tensors for t in row + ] - for request_id, task in request_to_task.items(): + for request_id, tasks in request_to_task.items(): # TODO error handling if self.global_rank == 0 or not self.load_only_first_rank: - if self.store.wait(task) != 0: + try: + self.k_store.wait(tasks[0]) + if len(tasks) > 1 and self.v_store: + self.v_store.wait(tasks[1]) + except RuntimeError as e: + logger.error("request {request_id} load kv cache failed.:", e) self._invalid_block_ids.update( metadata.request_meta[request_id].load_block_ids[1] ) - logger.error(f"request {request_id} load kv cache failed.") if self.load_only_first_rank: self._broadcast(req_broadcast_addr[request_id]) load_end_time = time.perf_counter() * 1000 @@ -567,8 +574,7 @@ def wait_for_save(self) -> None: metadata = self._get_connector_metadata() assert isinstance(metadata, UCMConnectorMetadata) - request_to_task: dict[str, Task] = {} - request_to_blocks: dict[str, list[str]] = {} + request_to_task: dict[str, List[Task]] = {} is_save = False num_saved_block = 0 num_saved_request = 0 @@ -583,36 +589,23 @@ def wait_for_save(self) -> None: ucm_block_ids, vllm_block_ids = request.dump_block_ids if self.global_rank != 0: for i, ucm_block_id in enumerate(ucm_block_ids): - ucm_block_ids[i] = str(self.request_hasher(ucm_block_id)) - rets = self.store.create(ucm_block_ids) - end = 0 - for i, ret in enumerate(rets): - if ret != 0: - logger.error( - f"create blocks for {request_id} failed, block index: {i}, ret code: {ret}" - ) - break - end += 1 - - if end == 0: - continue - ucm_block_ids = ucm_block_ids[:end] - vllm_block_ids = vllm_block_ids[:end] - ucm_total_block_ids, ucm_offsets, dst_tensor_addr = self._generate_task( + ucm_block_ids[i] = self.request_hasher(ucm_block_id) + block_ids, shard_indexs, k_tensors, v_tensors = self._generate_task( vllm_block_ids, ucm_block_ids ) - request_to_task[request_id] = self.store.dump( - ucm_total_block_ids, ucm_offsets, dst_tensor_addr - ) - request_to_blocks[request_id] = ucm_block_ids - - for request_id, task in request_to_task.items(): - ucm_block_ids = request_to_blocks[request_id] - if self.store.wait(task) == 0: - self.store.commit(ucm_block_ids, True) - else: - logger.error(f"request {request_id} dump kv cache failed.") - self.store.commit(ucm_block_ids, False) + k_task = self.k_store.dump(block_ids, shard_indexs, k_tensors) + request_to_task[request_id] = [k_task] + if v_tensors and self.v_store: + v_task = self.v_store.dump(block_ids, shard_indexs, v_tensors) + request_to_task[request_id].append(v_task) + + for request_id, tasks in request_to_task.items(): + try: + self.k_store.wait(tasks[0]) + if len(tasks) > 1 and self.v_store: + self.v_store.wait(tasks[1]) + except RuntimeError as e: + logger.error("request {request_id} dump kv cache failed.:", e) save_end_time = time.perf_counter() * 1000 save_speed = ( num_saved_block @@ -793,6 +786,16 @@ def update_state_after_alloc( """ self.connector.update_state_after_alloc(request, blocks, num_external_tokens) + def register_kv_caches(self, kv_caches: dict[str, torch.Tensor]): + """ + Initialize with the KV caches. Useful for pre-registering the + KV Caches in the KVConnector (e.g. for NIXL). + + Args: kv_caches: + dictionary of layer names, kv cache + """ + self.connector.register_kv_caches(kv_caches) + def build_connector_meta( self, scheduler_output: SchedulerOutput ) -> KVConnectorMetadata: diff --git a/ucm/shared/infra/status/status.h b/ucm/shared/infra/status/status.h index 3711de842..bbe6f2157 100644 --- a/ucm/shared/infra/status/status.h +++ b/ucm/shared/infra/status/status.h @@ -27,6 +27,7 @@ #include #include #include +#include namespace UC { @@ -49,6 +50,7 @@ class Status { static constexpr int32_t EDESERIALIZE_ = __MakeStatusCode<7>(); static constexpr int32_t EUNSUPPORTED_ = __MakeStatusCode<8>(); static constexpr int32_t ENOSPACE_ = __MakeStatusCode<9>(); + static constexpr int32_t ETIMEOUT_ = __MakeStatusCode<10>(); int32_t code_; std::string message_; explicit Status(int32_t code) : code_(code) {} @@ -72,8 +74,15 @@ class Status { static Status Error(std::string message) { return {ERROR_, std::move(message)}; } static Status Error() { return Status{ERROR_}; } static Status InvalidParam() { return Status{EPARAM_}; } + static Status InvalidParam(std::string message) { return {EPARAM_, std::move(message)}; } + template + static Status InvalidParam(fmt::format_string fmt, Args&&... args) + { + return InvalidParam(fmt::format(fmt, std::forward(args)...)); + } static Status OutOfMemory() { return Status{EOOM_}; } static Status OsApiError() { return Status{EOSERROR_}; } + static Status OsApiError(std::string message) { return Status{EOSERROR_, std::move(message)}; } static Status DuplicateKey() { return Status{EDUPLICATE_}; } static Status Retry() { return Status{ERETRY_}; } static Status NotFound() { return Status{ENOOBJ_}; } @@ -81,10 +90,25 @@ class Status { static Status DeserializeFailed() { return Status{EDESERIALIZE_}; } static Status Unsupported() { return Status{EUNSUPPORTED_}; } static Status NoSpace() { return Status{ENOSPACE_}; } + static Status Timeout() { return Status{ETIMEOUT_}; } +}; + +template +class Expected { + std::variant v_; + +public: + Expected(T&& val) : v_(std::move(val)) {} + Expected(Status err) : v_(err) {} + bool HasValue() const noexcept { return v_.index() == 1; } + explicit operator bool() const noexcept { return HasValue(); } + T& Value() & { return std::get(v_); } + T&& Value() && { return std::get(std::move(v_)); } + Status Error() const { return std::get(v_); } }; inline std::string format_as(const Status& status) { return status.ToString(); } -} // namespace UC +} // namespace UC #endif diff --git a/ucm/shared/infra/template/spsc_ring_queue.h b/ucm/shared/infra/template/spsc_ring_queue.h new file mode 100644 index 000000000..7bb7efbde --- /dev/null +++ b/ucm/shared/infra/template/spsc_ring_queue.h @@ -0,0 +1,121 @@ +/** + * MIT License + * + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * */ +#ifndef UNIFIEDCACHE_INFRA_SPSC_RING_QUEUE_H +#define UNIFIEDCACHE_INFRA_SPSC_RING_QUEUE_H + +#include +#include +#include +#include +#include +#include + +namespace UC { + +template +class SpscRingQueue { + alignas(64) std::atomic head_ = 0; + alignas(64) std::atomic tail_ = 0; + bool pow2_{false}; + size_t mask_{0}; + size_t capacity_{0}; + std::unique_ptr buffer_; + + size_t Mod(size_t n) { return pow2_ ? (n & mask_) : (n % capacity_); } + +public: + void Setup(size_t capacity) + { + capacity_ = capacity; + mask_ = capacity_ - 1; + pow2_ = (capacity_ & mask_) == 0; + buffer_ = std::make_unique(capacity_); + } + + void Push(T&& value) + { + while (true) { + const size_t currentHead = head_.load(std::memory_order_relaxed); + const size_t nextHead = Mod(currentHead + 1); + if (nextHead != tail_.load(std::memory_order_acquire)) { + buffer_[currentHead] = std::move(value); + head_.store(nextHead, std::memory_order_release); + return; + } + std::this_thread::yield(); + } + } + + bool TryPush(T&& value) + { + const size_t currentHead = head_.load(std::memory_order_relaxed); + const size_t nextHead = Mod(currentHead + 1); + const size_t currentTail = tail_.load(std::memory_order_acquire); + if (nextHead == currentTail) { return false; } + buffer_[currentHead] = std::move(value); + head_.store(nextHead, std::memory_order_release); + return true; + } + + bool TryPop(T& value) + { + const size_t currentHead = head_.load(std::memory_order_acquire); + const size_t currentTail = tail_.load(std::memory_order_relaxed); + if (currentTail == currentHead) { return false; } + value = std::move(buffer_[currentTail]); + tail_.store(Mod(currentTail + 1), std::memory_order_release); + return true; + } + + template + void ConsumerLoop(const std::atomic_bool& stop, ConsumerHandler&& handler, Args&&... args) + { + constexpr size_t kSpinLimit = 16; + constexpr size_t kTaskBatch = 64; + size_t spinCount = 0; + size_t taskCount = 0; + T task; + while (!stop.load(std::memory_order_relaxed)) { + if (TryPop(task)) { + spinCount = 0; + std::invoke(handler, std::forward(args)..., std::move(task)); + if (++taskCount % kTaskBatch == 0) { + if (stop.load(std::memory_order_acquire)) { break; } + } + continue; + } + if (++spinCount < kSpinLimit) { + std::this_thread::yield(); + } else { + if (stop.load(std::memory_order_acquire)) { break; } + std::this_thread::sleep_for(std::chrono::microseconds(100)); + spinCount = 0; + } + } + } +}; + +} // namespace UC + +#endif diff --git a/ucm/shared/infra/thread/latch.h b/ucm/shared/infra/thread/latch.h index fb1dcf583..037c916b7 100644 --- a/ucm/shared/infra/thread/latch.h +++ b/ucm/shared/infra/thread/latch.h @@ -28,21 +28,25 @@ #include #include #include +#include "time/now_time.h" namespace UC { class Latch { public: - explicit Latch(const size_t expected = 0) : counter_{expected} {} + Latch() : startTp{NowTime::Now()} {} + void Set(size_t expected) noexcept { this->counter_.store(expected); } + void SetEpilog(std::function finish) noexcept { finish_ = std::move(finish); } void Up() { ++this->counter_; } - void Done(std::function finish) noexcept + void Done(std::function&& finish = nullptr) noexcept { auto counter = this->counter_.load(std::memory_order_acquire); while (counter > 0) { auto desired = counter - 1; if (this->counter_.compare_exchange_weak(counter, desired, std::memory_order_acq_rel)) { if (desired == 0) { - if (finish) { finish(); } + auto& fn = finish ? finish : finish_; + if (fn) { fn(); } std::lock_guard lg(this->mutex_); this->cv_.notify_all(); } @@ -57,13 +61,33 @@ class Latch { if (this->counter_ == 0) { return; } this->cv_.wait(lk, [this] { return this->counter_ == 0; }); } + bool WaitFor(size_t timeoutMs) noexcept + { + if (timeoutMs == 0) { + this->Wait(); + return true; + } + std::unique_lock lk(this->mutex_); + if (this->counter_ == 0) { return true; } + auto elapsed = std::chrono::duration(NowTime::Now() - startTp); + auto elapsedMs = std::chrono::duration_cast(elapsed); + auto timeMs = std::chrono::milliseconds(timeoutMs); + if (timeMs <= elapsedMs) { return false; } + auto remainMs = timeMs - elapsedMs; + return this->cv_.wait_for(lk, remainMs, [this] { return this->counter_ == 0; }); + } + bool Check() noexcept { return this->counter_ == 0; } + +public: + double startTp{0}; protected: std::mutex mutex_; std::condition_variable cv_; - std::atomic counter_; + std::atomic counter_{0}; + std::function finish_{nullptr}; }; -} // namespace UC +} // namespace UC -#endif // UNIFIEDCACHE_INFRA_LATCH_H +#endif // UNIFIEDCACHE_INFRA_LATCH_H diff --git a/ucm/shared/infra/thread/thread_pool.h b/ucm/shared/infra/thread/thread_pool.h index baa514ed7..e2aecdef2 100644 --- a/ucm/shared/infra/thread/thread_pool.h +++ b/ucm/shared/infra/thread/thread_pool.h @@ -24,12 +24,17 @@ #ifndef UNIFIEDCACHE_INFRA_THREAD_POOL_H #define UNIFIEDCACHE_INFRA_THREAD_POOL_H +#include #include #include #include #include +#include #include +#include #include +#include +#include namespace UC { @@ -37,8 +42,25 @@ template class ThreadPool { using WorkerInitFn = std::function; using WorkerFn = std::function; + using WorkerTimeoutFn = std::function; using WorkerExitFn = std::function; + class StopToken { + std::shared_ptr> flag_ = std::make_shared>(false); + + public: + void RequestStop() noexcept { this->flag_->store(true, std::memory_order_relaxed); } + bool StopRequested() const noexcept { return this->flag_->load(std::memory_order_relaxed); } + }; + + struct Worker { + ssize_t tid; + std::thread th; + StopToken stop; + std::weak_ptr current; + std::atomic tp{}; + }; + public: ThreadPool() = default; ThreadPool(const ThreadPool&) = delete; @@ -46,12 +68,13 @@ class ThreadPool { ~ThreadPool() { { - std::unique_lock lk(this->mtx_); + std::lock_guard lock(this->taskMtx_); this->stop_ = true; this->cv_.notify_all(); } - for (auto& w : this->workers_) { - if (w.joinable()) { w.join(); } + if (this->monitor_.joinable()) { this->monitor_.join(); } + for (auto& worker : this->workers_) { + if (worker->th.joinable()) { worker->th.join(); } } } ThreadPool& SetWorkerFn(WorkerFn&& fn) @@ -69,6 +92,14 @@ class ThreadPool { this->exitFn_ = std::move(fn); return *this; } + ThreadPool& SetWorkerTimeoutFn(WorkerTimeoutFn&& fn, const size_t timeoutMs, + const size_t intervalMs = 1000) + { + this->timeoutFn_ = std::move(fn); + this->timeoutMs_ = timeoutMs; + this->intervalMs_ = intervalMs; + return *this; + } ThreadPool& SetNWorker(const size_t nWorker) { this->nWorker_ = nWorker; @@ -77,64 +108,126 @@ class ThreadPool { bool Run() { if (this->nWorker_ == 0) { return false; } - if (!this->fn_) { return false; } - std::list> start(this->nWorker_); - std::list> fut; - for (auto& s : start) { - fut.push_back(s.get_future()); - this->workers_.emplace_back([&] { this->Worker(s); }); + if (this->fn_ == nullptr) { return false; } + this->workers_.reserve(this->nWorker_); + for (size_t i = 0; i < this->nWorker_; i++) { + if (!this->AddOneWorker()) { return false; } } - auto success = true; - for (auto& f : fut) { - if (!f.get()) { success = false; } + if (this->timeoutMs_ > 0) { + this->monitor_ = std::thread([this] { this->MonitorLoop(); }); } - return success; + return true; } void Push(std::list& tasks) noexcept { - std::unique_lock lk(this->mtx_); + std::unique_lock lock(this->taskMtx_); this->taskQ_.splice(this->taskQ_.end(), tasks); this->cv_.notify_all(); } void Push(Task&& task) noexcept { - std::unique_lock lk(this->mtx_); + std::unique_lock lock(this->taskMtx_); this->taskQ_.push_back(std::move(task)); this->cv_.notify_one(); } private: - void Worker(std::promise& started) noexcept + bool AddOneWorker() + { + try { + auto worker = std::make_shared(); + std::promise prom; + auto fut = prom.get_future(); + worker->th = std::thread([this, worker, &prom] { this->WorkerLoop(prom, worker); }); + auto success = fut.get(); + if (!success) { return false; } + this->workers_.push_back(worker); + return true; + } catch (...) { + return false; + } + } + void WorkerLoop(std::promise& prom, std::shared_ptr worker) { + worker->tid = syscall(SYS_gettid); WorkerArgs args = nullptr; auto success = true; if (this->initFn_) { success = this->initFn_(args); } - started.set_value(success); + prom.set_value(success); while (success) { - std::unique_lock lk(this->mtx_); - this->cv_.wait(lk, [this] { return this->stop_ || !this->taskQ_.empty(); }); - if (this->stop_) { break; } - if (this->taskQ_.empty()) { continue; } - auto task = std::make_shared(std::move(this->taskQ_.front())); - this->taskQ_.pop_front(); - lk.unlock(); + std::shared_ptr task = nullptr; + { + std::unique_lock lock(this->taskMtx_); + this->cv_.wait(lock, [this, worker] { + return this->stop_ || worker->stop.StopRequested() || !this->taskQ_.empty(); + }); + if (this->stop_ || worker->stop.StopRequested()) { break; } + if (this->taskQ_.empty()) { continue; } + task = std::make_shared(std::move(this->taskQ_.front())); + this->taskQ_.pop_front(); + } + worker->current = task; + worker->tp.store(std::chrono::steady_clock::now(), std::memory_order_relaxed); this->fn_(*task, args); + if (worker->stop.StopRequested()) { break; } + worker->current.reset(); + worker->tp.store({}, std::memory_order_relaxed); } if (this->exitFn_) { this->exitFn_(args); } } + void MonitorLoop() + { + const auto interval = std::chrono::milliseconds(this->intervalMs_); + while (true) { + { + std::unique_lock lock(this->taskMtx_); + this->cv_.wait_for(lock, interval, [this] { return this->stop_; }); + if (this->stop_) { break; } + } + size_t nWorker = this->Monitor(); + for (size_t i = nWorker; i < this->nWorker_; i++) { (void)this->AddOneWorker(); } + } + } + + size_t Monitor() + { + using namespace std::chrono; + const auto timeout = milliseconds(this->timeoutMs_); + size_t nWorker = 0; + for (auto it = this->workers_.begin(); it != this->workers_.end();) { + auto tp = (*it)->tp.load(std::memory_order_relaxed); + auto task = (*it)->current.lock(); + auto now = steady_clock::now(); + if (task && tp != steady_clock::time_point{} && now - tp > timeout) { + if (this->timeoutFn_) { this->timeoutFn_(*task, (*it)->tid); } + (*it)->stop.RequestStop(); + if ((*it)->th.joinable()) { (*it)->th.detach(); } + it = this->workers_.erase(it); + } else { + it++; + } + nWorker++; + } + return nWorker; + } + private: + WorkerInitFn initFn_{nullptr}; + WorkerFn fn_{nullptr}; + WorkerTimeoutFn timeoutFn_{nullptr}; + WorkerExitFn exitFn_{nullptr}; + size_t timeoutMs_{0}; + size_t intervalMs_{0}; + size_t nWorker_{0}; bool stop_{false}; - size_t nWorker_{1}; - std::list workers_; - WorkerInitFn initFn_; - WorkerFn fn_; - WorkerExitFn exitFn_; + std::vector> workers_; + std::thread monitor_; + std::mutex taskMtx_; std::list taskQ_; - std::mutex mtx_; std::condition_variable cv_; }; -} // namespace UC +} // namespace UC #endif diff --git a/ucm/store/localstore/cc/domain/cache/cache_layout.h b/ucm/shared/infra/time/now_time.h similarity index 79% rename from ucm/store/localstore/cc/domain/cache/cache_layout.h rename to ucm/shared/infra/time/now_time.h index c4480b8e5..21fd55f27 100644 --- a/ucm/store/localstore/cc/domain/cache/cache_layout.h +++ b/ucm/shared/infra/time/now_time.h @@ -21,19 +21,22 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * */ -#ifndef UNIFIEDCACHE_CACHE_LAYOUT_H -#define UNIFIEDCACHE_CACHE_LAYOUT_H +#ifndef UNIFIEDCACHE_SHARED_INFRA_TIME_NOW_TIME_H +#define UNIFIEDCACHE_SHARED_INFRA_TIME_NOW_TIME_H -#include +#include namespace UC { -class CacheLayout { +class NowTime { public: - static std::string MetaShmFile(); - static std::string DataShmFile(const size_t id); + static auto Now() + { + auto now = std::chrono::steady_clock::now().time_since_epoch(); + return std::chrono::duration(now).count(); + } }; -} // namespace UC +} // namespace UC #endif diff --git a/ucm/shared/test/CMakeLists.txt b/ucm/shared/test/CMakeLists.txt index 07241d814..b99d46a8f 100644 --- a/ucm/shared/test/CMakeLists.txt +++ b/ucm/shared/test/CMakeLists.txt @@ -5,7 +5,7 @@ if(BUILD_UNIT_TESTS) target_include_directories(ucmshared.test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/case) target_link_libraries(ucmshared.test PRIVATE trans - gtest_main gtest mockcpp + gtest_main gtest ) gtest_discover_tests(ucmshared.test) endif() diff --git a/ucm/shared/test/case/infra/spsc_ring_queue_test.cc b/ucm/shared/test/case/infra/spsc_ring_queue_test.cc new file mode 100644 index 000000000..def570b30 --- /dev/null +++ b/ucm/shared/test/case/infra/spsc_ring_queue_test.cc @@ -0,0 +1,87 @@ +/** + * MIT License + * + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * */ +#include "template/spsc_ring_queue.h" +#include + +class UCSpscRingQueueTest : public testing::Test {}; + +TEST_F(UCSpscRingQueueTest, Basic) +{ + UC::SpscRingQueue queue; + queue.Setup(16); + size_t data; + ASSERT_FALSE(queue.TryPop(data)); + ASSERT_TRUE(queue.TryPush(1023)); + ASSERT_TRUE(queue.TryPop(data)); + ASSERT_EQ(data, 1023); + ASSERT_FALSE(queue.TryPop(data)); +} + +TEST_F(UCSpscRingQueueTest, FIFO) +{ + UC::SpscRingQueue queue; + queue.Setup(16); + constexpr size_t nElem = 10; + for (size_t i = 0; i < nElem; i++) { ASSERT_TRUE(queue.TryPush(std::move(i))); } + for (size_t i = 0; i < nElem; i++) { + size_t value = -1; + ASSERT_TRUE(queue.TryPop(value)); + ASSERT_EQ(value, i); + } + size_t value = -1; + ASSERT_FALSE(queue.TryPop(value)); +} + +TEST_F(UCSpscRingQueueTest, Full) +{ + constexpr size_t N = 10; + UC::SpscRingQueue queue; + queue.Setup(N); + constexpr size_t nElem = N - 1; + for (size_t i = 0; i < nElem; i++) { ASSERT_TRUE(queue.TryPush(std::move(i))); } + ASSERT_FALSE(queue.TryPush(999)); + size_t value = -1; + ASSERT_TRUE(queue.TryPop(value)); + ASSERT_EQ(value, 0); + ASSERT_TRUE(queue.TryPush(999)); +} + +TEST_F(UCSpscRingQueueTest, MoveOnly) +{ + struct MoveOnly { + int value; + MoveOnly() = default; + explicit MoveOnly(int v) : value(v) {} + MoveOnly(const MoveOnly&) = delete; + MoveOnly& operator=(const MoveOnly&) = delete; + MoveOnly(MoveOnly&&) = default; + MoveOnly& operator=(MoveOnly&&) = default; + }; + UC::SpscRingQueue queue; + queue.Setup(9); + EXPECT_TRUE(queue.TryPush(MoveOnly(42))); + MoveOnly obj; + EXPECT_TRUE(queue.TryPop(obj)); + EXPECT_EQ(obj.value, 42); +} diff --git a/ucm/shared/test/case/infra/thread_pool_test.cc b/ucm/shared/test/case/infra/thread_pool_test.cc new file mode 100644 index 000000000..c3805c5dd --- /dev/null +++ b/ucm/shared/test/case/infra/thread_pool_test.cc @@ -0,0 +1,101 @@ +/** + * MIT License + * + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * */ + +#include "thread/thread_pool.h" +#include +#include +#include +#include +#include "thread/latch.h" + +class UCThreadPoolTest : public ::testing::Test {}; + +TEST_F(UCThreadPoolTest, TimeoutDetection) +{ + struct TestTask { + int taskId; + std::atomic* finished; + std::atomic* timeout; + }; + + constexpr size_t nWorker = 2; + constexpr size_t timeoutMs = 20; + std::atomic timeoutCount{0}; + std::atomic taskFinished{false}; + std::atomic taskTimeout{false}; + + UC::ThreadPool threadPool; + threadPool.SetNWorker(nWorker) + .SetWorkerFn([](TestTask& task, const auto&) { + std::this_thread::sleep_for(std::chrono::milliseconds(30)); + *(task.finished) = true; + }) + .SetWorkerTimeoutFn( + [&](TestTask& task, const auto) { + timeoutCount++; + task.timeout->store(true); + }, + timeoutMs, 10) + .Run(); + std::list tasks{ + {1, &taskFinished, &taskTimeout} + }; + threadPool.Push(tasks); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + ASSERT_GT(timeoutCount.load(), 0); + ASSERT_TRUE(taskTimeout.load()); +} + +TEST_F(UCThreadPoolTest, SimulatedFileSystemHang) +{ + struct TestTask { + std::atomic* simulatingHang; + }; + + std::atomic hangDetected{0}; + constexpr size_t hangTimeoutMs = 20; + std::atomic taskHang{true}; + + UC::ThreadPool threadPool; + threadPool.SetNWorker(1) + .SetWorkerFn([](TestTask& task, const auto&) { + std::mutex fakeMutex; + std::unique_lock fakelock(fakeMutex); + std::condition_variable fakeCond; + while (*(task.simulatingHang)) { + fakeCond.wait_for(fakelock, std::chrono::milliseconds(10)); // waiting forever + } + }) + .SetWorkerTimeoutFn( + [&](TestTask& task, const auto) { + hangDetected++; + *(task.simulatingHang) = false; // stop simulating hang + }, + hangTimeoutMs, 10) + .Run(); + std::list tasks{{&taskHang}}; + threadPool.Push(tasks); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + EXPECT_GT(hangDetected.load(), 0); +} \ No newline at end of file diff --git a/ucm/shared/vendor/CMakeLists.txt b/ucm/shared/vendor/CMakeLists.txt index 411139a79..d70e957a6 100644 --- a/ucm/shared/vendor/CMakeLists.txt +++ b/ucm/shared/vendor/CMakeLists.txt @@ -22,6 +22,10 @@ function(EnableDept) endif() message(STATUS "Fetching ${DEPT_NAME}(${DEPT_TAG}) from ${VALID_GIT_URL}") FetchContent_Declare(${DEPT_NAME} GIT_REPOSITORY ${VALID_GIT_URL} GIT_TAG ${DEPT_TAG} GIT_SHALLOW TRUE) + string(TOUPPER ${DEPT_NAME} NAME_UPPER) + set(${NAME_UPPER}_INSTALL OFF CACHE INTERNAL "" FORCE) + set(${NAME_UPPER}_BUILD_TESTS OFF CACHE INTERNAL "" FORCE) + set(${NAME_UPPER}_BUILD_EXAMPLES OFF CACHE INTERNAL "" FORCE) FetchContent_MakeAvailable(${DEPT_NAME}) endfunction() diff --git a/ucm/sparse/gsa/prefetch/CMakeLists.txt b/ucm/sparse/gsa/prefetch/CMakeLists.txt index 7cd054a6f..ace6f497f 100644 --- a/ucm/sparse/gsa/prefetch/CMakeLists.txt +++ b/ucm/sparse/gsa/prefetch/CMakeLists.txt @@ -39,7 +39,7 @@ set(LIBRARIES torch_python gomp pthread - storetask + storeintf ) # NPU特殊配置 diff --git a/ucm/store/CMakeLists.txt b/ucm/store/CMakeLists.txt index c3825360d..b4bd46a44 100644 --- a/ucm/store/CMakeLists.txt +++ b/ucm/store/CMakeLists.txt @@ -1,10 +1,8 @@ -include_directories(.) -add_subdirectory(infra) -add_subdirectory(device) +add_subdirectory(detail) +add_library(storeintf INTERFACE) +target_include_directories(storeintf INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) +target_link_libraries(storeintf INTERFACE storedetail infra_status) add_subdirectory(nfsstore) add_subdirectory(pcstore) -add_subdirectory(dramstore) -add_subdirectory(localstore) add_subdirectory(mooncakestore) -add_subdirectory(task) add_subdirectory(test) diff --git a/ucm/store/detail/CMakeLists.txt b/ucm/store/detail/CMakeLists.txt new file mode 100644 index 000000000..d994453d3 --- /dev/null +++ b/ucm/store/detail/CMakeLists.txt @@ -0,0 +1,3 @@ +file(GLOB_RECURSE UCM_STORE_DETAIL_SOURCE "*.*") +add_library(storedetail OBJECT ${UCM_STORE_DETAIL_SOURCE}) +target_include_directories(storedetail PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/ucm/store/detail/task/task_manager.h b/ucm/store/detail/task/task_manager.h new file mode 100644 index 000000000..49c2f7ef2 --- /dev/null +++ b/ucm/store/detail/task/task_manager.h @@ -0,0 +1,121 @@ +/** + * MIT License + * + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * */ +#ifndef UNIFIEDCACHE_TASK_MANAGER_H +#define UNIFIEDCACHE_TASK_MANAGER_H + +#include +#include "status/status.h" +#include "task_queue.h" +#include "task_set.h" + +namespace UC { + +class TaskManager { + using TaskPtr = std::shared_ptr; + using WaiterPtr = std::shared_ptr; + using TaskPair = std::pair; + using QueuePtr = std::shared_ptr; + +public: + virtual ~TaskManager() = default; + virtual Status Submit(Task&& task, size_t& taskId) noexcept + { + taskId = task.Id(); + const auto taskStr = task.Str(); + TaskPtr taskPtr = nullptr; + WaiterPtr waiterPtr = nullptr; + try { + taskPtr = std::make_shared(std::move(task)); + waiterPtr = std::make_shared(0, task.StartTp()); + } catch (const std::exception& e) { + UC_ERROR("Failed({}) to submit task({}).", e.what(), taskStr); + return Status::OutOfMemory(); + } + std::lock_guard lg(mutex_); + const auto& [iter, success] = + tasks_.emplace(taskId, std::make_pair(std::move(taskPtr), std::move(waiterPtr))); + if (!success) { + UC_ERROR("Failed to submit task({}).", taskStr); + return Status::OutOfMemory(); + } + auto shards = iter->second.first->Split(queues_.size(), iter->second.second); + for (auto& shard : shards) { + auto& q = queues_[qIndex_++]; + if (qIndex_ == queues_.size()) { qIndex_ = 0; } + q->Push(shard); + } + return Status::OK(); + } + virtual Status Wait(const size_t taskId) noexcept + { + TaskPtr task = nullptr; + WaiterPtr waiter = nullptr; + { + std::lock_guard lg(mutex_); + auto iter = tasks_.find(taskId); + if (iter == tasks_.end()) { + UC_ERROR("Not found task by id({}).", taskId); + return Status::NotFound(); + } + task = iter->second.first; + waiter = iter->second.second; + tasks_.erase(iter); + } + if (!waiter->Wait(timeoutMs_)) { + UC_ERROR("Task({}) timeout({}).", task->Str(), timeoutMs_); + failureSet_.Insert(taskId); + waiter->Wait(); + } + auto failure = failureSet_.Contains(taskId); + if (failure) { + failureSet_.Remove(taskId); + UC_ERROR("Task({}) failed.", task->Str()); + return Status::Error(); + } + return Status::OK(); + } + virtual Status Check(const size_t taskId, bool& finish) noexcept + { + std::lock_guard lg(mutex_); + auto iter = tasks_.find(taskId); + if (iter == tasks_.end()) { + UC_ERROR("Not found task by id({}).", taskId); + return Status::NotFound(); + } + finish = iter->second.second->Finish(); + return Status::OK(); + } + +protected: + std::mutex mutex_; + std::unordered_map tasks_; + size_t qIndex_{0}; + std::vector queues_; + size_t timeoutMs_{0}; + TaskSet failureSet_; +}; + +} // namespace UC + +#endif diff --git a/ucm/store/task/task_queue.h b/ucm/store/detail/task/task_queue.h similarity index 100% rename from ucm/store/task/task_queue.h rename to ucm/store/detail/task/task_queue.h diff --git a/ucm/store/task/task_set.h b/ucm/store/detail/task/task_set.h similarity index 100% rename from ucm/store/task/task_set.h rename to ucm/store/detail/task/task_set.h diff --git a/ucm/store/task/task_shard.h b/ucm/store/detail/task/task_shard.h similarity index 100% rename from ucm/store/task/task_shard.h rename to ucm/store/detail/task/task_shard.h diff --git a/ucm/store/localstore/cc/domain/cache/cache_data.h b/ucm/store/detail/task/task_waiter.h similarity index 72% rename from ucm/store/localstore/cc/domain/cache/cache_data.h rename to ucm/store/detail/task/task_waiter.h index 89a9536a2..f4bfa5c79 100644 --- a/ucm/store/localstore/cc/domain/cache/cache_data.h +++ b/ucm/store/detail/task/task_waiter.h @@ -21,24 +21,25 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * */ -#ifndef UNIFIEDCACHE_CACHE_DATA_H -#define UNIFIEDCACHE_CACHE_DATA_H +#ifndef UNIFIEDCACHE_ITASK_WAITER_H +#define UNIFIEDCACHE_ITASK_WAITER_H -#include -#include "cache_segment.h" +#include "thread/latch.h" namespace UC { -class CacheData { - uint32_t ioNumberPerSegment_; - std::vector segments_; - +class TaskWaiter : public Latch { public: - CacheData() : ioNumberPerSegment_{0} {} - Status Setup(const size_t ioSize, const size_t ioNumber); - void* At(const size_t index); + TaskWaiter(const size_t expected, const double startTp) : Latch{} + { + this->startTp = startTp; + Set(expected); + } + using Latch::Wait; + virtual bool Wait(const size_t timeoutMs) noexcept { return WaitFor(timeoutMs); } + virtual bool Finish() noexcept { return Check(); } }; -} // namespace UC +} // namespace UC #endif diff --git a/ucm/store/detail/type/types.h b/ucm/store/detail/type/types.h new file mode 100644 index 000000000..9f96656a2 --- /dev/null +++ b/ucm/store/detail/type/types.h @@ -0,0 +1,69 @@ +/** + * MIT License + * + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * */ +#ifndef UNIFIEDCACHE_STORE_DETAIL_TYPE_TYPES_H +#define UNIFIEDCACHE_STORE_DETAIL_TYPE_TYPES_H + +#include +#include +#include + +namespace UC::Detail { + +using BlockId = std::array; /* 16-byte block hash */ +using TaskHandle = std::size_t; /* Opaque task token (0 = invalid) */ + +/** + * @brief Hasher of BlockId + */ +struct BlockIdHasher { + size_t operator()(const BlockId& blockId) const noexcept + { + std::string_view sv(reinterpret_cast(blockId.data()), blockId.size()); + return std::hash{}(sv); + } +}; + +/** + * @brief Describes one shard (slice) of a block. + */ +struct Shard { + BlockId owner; /* Parent block identifier */ + std::size_t index; /* Shard index inside the block */ + std::vector addrs; /* Device-side buffer addresses */ +}; + +/** + * @brief Batch descriptor for load or dump operations. + * + * Inherits from std::vector to store the shard list and reserves + * room for future extensions (priority, deadline, etc.). + */ +struct TaskDesc : std::vector { + using vector::vector; /* Inherit all ctors */ + std::string brief; /* Description of Task */ +}; + +} // namespace UC::Detail + +#endif diff --git a/ucm/store/device/simu/CMakeLists.txt b/ucm/store/device/simu/CMakeLists.txt deleted file mode 100644 index 1838f18cd..000000000 --- a/ucm/store/device/simu/CMakeLists.txt +++ /dev/null @@ -1,2 +0,0 @@ -add_library(storedevice STATIC simu_device.cc) -target_link_libraries(storedevice PUBLIC storeinfra) diff --git a/ucm/store/dramstore/CMakeLists.txt b/ucm/store/dramstore/CMakeLists.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/ucm/store/dramstore/__init__.py b/ucm/store/dramstore/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/ucm/store/dramstore/dramstore_connector.py b/ucm/store/dramstore/dramstore_connector.py deleted file mode 100644 index 24f4306bc..000000000 --- a/ucm/store/dramstore/dramstore_connector.py +++ /dev/null @@ -1,251 +0,0 @@ -# -# MIT License -# -# Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. -# - -from dataclasses import dataclass -from typing import Any, Dict, List, Optional - -import torch - -from ucm.logger import init_logger -from ucm.store.ucmstore import Task, UcmKVStoreBase - -logger = init_logger(__name__) - -SUCCESS = 0 -FAILURE = -1 - -if torch.cuda.is_available(): - device = torch.cuda -elif hasattr(torch, "musa") and torch.musa.is_available(): - device = torch.musa -elif hasattr(torch, "npu") and torch.npu.is_available(): - device = torch.npu -else: - raise RuntimeError( - "No supported accelerator found. " - "Please ensure either CUDA or NPU is available." - ) - - -@dataclass -class DramTask(Task): - task_id: str = "1" - event: Optional[Any] = None - - -class UcmDramStore(UcmKVStoreBase): - """ - Dram Connector - """ - - def __init__(self, config: Dict): - super().__init__(config) - self.dram_cache: Dict[str, any] = {} - self.max_cache_byte = int(config.get("max_cache_size", 5368709120)) - self.kv_block_size = int(config.get("kv_block_size", 262144)) - self.max_block_num = self.max_cache_byte // self.kv_block_size - if config["role"] == "scheduler": - self.cached_blocks = set() - - def cc_store(self) -> int: - """ - get the underlying implementation of Store - - Returns: - cc pointer to Store - """ - return 0 - - def create(self, block_ids: List[str]) -> List[int]: - """ - create kv cache space in storage - - Args: - block_ids (List[str]): vLLM block hash. - Returns: - success mask - """ - return [SUCCESS] * len(block_ids) - - def lookup(self, block_ids: List[str]) -> List[bool]: - """ - Get number of blocks that can be loaded from the - external KV cache. - - Args: - block_ids (List[str]): vLLM block hash. - - Returns: - hit block mask, True -> hit - """ - hit_list = [block_id in self.cached_blocks for block_id in block_ids] - return hit_list - - def prefetch(self, block_ids: List[str]) -> None: - """ - prefetch kv cache to high speed cache according to block_ids. - - Args: - block_ids (List[str]): vLLM block hash. - """ - pass - - def load( - self, block_ids: List[str], offset: List[int], dst_tensor: List[torch.Tensor] - ) -> Task: - """ - load kv cache to device. - - Args: - block_ids (List[str]): vLLM block hash. - offset(List[int]): tp > 1 scene - dst_tensor: List[torch.Tensor]: device tensor addr. - Returns: - task(Task). - """ - task = DramTask() - stream = device.Stream() - task.event = device.Event(enable_timing=True) - with device.stream(stream): - for i, block_id in enumerate(block_ids): - key = block_id + "_" + str(offset[i]) - dst_tensor[i].copy_(self.dram_cache[key], non_blocking=True) - task.event.record(stream=stream) - logger.debug(f"load block {block_ids} finished.") - return task - - def dump( - self, block_ids: List[str], offset: List[int], src_tensor: List[torch.Tensor] - ) -> Task: - """ - dump kv cache to device. - - Args: - block_ids (List[str]): vLLM block hash. - offset(List[int]): tp > 1 scene - src_tensor: List[torch.Tensor]: device tensor addr. - Returns: - task(Task). - """ - task = DramTask() - if len(self.dram_cache) > self.max_block_num: - logger.warning( - "Dram cache usage exceeds limit! No more kv cache offload! Try to increase your initial max_cache_size." - ) - task.task_id = "-1" - return task - else: - stream = device.Stream() - task.event = device.Event(enable_timing=True) - with device.stream(stream): - for i, block_id in enumerate(block_ids): - key = block_id + "_" + str(offset[i]) - self.dram_cache[key] = src_tensor[i].to("cpu", non_blocking=True) - task.event.record(stream=stream) - logger.debug(f"dump block {block_ids} finished.") - return task - - def fetch_data( - self, - block_ids: List[str], - offset: List[int], - dst_addr: List[int], - size: List[int], - ) -> Task: - """ - load kv cache data to device. - - Args: - block_ids (List[str]): vLLM block hash. - offset(List[int]): tp > 1 scene - dst_addr: List[int]: device tensor addr ptr. - size: List[int]: device tensor size. - Returns: - task(Task). - """ - pass - - def dump_data( - self, - block_ids: List[str], - offset: List[int], - src_addr: List[int], - size: List[int], - ) -> Task: - """ - dump kv cache data from device. - - Args: - block_ids (List[str]): vLLM block hash. - offset(List[int]): tp > 1 scene - src_addr: List[int]: device tensor addr ptr. - size: List[int]: device tensor size. - Returns: - task(Task). - """ - pass - - def wait(self, task: DramTask) -> int: - """ - wait kv cache kv transfer task finished. - - Args: - task (Task): transfer engine task. - Returns: - 0 - success - others - failed. - """ - if task.task_id == "-1": - logger.warning("Dump failure with full cache usage!") - return FAILURE - try: - event = task.event - event.synchronize() - return SUCCESS - except Exception as e: - logger.error(f"Error waiting cache for block IDs: {e}") - return FAILURE - - def commit(self, block_ids: List[str], is_success: bool = True) -> None: - """ - commit kv cache, now kv cache can be reused. - - Args: - block_ids (List[str]): vLLM block hash. - is_success(bool): if False, we need release block - """ - if is_success: - self.cached_blocks.update(block_ids) - - def check(self, task: Task) -> int: - """ - check if kv transfer task finished. - - Args: - task (Task): transfer engine task. - Returns: - 0 - finished - others - in process. - """ - pass diff --git a/ucm/store/factory.py b/ucm/store/factory.py index 8b893cda3..1db735506 100644 --- a/ucm/store/factory.py +++ b/ucm/store/factory.py @@ -57,9 +57,6 @@ def create_connector(cls, connector_name: str, config: dict) -> UcmKVStoreBase: return connector_cls(config) -UcmConnectorFactory.register_connector( - "UcmDramStore", "ucm.store.dramstore.dramstore_connector", "UcmDramStore" -) UcmConnectorFactory.register_connector( "UcmNfsStore", "ucm.store.nfsstore.nfsstore_connector", "UcmNfsStore" ) diff --git a/ucm/store/factory_v1.py b/ucm/store/factory_v1.py new file mode 100644 index 000000000..6f1b7431a --- /dev/null +++ b/ucm/store/factory_v1.py @@ -0,0 +1,62 @@ +# +# MIT License +# +# Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +import importlib +from typing import Callable + +from ucm.logger import init_logger +from ucm.store.ucmstore_v1 import UcmKVStoreBaseV1 + +logger = init_logger(__name__) + + +class UcmConnectorFactoryV1: + _registry: dict[str, Callable[[], type[UcmKVStoreBaseV1]]] = {} + + @classmethod + def register_connector(cls, name: str, module_path: str, class_name: str) -> None: + """Register a connector with a lazy-loading module and class name.""" + if name in cls._registry: + raise ValueError(f"Connector '{name}' is already registered.") + + def loader() -> type[UcmKVStoreBaseV1]: + module = importlib.import_module(module_path) + return getattr(module, class_name) + + cls._registry[name] = loader + + @classmethod + def create_connector(cls, connector_name: str, config: dict) -> UcmKVStoreBaseV1: + if connector_name in cls._registry: + connector_cls = cls._registry[connector_name]() + else: + raise ValueError(f"Unsupported connector type: {connector_name}") + assert issubclass(connector_cls, UcmKVStoreBaseV1) + logger.info("Creating connector with name: %s", connector_name) + return connector_cls(config) + + +UcmConnectorFactoryV1.register_connector( + "UcmNfsStore", "ucm.store.pcstore.pcstore_connector_v1", "UcmPcStoreV1" +) diff --git a/ucm/store/infra/CMakeLists.txt b/ucm/store/infra/CMakeLists.txt deleted file mode 100644 index 6bc8dc4a4..000000000 --- a/ucm/store/infra/CMakeLists.txt +++ /dev/null @@ -1,11 +0,0 @@ -add_library(storeinfra STATIC) -target_include_directories(storeinfra PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) -file(GLOB_RECURSE UCMSTORE_COMMON_FILE_SOURCE_FILES "file/*.cc") -target_sources(storeinfra PRIVATE ${UCMSTORE_COMMON_FILE_SOURCE_FILES}) -target_link_libraries(storeinfra PUBLIC - infra_status - infra_logger - infra_template - infra_thread - infra_time -) diff --git a/ucm/store/localstore/CMakeLists.txt b/ucm/store/localstore/CMakeLists.txt deleted file mode 100644 index 77e924a52..000000000 --- a/ucm/store/localstore/CMakeLists.txt +++ /dev/null @@ -1,17 +0,0 @@ -file(GLOB_RECURSE UCMSTORE_LOCAL_CC_SOURCE_FILES "./cc/*.cc") -add_library(localstore STATIC ${UCMSTORE_LOCAL_CC_SOURCE_FILES}) -target_include_directories(localstore PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR}/cc/api - ${CMAKE_CURRENT_SOURCE_DIR}/cc/domain -) -target_link_libraries(localstore PUBLIC storeinfra storetask) - -file(GLOB_RECURSE UCMSTORE_LOCAL_CPY_SOURCE_FILES "./cpy/*.cc") -pybind11_add_module(ucmlocalstore ${UCMSTORE_LOCAL_CPY_SOURCE_FILES}) -target_link_libraries(ucmlocalstore PRIVATE localstore) - -file(RELATIVE_PATH INSTALL_REL_PATH - ${CMAKE_SOURCE_DIR} - ${CMAKE_CURRENT_SOURCE_DIR} -) -install(TARGETS ucmlocalstore LIBRARY DESTINATION ${INSTALL_REL_PATH} COMPONENT ucm) diff --git a/ucm/store/localstore/__init__.py b/ucm/store/localstore/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/ucm/store/localstore/cc/api/localstore.cc b/ucm/store/localstore/cc/api/localstore.cc deleted file mode 100644 index 15f5d8bc3..000000000 --- a/ucm/store/localstore/cc/api/localstore.cc +++ /dev/null @@ -1,96 +0,0 @@ -/** - * MIT License - * - * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * */ -#include "localstore.h" -#include "logger/logger.h" -#include "status/status.h" - -namespace UC { - -class LocalStoreWithoutBackendImpl : public LocalStore { -public: - int32_t Setup(const size_t ioSize, const size_t capacity, const int32_t deviceId) { return -1; } - int32_t Alloc(const std::string& block) override { return -1; } - bool Lookup(const std::string& block) override { return false; } - void Commit(const std::string& block, const bool success) override {} - std::list Alloc(const std::list& blocks) override - { - return std::list(); - } - std::list Lookup(const std::list& blocks) override - { - return std::list(); - } - void Commit(const std::list& blocks, const bool success) override {} - size_t Submit(Task&& task) override { return 0; } - int32_t Wait(const size_t task) override { return -1; } - int32_t Check(const size_t task, bool& finish) override { return -1; } -}; - -class LocalStoreWithBackendImpl : public LocalStore { -public: - int32_t Setup(const size_t ioSize, const size_t capacity, void* backend, const int32_t deviceId) - { - return -1; - } - int32_t Alloc(const std::string& block) override { return -1; } - bool Lookup(const std::string& block) override { return false; } - void Commit(const std::string& block, const bool success) override {} - std::list Alloc(const std::list& blocks) override - { - return std::list(); - } - std::list Lookup(const std::list& blocks) override - { - return std::list(); - } - void Commit(const std::list& blocks, const bool success) override {} - size_t Submit(Task&& task) override { return 0; } - int32_t Wait(const size_t task) override { return -1; } - int32_t Check(const size_t task, bool& finish) override { return -1; } - -private: - CCStore* backend_; -}; - -int32_t LocalStore::Setup(const Config& config) -{ - if (config.backend) { - auto impl = new (std::nothrow) LocalStoreWithBackendImpl(); - if (!impl) { - UC_ERROR("Out of memory."); - return Status::OutOfMemory().Underlying(); - } - this->impl_ = impl; - return impl->Setup(config.ioSize, config.capacity, config.backend, config.deviceId); - } - auto impl = new (std::nothrow) LocalStoreWithoutBackendImpl(); - if (!impl) { - UC_ERROR("Out of memory."); - return Status::OutOfMemory().Underlying(); - } - this->impl_ = impl; - return impl->Setup(config.ioSize, config.capacity, config.deviceId); -} - -} // namespace UC diff --git a/ucm/store/localstore/cc/api/localstore.h b/ucm/store/localstore/cc/api/localstore.h deleted file mode 100644 index edca1e0b1..000000000 --- a/ucm/store/localstore/cc/api/localstore.h +++ /dev/null @@ -1,82 +0,0 @@ -/** - * MIT License - * - * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * */ -#ifndef UNIFIEDCACHE_LOCALSTORE_H -#define UNIFIEDCACHE_LOCALSTORE_H - -#include "ucmstore.h" - -namespace UC { - -class LocalStore : public CCStore<> { -public: - struct Config { - size_t ioSize; - size_t capacity; - void* backend; - int32_t deviceId; - Config(const size_t ioSize, const size_t capacity) - : ioSize{ioSize}, capacity{capacity}, backend{nullptr}, deviceId{-1} - { - } - }; - -public: - LocalStore() : impl_{nullptr} {} - ~LocalStore() override - { - if (this->impl_) { delete this->impl_; } - } - int32_t Setup(const Config& config); - int32_t Alloc(const std::string& block) override { return this->impl_->Alloc(block); } - bool Lookup(const std::string& block) override { return this->impl_->Lookup(block); } - void Commit(const std::string& block, const bool success) override - { - this->impl_->Commit(block, success); - } - std::list Alloc(const std::list& blocks) override - { - return this->impl_->Alloc(blocks); - } - std::list Lookup(const std::list& blocks) override - { - return this->impl_->Lookup(blocks); - } - void Commit(const std::list& blocks, const bool success) override - { - this->impl_->Commit(blocks, success); - } - size_t Submit(Task&& task) override { return this->impl_->Submit(std::move(task)); } - int32_t Wait(const size_t task) override { return this->impl_->Wait(task); } - int32_t Check(const size_t task, bool& finish) override - { - return this->impl_->Check(task, finish); - } - -private: - LocalStore* impl_; -}; - -} // namespace UC - -#endif diff --git a/ucm/store/localstore/cc/domain/cache/cache_data.cc b/ucm/store/localstore/cc/domain/cache/cache_data.cc deleted file mode 100644 index d17190dd8..000000000 --- a/ucm/store/localstore/cc/domain/cache/cache_data.cc +++ /dev/null @@ -1,47 +0,0 @@ -/** - * MIT License - * - * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * */ -#include "cache_data.h" - -namespace UC { - -Status CacheData::Setup(const size_t ioSize, const size_t ioNumber) -{ - auto segmentSize = CacheSegment::TotalSize(); - this->ioNumberPerSegment_ = segmentSize / ioSize; - auto segmentNumber = (ioNumber + this->ioNumberPerSegment_ - 1) / this->ioNumberPerSegment_; - this->segments_.reserve(segmentNumber); - for (size_t i = 0; i < segmentNumber; i++) { - auto& segment = this->segments_.emplace_back(); - auto status = segment.Setup(i, ioSize); - if (status.Failure()) { return status; } - } - return Status::OK(); -} - -void* CacheData::At(const size_t index) -{ - return this->segments_[index / this->ioNumberPerSegment_].At(index % this->ioNumberPerSegment_); -} - -} // namespace UC diff --git a/ucm/store/localstore/cc/domain/cache/cache_hash.cc b/ucm/store/localstore/cc/domain/cache/cache_hash.cc deleted file mode 100644 index 6f0325f53..000000000 --- a/ucm/store/localstore/cc/domain/cache/cache_hash.cc +++ /dev/null @@ -1,295 +0,0 @@ -/** - * MIT License - * - * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * */ -#include "cache_hash.h" -#include -#include - -namespace UC { - -static constexpr uint32_t BucketNumber = 17683; -static constexpr uint32_t Magic = (('C' << 16) | ('h' << 8) | 1); - -struct Key { - uint64_t k1, k2; - size_t off; - Key() { this->Init(); } - Key(const std::string& id, const size_t off) { this->Fill(id, off); } - bool operator==(const Key& k) const { return k1 == k.k1 && k2 == k.k2 && off == k.off; } - void Init() { this->k1 = this->k2 = this->off = 0; } - void Fill(const std::string& id, const size_t off) - { - auto idPair = static_cast(static_cast(id.data())); - this->k1 = idPair[0]; - this->k2 = idPair[1]; - this->off = off; - } - uint32_t Hash() - { - static std::hash hasher{}; - return (hasher(k1) | hasher(k2) | hasher(off)) % BucketNumber; - } -}; - -struct Node { - Key key; - uint32_t prev; - uint32_t next; - std::atomic ref; - uint64_t tp; - void Init() - { - this->key.Init(); - this->prev = CacheHash::npos; - this->next = CacheHash::npos; - this->ref = 0; - } -}; - -struct Bucket { - pthread_mutex_t mutex; - uint32_t head; - uint32_t tail; - void Init() - { - pthread_mutexattr_t attr; - pthread_mutexattr_init(&attr); - pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); - pthread_mutex_init(&this->mutex, &attr); - pthread_mutexattr_destroy(&attr); - this->head = CacheHash::npos; - this->tail = CacheHash::npos; - } - void Lock() { pthread_mutex_lock(&this->mutex); } - void Unlock() { pthread_mutex_unlock(&this->mutex); } -}; - -struct Header { - uint32_t magic; - uint32_t capacity; - std::atomic tp; - uint64_t reserved[7]; - Bucket buckets[BucketNumber]; - Node nodes[0]; -}; - -inline auto HeaderPtr(void* addr) { return (Header*)addr; } - -inline void InsertNode2Bucket(Header* header, Bucket* bucket, const uint32_t index) -{ - auto slot = header->nodes + index; - slot->prev = CacheHash::npos; - slot->next = bucket->head; - if (bucket->head != CacheHash::npos) { - auto next = header->nodes + bucket->head; - next->prev = index; - } - bucket->head = index; - if (bucket->tail == CacheHash::npos) { bucket->tail = index; } -} - -inline void MoveNode2BucketHead(Header* header, Bucket* bucket, const uint32_t index) -{ - if (bucket->head == index) { return; } - auto slot = header->nodes + index; - if (bucket->tail == index) { - auto tail = header->nodes + slot->prev; - tail->next = CacheHash::npos; - bucket->tail = slot->prev; - slot->prev = CacheHash::npos; - } else { - auto prev = header->nodes + slot->prev; - auto next = header->nodes + slot->next; - prev->next = slot->next; - next->prev = slot->prev; - } - auto head = header->nodes + bucket->head; - head->prev = index; - slot->next = bucket->head; - bucket->head = index; -} - -inline void RemoveNodeFromBucket(Header* header, Bucket* bucket, const uint32_t index) -{ - auto slot = header->nodes + index; - if (bucket->head == index && bucket->tail == index) { - bucket->head = bucket->tail = CacheHash::npos; - return; - } - if (bucket->head != index && bucket->tail != index) { - auto prev = header->nodes + slot->prev; - auto next = header->nodes + slot->next; - prev->next = slot->next; - next->prev = slot->prev; - return; - } - if (bucket->head == index) { - bucket->head = slot->next; - auto head = header->nodes + slot->next; - head->prev = CacheHash::npos; - return; - } - if (bucket->tail == index) { - bucket->tail = slot->prev; - auto tail = header->nodes + slot->prev; - tail->next = CacheHash::npos; - return; - } -} - -size_t CacheHash::MemorySize() const noexcept -{ - return sizeof(Header) * sizeof(Node) * this->capacity_; -} - -void CacheHash::Setup(void* addr) noexcept -{ - this->addr_ = addr; - auto header = HeaderPtr(this->addr_); - if (header->magic == Magic) { return; } - header->capacity = this->capacity_; - header->tp.store(0, std::memory_order_relaxed); - auto reservedSize = sizeof(header->reserved) / sizeof(*header->reserved); - std::fill_n(header->reserved, reservedSize, 0); - for (uint32_t i = 0; i < BucketNumber; i++) { header->buckets[i].Init(); } - for (uint32_t i = 0; i < header->capacity; i++) { header->nodes[i].Init(); } - header->magic = Magic; - return; -} - -void CacheHash::Insert(const std::string& id, const size_t offset, const uint32_t index) noexcept -{ - auto header = HeaderPtr(this->addr_); - auto slot = header->nodes + index; - slot->key.Fill(id, offset); - auto bucket = header->buckets + slot->key.Hash(); - bucket->Lock(); - slot->ref.store(1, std::memory_order_relaxed); - slot->tp = header->tp.fetch_add(1); - InsertNode2Bucket(header, bucket, index); - bucket->Unlock(); -} - -uint32_t CacheHash::Find(const std::string& id, const size_t offset) noexcept -{ - Key key{id, offset}; - auto header = HeaderPtr(this->addr_); - auto bucket = header->buckets + key.Hash(); - bucket->Lock(); - auto pos = bucket->head; - while (pos != npos) { - auto slot = header->nodes + pos; - if (slot->key == key) { - slot->ref++; - slot->tp = header->tp.fetch_add(1); - MoveNode2BucketHead(header, bucket, pos); - break; - } - pos = slot->next; - } - bucket->Unlock(); - return pos; -} - -void CacheHash::PutRef(const uint32_t index) noexcept -{ - auto header = HeaderPtr(this->addr_); - if (index >= header->capacity) { return; } - auto slot = header->nodes + index; - auto ref = slot->ref.load(std::memory_order_acquire); - while (ref > 0) { - auto desired = ref - 1; - if (slot->ref.compare_exchange_weak(ref, desired, std::memory_order_acq_rel)) { break; } - ref = slot->ref.load(std::memory_order_acquire); - } -} - -void CacheHash::PutRef(const std::string& id, const size_t offset) noexcept -{ - Key key{id, offset}; - auto header = HeaderPtr(this->addr_); - auto bucket = header->buckets + key.Hash(); - bucket->Lock(); - auto pos = bucket->head; - while (pos != npos) { - auto slot = header->nodes + pos; - if (slot->key == key) { - this->PutRef(pos); - break; - } - pos = slot->next; - } - bucket->Unlock(); -} - -void CacheHash::Remove(const std::string& id, const size_t offset) noexcept -{ - Key key{id, offset}; - auto header = HeaderPtr(this->addr_); - auto bucket = header->buckets + key.Hash(); - bucket->Lock(); - auto pos = bucket->head; - while (pos != npos) { - auto slot = header->nodes + pos; - if (slot->key == key) { - RemoveNodeFromBucket(header, bucket, pos); - break; - } - pos = slot->next; - } - bucket->Unlock(); -} - -uint32_t CacheHash::Evict() noexcept -{ - auto header = HeaderPtr(this->addr_); - auto iBucket = npos; - auto pos = npos; - auto tp = (uint64_t)(-1); - for (uint32_t i = 0; i < BucketNumber; i++) { - auto bucket = header->buckets + i; - bucket->Lock(); - if (bucket->tail != npos) { - auto slot = header->nodes + bucket->tail; - if (slot->ref == 0 && tp > slot->tp) { - iBucket = i; - pos = bucket->tail; - tp = slot->tp; - } - } - bucket->Unlock(); - } - if (iBucket == npos) { return npos; } - auto bucket = header->buckets + iBucket; - bucket->Lock(); - auto slot = header->nodes + pos; - if (bucket->tail != pos || slot->ref != 0) { - pos = npos; - } else { - RemoveNodeFromBucket(header, bucket, pos); - } - bucket->Unlock(); - return pos; -} - -} // namespace UC diff --git a/ucm/store/localstore/cc/domain/cache/cache_index.cc b/ucm/store/localstore/cc/domain/cache/cache_index.cc deleted file mode 100644 index 4682dd096..000000000 --- a/ucm/store/localstore/cc/domain/cache/cache_index.cc +++ /dev/null @@ -1,105 +0,0 @@ -/** - * MIT License - * - * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * */ -#include "cache_index.h" -#include -#include - -namespace UC { - -struct Node { - uint32_t idx; - uint32_t next; -}; -struct Pointer { - uint32_t slot; - uint32_t ver; -}; -struct Header { - uint32_t magic; - uint32_t capacity; - alignas(64) std::atomic pointer; - uint64_t padding[7]; - Node nodes[0]; -}; -static_assert(sizeof(Pointer) == 8, "Pointer must be 64-bit"); -static_assert(sizeof(Header) == 128, "Header must be 128-Byte"); - -static constexpr uint32_t Magic = (('C' << 16) | ('i' << 8) | 1); -inline auto HeaderPtr(void* addr) { return (Header*)addr; } - -size_t CacheIndex::MemorySize() const noexcept -{ - return sizeof(Header) + sizeof(Node) * (this->capacity_ + 1); -} - -void CacheIndex::Setup(void* addr) noexcept -{ - this->addr_ = addr; - auto header = HeaderPtr(this->addr_); - if (header->magic == Magic) { return; } - header->capacity = this->capacity_; - header->pointer.store({1, 0}); - auto paddingSize = sizeof(header->padding) / sizeof(*header->padding); - std::fill_n(header->padding, paddingSize, 0); - for (uint32_t slot = 1; slot <= header->capacity; slot++) { - header->nodes[slot].idx = slot - 1; - header->nodes[slot].next = slot + 1; - } - header->nodes[header->capacity].next = 0; - header->magic = Magic; - return; -} - -uint32_t CacheIndex::Acquire() noexcept -{ - auto header = HeaderPtr(this->addr_); - for (;;) { - auto ptr = header->pointer.load(std::memory_order_acquire); - if (ptr.slot == 0) { return npos; } - auto next = header->nodes[ptr.slot].next; - Pointer desired{next, ptr.ver + 1}; - if (header->pointer.compare_exchange_weak(ptr, desired, std::memory_order_release, - std::memory_order_relaxed)) { - return header->nodes[ptr.slot].idx; - } - } -} - -void CacheIndex::Release(const uint32_t idx) noexcept -{ - auto header = HeaderPtr(this->addr_); - if (idx >= header->capacity) { return; } - auto slot = idx + 1; - for (;;) { - auto ptr = header->pointer.load(std::memory_order_acquire); - header->nodes[slot].next = ptr.slot; - Pointer desired{slot, ptr.ver + 1}; - if (header->pointer.compare_exchange_weak(ptr, desired, std::memory_order_release, - std::memory_order_relaxed)) { - return; - } - } -} - -} // namespace UC diff --git a/ucm/store/localstore/cc/domain/cache/cache_index.h b/ucm/store/localstore/cc/domain/cache/cache_index.h deleted file mode 100644 index 45d0e826d..000000000 --- a/ucm/store/localstore/cc/domain/cache/cache_index.h +++ /dev/null @@ -1,49 +0,0 @@ -/** - * MIT License - * - * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * */ -#ifndef UNIFIEDCACHE_CACHE_INDEX_H -#define UNIFIEDCACHE_CACHE_INDEX_H - -#include -#include -#include - -namespace UC { - -class CacheIndex { - uint32_t capacity_; - void* addr_; - -public: - static constexpr uint32_t npos = std::numeric_limits::max(); - CacheIndex() noexcept : capacity_{0}, addr_{nullptr} {} - void Setup(const uint32_t capacity) noexcept { this->capacity_ = capacity; } - size_t MemorySize() const noexcept; - void Setup(void* addr) noexcept; - uint32_t Acquire() noexcept; - void Release(const uint32_t idx) noexcept; -}; - -} // namespace UC - -#endif diff --git a/ucm/store/localstore/cc/domain/cache/cache_instance.cc b/ucm/store/localstore/cc/domain/cache/cache_instance.cc deleted file mode 100644 index 3d7543a8c..000000000 --- a/ucm/store/localstore/cc/domain/cache/cache_instance.cc +++ /dev/null @@ -1,52 +0,0 @@ -/** - * MIT License - * - * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * */ -#include "cache_instance.h" - -namespace UC { - -Status CacheInstance::Setup(const size_t ioSize, const uint32_t capacity) noexcept -{ - auto status = this->meta_.Setup(capacity); - if (status.Failure()) { return status; } - return this->data_.Setup(ioSize, capacity); -} - -void* CacheInstance::Alloc(const std::string& id, const size_t offset) noexcept -{ - auto index = this->meta_.Alloc(id, offset); - return index != this->meta_.npos ? this->data_.At(index) : nullptr; -} - -void* CacheInstance::Find(const std::string& id, const size_t offset) noexcept -{ - auto index = this->meta_.Find(id, offset); - return index != this->meta_.npos ? this->data_.At(index) : nullptr; -} - -void CacheInstance::PutRef(const std::string& id, const size_t offset) noexcept -{ - this->meta_.PutRef(id, offset); -} - -} // namespace UC diff --git a/ucm/store/localstore/cc/domain/cache/cache_instance.h b/ucm/store/localstore/cc/domain/cache/cache_instance.h deleted file mode 100644 index 2e7839b69..000000000 --- a/ucm/store/localstore/cc/domain/cache/cache_instance.h +++ /dev/null @@ -1,45 +0,0 @@ -/** - * MIT License - * - * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * */ -#ifndef UNIFIEDCACHE_CACHE_INSTANCE_H -#define UNIFIEDCACHE_CACHE_INSTANCE_H - -#include "cache_data.h" -#include "cache_meta.h" - -namespace UC { - -class CacheInstance { - CacheMeta meta_; - CacheData data_; - -public: - Status Setup(const size_t ioSize, const uint32_t capacity) noexcept; - void* Alloc(const std::string& id, const size_t offset) noexcept; - void* Find(const std::string& id, const size_t offset) noexcept; - void PutRef(const std::string& id, const size_t offset) noexcept; -}; - -} // namespace UC - -#endif diff --git a/ucm/store/localstore/cc/domain/cache/cache_layout.cc b/ucm/store/localstore/cc/domain/cache/cache_layout.cc deleted file mode 100644 index 236dc1402..000000000 --- a/ucm/store/localstore/cc/domain/cache/cache_layout.cc +++ /dev/null @@ -1,38 +0,0 @@ -/** - * MIT License - * - * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * */ -#include "cache_layout.h" -#include - -namespace UC { - -static const std::string ShmFilePrefix = "/ucmlocal"; - -std::string CacheLayout::MetaShmFile() { return fmt::format("{}.meta", ShmFilePrefix); } - -std::string CacheLayout::DataShmFile(const size_t id) -{ - return fmt::format("{}.dat{:06}", ShmFilePrefix, id); -} - -} // namespace UC diff --git a/ucm/store/localstore/cc/domain/cache/cache_meta.cc b/ucm/store/localstore/cc/domain/cache/cache_meta.cc deleted file mode 100644 index 057f8a31d..000000000 --- a/ucm/store/localstore/cc/domain/cache/cache_meta.cc +++ /dev/null @@ -1,147 +0,0 @@ -/** - * MIT License - * - * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * */ -#include "cache_meta.h" -#include -#include -#include -#include "cache_layout.h" -#include "file/file.h" -#include "logger/logger.h" - -namespace UC { - -static constexpr uint32_t Magic = (('C' << 16) | ('m' << 8) | 1); - -struct Header { - std::atomic magic; - uint32_t padding; - uint64_t reserved[7]; -}; - -CacheMeta::~CacheMeta() -{ - if (this->addr_) { - File::MUnmap(this->addr_, this->size_); - File::ShmUnlink(CacheLayout::MetaShmFile()); - } - this->addr_ = nullptr; -} - -Status CacheMeta::Setup(const Index capacity) noexcept -{ - this->index_.Setup(capacity); - this->hash_.Setup(capacity); - this->size_ = this->index_.MemorySize() + this->hash_.MemorySize(); - auto file = File::Make(CacheLayout::MetaShmFile()); - if (!file) { return Status::OutOfMemory(); } - auto openFlags = IFile::OpenFlag::CREATE | IFile::OpenFlag::EXCL | IFile::OpenFlag::READ_WRITE; - auto status = file->ShmOpen(openFlags); - if (status.Success()) { return this->InitShmMeta(file.get()); } - if (status == Status::DuplicateKey()) { return this->LoadShmMeta(file.get()); } - return status; -} - -CacheMeta::Index CacheMeta::Alloc(const std::string& id, const size_t offset) noexcept -{ - auto index = this->index_.Acquire(); - if (index != CacheIndex::npos) { - this->hash_.Insert(id, offset, index); - return index; - } - auto evict = this->hash_.Evict(); - if (evict != CacheHash::npos) { this->index_.Release(evict); } - return npos; -} - -CacheMeta::Index CacheMeta::Find(const std::string& id, const size_t offset) noexcept -{ - auto idx = this->hash_.Find(id, offset); - return idx != CacheHash::npos ? idx : npos; -} - -void CacheMeta::PutRef(const uint32_t index) noexcept { this->hash_.PutRef(index); } - -void CacheMeta::PutRef(const std::string& id, const size_t offset) noexcept -{ - this->hash_.PutRef(id, offset); -} - -Status CacheMeta::InitShmMeta(IFile* shmMetaFile) -{ - auto status = shmMetaFile->Truncate(this->size_); - if (status.Failure()) { return status; } - status = shmMetaFile->MMap(this->addr_, this->size_, true, true, true); - if (status.Failure()) { return status; } - auto header = (Header*)this->addr_; - auto indexBase = (void*)(((uint8_t*)this->addr_) + sizeof(Header)); - auto hashBase = (void*)(((uint8_t*)indexBase) + this->index_.MemorySize()); - this->index_.Setup(indexBase); - this->hash_.Setup(hashBase); - header->padding = 0; - auto reservedSize = sizeof(header->reserved) / sizeof(*header->reserved); - std::fill_n(header->reserved, reservedSize, 0); - header->magic = Magic; - return Status::OK(); -} - -Status CacheMeta::LoadShmMeta(IFile* shmMetaFile) -{ - auto openFlags = IFile::OpenFlag::READ_WRITE; - auto status = shmMetaFile->ShmOpen(openFlags); - if (status.Failure()) { return status; } - constexpr auto retryInterval = std::chrono::milliseconds(100); - constexpr auto maxTryTime = 100; - auto tryTime = 0; - IFile::FileStat stat; - do { - if (tryTime > maxTryTime) { - UC_ERROR("Shm file({}) not ready.", shmMetaFile->Path()); - return Status::Retry(); - } - std::this_thread::sleep_for(retryInterval); - status = shmMetaFile->Stat(stat); - if (status.Failure()) { return status; } - tryTime++; - } while (static_cast(stat.st_size) != this->size_); - status = shmMetaFile->MMap(this->addr_, this->size_, true, true, true); - if (status.Failure()) { return status; } - auto header = (Header*)this->addr_; - tryTime = 0; - do { - if (header->magic == Magic) { break; } - if (tryTime > maxTryTime) { - UC_ERROR("Shm file({}) not ready.", shmMetaFile->Path()); - return Status::Retry(); - } - std::this_thread::sleep_for(retryInterval); - tryTime++; - } while (true); - auto indexBase = (void*)(((uint8_t*)this->addr_) + sizeof(Header)); - auto hashBase = (void*)(((uint8_t*)indexBase) + this->index_.MemorySize()); - this->index_.Setup(indexBase); - this->hash_.Setup(hashBase); - return Status::OK(); -} - -} // namespace UC diff --git a/ucm/store/localstore/cc/domain/cache/cache_segment.cc b/ucm/store/localstore/cc/domain/cache/cache_segment.cc deleted file mode 100644 index abf7eef52..000000000 --- a/ucm/store/localstore/cc/domain/cache/cache_segment.cc +++ /dev/null @@ -1,60 +0,0 @@ -/** - * MIT License - * - * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * */ -#include "cache_segment.h" -#include "cache_layout.h" -#include "file/file.h" - -namespace UC { - -CacheSegment::~CacheSegment() -{ - if (this->base_) { - File::MUnmap(this->base_, TotalSize()); - File::ShmUnlink(CacheLayout::DataShmFile(this->id_)); - } - this->base_ = nullptr; -} - -Status CacheSegment::Setup(const size_t id, const size_t ioSize) -{ - auto path = CacheLayout::DataShmFile(this->id_); - auto file = File::Make(path); - if (!file) { return Status::OutOfMemory(); } - auto status = Status::OK(); - auto openFlags = IFile::OpenFlag::CREATE | IFile::OpenFlag::READ_WRITE; - if ((status = file->ShmOpen(openFlags)).Failure()) { return status; } - auto size = TotalSize(); - if ((status = file->Truncate(size)).Failure()) { return status; } - if ((status = file->MMap(this->base_, size, true, true, true)).Failure()) { return status; } - return status; -} - -void* CacheSegment::At(const size_t idx) const -{ - auto addr = (uint8_t*)this->base_; - auto offset = this->ioSize_ * idx; - return addr + offset; -} - -} // namespace UC diff --git a/ucm/store/localstore/cc/domain/cache/cache_segment.h b/ucm/store/localstore/cc/domain/cache/cache_segment.h deleted file mode 100644 index 1cefd5f91..000000000 --- a/ucm/store/localstore/cc/domain/cache/cache_segment.h +++ /dev/null @@ -1,47 +0,0 @@ -/** - * MIT License - * - * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * */ -#ifndef UNIFIEDCACHE_CACHE_SEGMENT_H -#define UNIFIEDCACHE_CACHE_SEGMENT_H - -#include -#include "status/status.h" - -namespace UC { - -class CacheSegment { - size_t id_; - size_t ioSize_; - void* base_; - -public: - static size_t TotalSize() { return 1ULL << 30; } - CacheSegment() : id_{0}, ioSize_{0}, base_{nullptr} {} - ~CacheSegment(); - Status Setup(const size_t id, const size_t ioSize); - void* At(const size_t idx) const; -}; - -} // namespace UC - -#endif diff --git a/ucm/store/localstore/cpy/localstore.py.cc b/ucm/store/localstore/cpy/localstore.py.cc deleted file mode 100644 index c067df231..000000000 --- a/ucm/store/localstore/cpy/localstore.py.cc +++ /dev/null @@ -1,120 +0,0 @@ -/** - * MIT License - * - * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * */ -#include "localstore.h" -#include - -namespace py = pybind11; - -namespace UC { - -class LocalStorePy : public LocalStore { -public: - void* CCStoreImpl() { return this; } - py::list AllocBatch(const py::list& blocks) - { - py::list results; - for (auto& block : blocks) { results.append(this->Alloc(block.cast())); } - return results; - } - py::list LookupBatch(const py::list& blocks) - { - py::list founds; - for (auto& block : blocks) { founds.append(this->Lookup(block.cast())); } - return founds; - } - void CommitBatch(const py::list& blocks, const bool success) - { - for (auto& block : blocks) { this->Commit(block.cast(), success); } - } - py::tuple CheckPy(const size_t task) - { - auto finish = false; - auto ret = this->Check(task, finish); - return py::make_tuple(ret, finish); - } - size_t Load(const py::list& blockIds, const py::list& offsets, const py::list& addresses, - const py::list& lengths) - { - return this->SubmitPy(blockIds, offsets, addresses, lengths, Task::Type::LOAD, - Task::Location::DEVICE, "LOCAL::S2D"); - } - size_t Dump(const py::list& blockIds, const py::list& offsets, const py::list& addresses, - const py::list& lengths) - { - return this->SubmitPy(blockIds, offsets, addresses, lengths, Task::Type::DUMP, - Task::Location::DEVICE, "LOCAL::D2S"); - } - -private: - size_t SubmitPy(const py::list& blockIds, const py::list& offsets, const py::list& addresses, - const py::list& lengths, Task::Type&& type, Task::Location&& location, - std::string&& brief) - { - Task task{std::move(type), std::move(location), std::move(brief)}; - auto blockId = blockIds.begin(); - auto offset = offsets.begin(); - auto address = addresses.begin(); - auto length = lengths.begin(); - while ((blockId != blockIds.end()) && (offset != offsets.end()) && - (address != addresses.end()) && (length != lengths.end())) { - task.Append(blockId->cast(), offset->cast(), - address->cast(), length->cast()); - blockId++; - offset++; - address++; - length++; - } - return this->Submit(std::move(task)); - } -}; - -} // namespace UC - -PYBIND11_MODULE(ucmlocalstore, module) -{ - module.attr("project") = UCM_PROJECT_NAME; - module.attr("version") = UCM_PROJECT_VERSION; - module.attr("commit_id") = UCM_COMMIT_ID; - module.attr("build_type") = UCM_BUILD_TYPE; - auto store = py::class_(module, "LocalStore"); - auto config = py::class_(store, "Config"); - config.def(py::init(), py::arg("ioSize"), py::arg("capacity")); - config.def_readwrite("ioSize", &UC::LocalStorePy::Config::ioSize); - config.def_readwrite("capacity", &UC::LocalStorePy::Config::capacity); - config.def_readwrite("backend", &UC::LocalStorePy::Config::backend); - config.def_readwrite("deviceId", &UC::LocalStorePy::Config::deviceId); - store.def("CCStoreImpl", &UC::LocalStorePy::CCStoreImpl); - store.def("Setup", &UC::LocalStorePy::Setup); - store.def("Alloc", py::overload_cast(&UC::LocalStorePy::Alloc)); - store.def("AllocBatch", &UC::LocalStorePy::AllocBatch); - store.def("Lookup", py::overload_cast(&UC::LocalStorePy::Lookup)); - store.def("LookupBatch", &UC::LocalStorePy::LookupBatch); - store.def("Load", &UC::LocalStorePy::Load); - store.def("Dump", &UC::LocalStorePy::Dump); - store.def("Wait", &UC::LocalStorePy::Wait); - store.def("Check", &UC::LocalStorePy::Check); - store.def("Commit", - py::overload_cast(&UC::LocalStorePy::Commit)); - store.def("CommitBatch", &UC::LocalStorePy::CommitBatch); -} diff --git a/ucm/store/nfsstore/CMakeLists.txt b/ucm/store/nfsstore/CMakeLists.txt index 7856c89e0..b48376278 100644 --- a/ucm/store/nfsstore/CMakeLists.txt +++ b/ucm/store/nfsstore/CMakeLists.txt @@ -1,10 +1,12 @@ +add_subdirectory(device) file(GLOB_RECURSE UCMSTORE_NFS_CC_SOURCE_FILES "./cc/*.cc") add_library(nfsstore STATIC ${UCMSTORE_NFS_CC_SOURCE_FILES}) target_include_directories(nfsstore PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/cc/api ${CMAKE_CURRENT_SOURCE_DIR}/cc/domain ) -target_link_libraries(nfsstore PUBLIC storeinfra storedevice storetask) +target_link_libraries(nfsstore PUBLIC storeintf storedevice infra_logger) file(GLOB_RECURSE UCMSTORE_NFS_CPY_SOURCE_FILES "./cpy/*.cc") pybind11_add_module(ucmnfsstore ${UCMSTORE_NFS_CPY_SOURCE_FILES}) diff --git a/ucm/store/infra/file/file.cc b/ucm/store/nfsstore/cc/domain/file/file.cc similarity index 100% rename from ucm/store/infra/file/file.cc rename to ucm/store/nfsstore/cc/domain/file/file.cc diff --git a/ucm/store/infra/file/file.h b/ucm/store/nfsstore/cc/domain/file/file.h similarity index 100% rename from ucm/store/infra/file/file.h rename to ucm/store/nfsstore/cc/domain/file/file.h diff --git a/ucm/store/infra/file/ifile.h b/ucm/store/nfsstore/cc/domain/file/ifile.h similarity index 100% rename from ucm/store/infra/file/ifile.h rename to ucm/store/nfsstore/cc/domain/file/ifile.h diff --git a/ucm/store/infra/file/posix_file.cc b/ucm/store/nfsstore/cc/domain/file/posix_file.cc similarity index 100% rename from ucm/store/infra/file/posix_file.cc rename to ucm/store/nfsstore/cc/domain/file/posix_file.cc diff --git a/ucm/store/infra/file/posix_file.h b/ucm/store/nfsstore/cc/domain/file/posix_file.h similarity index 100% rename from ucm/store/infra/file/posix_file.h rename to ucm/store/nfsstore/cc/domain/file/posix_file.h diff --git a/ucm/store/nfsstore/cc/domain/trans/posix_queue.h b/ucm/store/nfsstore/cc/domain/trans/posix_queue.h index 0e2dc17f8..9083e23e5 100644 --- a/ucm/store/nfsstore/cc/domain/trans/posix_queue.h +++ b/ucm/store/nfsstore/cc/domain/trans/posix_queue.h @@ -27,8 +27,8 @@ #include "device/idevice.h" #include "space/space_layout.h" #include "status/status.h" -#include "task_queue.h" -#include "task_set.h" +#include "task/task_queue.h" +#include "task/task_set.h" #include "thread/thread_pool.h" namespace UC { @@ -45,7 +45,8 @@ class PosixQueue : public TaskQueue { public: Status Setup(const int32_t deviceId, const size_t bufferSize, const size_t bufferNumber, - TaskSet* failureSet, const SpaceLayout* layout, const size_t timeoutMs, bool useDirect = false); + TaskSet* failureSet, const SpaceLayout* layout, const size_t timeoutMs, + bool useDirect = false); void Push(std::list& shards) noexcept override; private: @@ -59,6 +60,6 @@ class PosixQueue : public TaskQueue { Status S2H(Task::Shard& shard); }; -} // namespace UC +} // namespace UC #endif diff --git a/ucm/store/nfsstore/cc/domain/trans/trans_manager.h b/ucm/store/nfsstore/cc/domain/trans/trans_manager.h index 06748042e..5675c09b1 100644 --- a/ucm/store/nfsstore/cc/domain/trans/trans_manager.h +++ b/ucm/store/nfsstore/cc/domain/trans/trans_manager.h @@ -25,21 +25,22 @@ #define UNIFIEDCACHE_TRANS_MANAGER_H #include "posix_queue.h" -#include "task_manager.h" +#include "task/task_manager.h" namespace UC { class TransManager : public TaskManager { public: Status Setup(const int32_t deviceId, const size_t streamNumber, const size_t ioSize, - const size_t bufferNumber, const SpaceLayout* layout, const size_t timeoutMs, bool useDirect = false) + const size_t bufferNumber, const SpaceLayout* layout, const size_t timeoutMs, + bool useDirect = false) { this->timeoutMs_ = timeoutMs; auto status = Status::OK(); for (size_t i = 0; i < streamNumber; i++) { auto q = std::make_shared(); - status = - q->Setup(deviceId, ioSize, bufferNumber, &this->failureSet_, layout, timeoutMs, useDirect); + status = q->Setup(deviceId, ioSize, bufferNumber, &this->failureSet_, layout, timeoutMs, + useDirect); if (status.Failure()) { break; } this->queues_.emplace_back(std::move(q)); } @@ -47,6 +48,6 @@ class TransManager : public TaskManager { } }; -} // namespace UC +} // namespace UC #endif diff --git a/ucm/store/device/CMakeLists.txt b/ucm/store/nfsstore/device/CMakeLists.txt similarity index 100% rename from ucm/store/device/CMakeLists.txt rename to ucm/store/nfsstore/device/CMakeLists.txt diff --git a/ucm/store/device/ascend/CMakeLists.txt b/ucm/store/nfsstore/device/ascend/CMakeLists.txt similarity index 83% rename from ucm/store/device/ascend/CMakeLists.txt rename to ucm/store/nfsstore/device/ascend/CMakeLists.txt index c5e40fc6a..c8ae7de2c 100644 --- a/ucm/store/device/ascend/CMakeLists.txt +++ b/ucm/store/nfsstore/device/ascend/CMakeLists.txt @@ -6,4 +6,4 @@ set_target_properties(Ascend::ascendcl PROPERTIES ) add_library(storedevice STATIC ascend_device.cc) -target_link_libraries(storedevice PUBLIC storeinfra Ascend::ascendcl) +target_link_libraries(storedevice PUBLIC infra_status Ascend::ascendcl) diff --git a/ucm/store/device/ascend/ascend_device.cc b/ucm/store/nfsstore/device/ascend/ascend_device.cc similarity index 100% rename from ucm/store/device/ascend/ascend_device.cc rename to ucm/store/nfsstore/device/ascend/ascend_device.cc diff --git a/ucm/store/device/cuda/CMakeLists.txt b/ucm/store/nfsstore/device/cuda/CMakeLists.txt similarity index 86% rename from ucm/store/device/cuda/CMakeLists.txt rename to ucm/store/nfsstore/device/cuda/CMakeLists.txt index fa0db292d..3969b8e64 100644 --- a/ucm/store/device/cuda/CMakeLists.txt +++ b/ucm/store/nfsstore/device/cuda/CMakeLists.txt @@ -3,7 +3,7 @@ set(CMAKE_CUDA_COMPILER ${CUDA_ROOT}/bin/nvcc) set(CMAKE_CUDA_ARCHITECTURES 75 80 86 89 90) enable_language(CUDA) add_library(storedevice STATIC cuda_device.cu) -target_link_libraries(storedevice PUBLIC storeinfra) +target_link_libraries(storedevice PUBLIC infra_status) target_compile_options(storedevice PRIVATE --diag-suppress=128 --diag-suppress=2417 --diag-suppress=2597 -Wall -fPIC diff --git a/ucm/store/device/cuda/cuda_device.cu b/ucm/store/nfsstore/device/cuda/cuda_device.cu similarity index 100% rename from ucm/store/device/cuda/cuda_device.cu rename to ucm/store/nfsstore/device/cuda/cuda_device.cu diff --git a/ucm/store/device/ibuffered_device.h b/ucm/store/nfsstore/device/ibuffered_device.h similarity index 100% rename from ucm/store/device/ibuffered_device.h rename to ucm/store/nfsstore/device/ibuffered_device.h diff --git a/ucm/store/device/idevice.h b/ucm/store/nfsstore/device/idevice.h similarity index 100% rename from ucm/store/device/idevice.h rename to ucm/store/nfsstore/device/idevice.h diff --git a/ucm/store/device/maca/CMakeLists.txt b/ucm/store/nfsstore/device/maca/CMakeLists.txt similarity index 90% rename from ucm/store/device/maca/CMakeLists.txt rename to ucm/store/nfsstore/device/maca/CMakeLists.txt index 69066670a..17589a826 100644 --- a/ucm/store/device/maca/CMakeLists.txt +++ b/ucm/store/nfsstore/device/maca/CMakeLists.txt @@ -16,5 +16,5 @@ target_include_directories(WCUDA::cudart INTERFACE /opt/maca/include/mcr ) -target_link_libraries(storedevice PUBLIC storeinfra WCUDA::cudart) +target_link_libraries(storedevice PUBLIC infra_status WCUDA::cudart) target_compile_options(storedevice PRIVATE -Wall -fPIC -std=c++17) diff --git a/ucm/store/device/maca/maca_device.cu b/ucm/store/nfsstore/device/maca/maca_device.cu similarity index 100% rename from ucm/store/device/maca/maca_device.cu rename to ucm/store/nfsstore/device/maca/maca_device.cu diff --git a/ucm/store/device/musa/CMakeLists.txt b/ucm/store/nfsstore/device/musa/CMakeLists.txt similarity index 82% rename from ucm/store/device/musa/CMakeLists.txt rename to ucm/store/nfsstore/device/musa/CMakeLists.txt index 2e1ff3a75..0d4ba5b51 100644 --- a/ucm/store/device/musa/CMakeLists.txt +++ b/ucm/store/nfsstore/device/musa/CMakeLists.txt @@ -6,4 +6,4 @@ set_target_properties(Musa::musart PROPERTIES ) add_library(storedevice STATIC musa_device.cc) -target_link_libraries(storedevice PUBLIC storeinfra Musa::musart) +target_link_libraries(storedevice PUBLIC infra_status Musa::musart) diff --git a/ucm/store/device/musa/musa_device.cc b/ucm/store/nfsstore/device/musa/musa_device.cc similarity index 100% rename from ucm/store/device/musa/musa_device.cc rename to ucm/store/nfsstore/device/musa/musa_device.cc diff --git a/ucm/store/device/musa/musa_device.mu b/ucm/store/nfsstore/device/musa/musa_device.mu similarity index 100% rename from ucm/store/device/musa/musa_device.mu rename to ucm/store/nfsstore/device/musa/musa_device.mu diff --git a/ucm/store/nfsstore/device/simu/CMakeLists.txt b/ucm/store/nfsstore/device/simu/CMakeLists.txt new file mode 100644 index 000000000..82115f3a8 --- /dev/null +++ b/ucm/store/nfsstore/device/simu/CMakeLists.txt @@ -0,0 +1,2 @@ +add_library(storedevice STATIC simu_device.cc) +target_link_libraries(storedevice PUBLIC infra_status) diff --git a/ucm/store/device/simu/simu_device.cc b/ucm/store/nfsstore/device/simu/simu_device.cc similarity index 99% rename from ucm/store/device/simu/simu_device.cc rename to ucm/store/nfsstore/device/simu/simu_device.cc index a26b71744..c16c62d05 100644 --- a/ucm/store/device/simu/simu_device.cc +++ b/ucm/store/nfsstore/device/simu/simu_device.cc @@ -79,7 +79,8 @@ class SimuDevice : public IBufferedDevice { } Status Synchronized() override { - Latch waiter{1}; + Latch waiter; + waiter.Up(); this->backend_.Push([&] { waiter.Done(nullptr); }); waiter.Wait(); return Status::OK(); diff --git a/ucm/store/pcstore/CMakeLists.txt b/ucm/store/pcstore/CMakeLists.txt index fe1c98d86..e0a5b40b3 100644 --- a/ucm/store/pcstore/CMakeLists.txt +++ b/ucm/store/pcstore/CMakeLists.txt @@ -4,7 +4,7 @@ target_include_directories(pcstore PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/cc/api ${CMAKE_CURRENT_SOURCE_DIR}/cc/domain ) -target_link_libraries(pcstore PUBLIC trans storeinfra) +target_link_libraries(pcstore PUBLIC storeintf trans infra_logger) file(GLOB_RECURSE UCMSTORE_PC_CPY_SOURCE_FILES "./cpy/*.cc") pybind11_add_module(ucmpcstore ${UCMSTORE_PC_CPY_SOURCE_FILES}) diff --git a/ucm/store/pcstore/cc/api/pcstore.cc b/ucm/store/pcstore/cc/api/pcstore.cc index 81b954812..691354865 100644 --- a/ucm/store/pcstore/cc/api/pcstore.cc +++ b/ucm/store/pcstore/cc/api/pcstore.cc @@ -37,11 +37,15 @@ class PcStoreImpl : public PcStore { auto status = this->spaceMgr_.Setup(config.storageBackends, config.kvcacheBlockSize); if (status.Failure()) { return status.Underlying(); } if (config.transferEnable) { + if (config.uniqueId.empty()) { + UC_ERROR("UniqueId is required."); + return Status::InvalidParam().Underlying(); + } status = this->transMgr_.Setup( config.transferLocalRankSize, config.transferDeviceId, config.transferStreamNumber, config.kvcacheBlockSize, config.transferIoSize, config.transferIoDirect, config.transferBufferNumber, this->spaceMgr_.GetSpaceLayout(), - config.transferTimeoutMs, config.transferScatterGatherEnable); + config.transferTimeoutMs, config.transferScatterGatherEnable, config.uniqueId); if (status.Failure()) { return status.Underlying(); } } this->ShowConfig(config); @@ -86,6 +90,7 @@ class PcStoreImpl : public PcStore { UC_INFO("Set UC::BlockSize to {}.", config.kvcacheBlockSize); UC_INFO("Set UC::TransferEnable to {}.", config.transferEnable); if (!config.transferEnable) { return; } + UC_INFO("Set UC::UniqueId to {}.", config.uniqueId); UC_INFO("Set UC::IoSize to {}.", config.transferIoSize); UC_INFO("Set UC::IoDirect to {}.", config.transferIoDirect); UC_INFO("Set UC::LocalRankSize to {}.", config.transferLocalRankSize); @@ -112,4 +117,4 @@ int32_t PcStore::Setup(const Config& config) return impl->Setup(config); } -} // namespace UC +} // namespace UC diff --git a/ucm/store/pcstore/cc/api/pcstore.h b/ucm/store/pcstore/cc/api/pcstore.h index 93177a961..5db446ad9 100644 --- a/ucm/store/pcstore/cc/api/pcstore.h +++ b/ucm/store/pcstore/cc/api/pcstore.h @@ -35,6 +35,7 @@ class PcStore : CCStore { std::vector storageBackends; size_t kvcacheBlockSize; bool transferEnable; + std::string uniqueId{}; size_t transferIoSize{262144}; bool transferIoDirect{false}; size_t transferLocalRankSize{1}; @@ -46,7 +47,8 @@ class PcStore : CCStore { Config(const std::vector& storageBackends, const size_t kvcacheBlockSize, const bool transferEnable) - : storageBackends{storageBackends}, kvcacheBlockSize{kvcacheBlockSize}, + : storageBackends{storageBackends}, + kvcacheBlockSize{kvcacheBlockSize}, transferEnable{transferEnable} { } @@ -87,6 +89,6 @@ class PcStore : CCStore { PcStore* impl_{nullptr}; }; -} // namespace UC +} // namespace UC #endif diff --git a/ucm/store/pcstore/cc/domain/file/file.cc b/ucm/store/pcstore/cc/domain/file/file.cc new file mode 100644 index 000000000..95c33a654 --- /dev/null +++ b/ucm/store/pcstore/cc/domain/file/file.cc @@ -0,0 +1,97 @@ +/** + * MIT License + * + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * */ +#include "file.h" +#include "logger/logger.h" +#include "posix_file.h" + +namespace UC { + +using FileImpl = PosixFile; + +std::unique_ptr File::Make(const std::string& path) +{ + try { + return std::make_unique(path); + } catch (const std::exception& e) { + UC_ERROR("Failed({}) to make file({}) pointer.", e.what(), path); + return nullptr; + } +} + +Status File::MkDir(const std::string& path) { return FileImpl{path}.MkDir(); } + +Status File::RmDir(const std::string& path) { return FileImpl{path}.RmDir(); } + +Status File::Rename(const std::string& path, const std::string& newName) +{ + return FileImpl{path}.Rename(newName); +} + +Status File::Access(const std::string& path, const int32_t mode) +{ + return FileImpl{path}.Access(mode); +} + +Status File::Stat(const std::string& path, IFile::FileStat& st) +{ + FileImpl file{path}; + auto status = file.Open(IFile::OpenFlag::READ_ONLY); + if (status.Failure()) { return status; } + status = file.Stat(st); + file.Close(); + return status; +} + +Status File::Read(const std::string& path, const size_t offset, const size_t length, + uintptr_t address, const bool directIo) +{ + FileImpl file{path}; + Status status = Status::OK(); + auto flags = directIo ? IFile::OpenFlag::READ_ONLY | IFile::OpenFlag::DIRECT + : IFile::OpenFlag::READ_ONLY; + if ((status = file.Open(flags)).Failure()) { return status; } + if ((status = file.Read((void*)address, length, offset)).Failure()) { return status; } + return status; +} + +Status File::Write(const std::string& path, const size_t offset, const size_t length, + const uintptr_t address, const bool directIo, const bool create) +{ + FileImpl file{path}; + Status status = Status::OK(); + auto flags = IFile::OpenFlag::WRITE_ONLY; + if (directIo) { flags |= IFile::OpenFlag::DIRECT; } + if (create) { flags |= IFile::OpenFlag::CREATE; } + if ((status = file.Open(flags)).Failure()) { return status; } + if ((status = file.Write((const void*)address, length, offset)).Failure()) { return status; } + return status; +} + +void File::MUnmap(void* addr, size_t size) { FileImpl{{}}.MUnmap(addr, size); } + +void File::ShmUnlink(const std::string& path) { FileImpl{path}.ShmUnlink(); } + +void File::Remove(const std::string& path) { FileImpl{path}.Remove(); } + +} // namespace UC diff --git a/ucm/store/localstore/cc/domain/cache/cache_hash.h b/ucm/store/pcstore/cc/domain/file/file.h similarity index 53% rename from ucm/store/localstore/cc/domain/cache/cache_hash.h rename to ucm/store/pcstore/cc/domain/file/file.h index e37ef79e1..4bd1291a4 100644 --- a/ucm/store/localstore/cc/domain/cache/cache_hash.h +++ b/ucm/store/pcstore/cc/domain/file/file.h @@ -21,34 +21,32 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * */ -#ifndef UNIFIEDCACHE_CACHE_HASH_H -#define UNIFIEDCACHE_CACHE_HASH_H +#ifndef UNIFIEDCACHE_FILE_H +#define UNIFIEDCACHE_FILE_H -#include -#include -#include -#include +#include +#include "ifile.h" namespace UC { -class CacheHash { - uint32_t capacity_; - void* addr_; - +class File { public: - static constexpr uint32_t npos = std::numeric_limits::max(); - CacheHash() : capacity_{0}, addr_{nullptr} {} - void Setup(const uint32_t capacity) noexcept { this->capacity_ = capacity; } - size_t MemorySize() const noexcept; - void Setup(void* addr) noexcept; - void Insert(const std::string& id, const size_t offset, const uint32_t index) noexcept; - uint32_t Find(const std::string& id, const size_t offset) noexcept; - void PutRef(const uint32_t index) noexcept; - void PutRef(const std::string& id, const size_t offset) noexcept; - void Remove(const std::string& id, const size_t offset) noexcept; - uint32_t Evict() noexcept; + static std::unique_ptr Make(const std::string& path); + static Status MkDir(const std::string& path); + static Status RmDir(const std::string& path); + static Status Rename(const std::string& path, const std::string& newName); + static Status Access(const std::string& path, const int32_t mode); + static Status Stat(const std::string& path, IFile::FileStat& st); + static Status Read(const std::string& path, const size_t offset, const size_t length, + uintptr_t address, const bool directIo = false); + static Status Write(const std::string& path, const size_t offset, const size_t length, + const uintptr_t address, const bool directIo = false, + const bool create = false); + static void MUnmap(void* addr, size_t size); + static void ShmUnlink(const std::string& path); + static void Remove(const std::string& path); }; -} // namespace UC +} // namespace UC #endif diff --git a/ucm/store/pcstore/cc/domain/file/ifile.h b/ucm/store/pcstore/cc/domain/file/ifile.h new file mode 100644 index 000000000..512c33ba4 --- /dev/null +++ b/ucm/store/pcstore/cc/domain/file/ifile.h @@ -0,0 +1,81 @@ +/** + * MIT License + * + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * */ +#ifndef UNIFIEDCACHE_IFILE_H +#define UNIFIEDCACHE_IFILE_H + +#include +#include +#include "status/status.h" + +namespace UC { + +class IFile { +public: + class AccessMode { + public: + static constexpr int32_t READ = R_OK; + static constexpr int32_t WRITE = W_OK; + static constexpr int32_t EXIST = F_OK; + static constexpr int32_t EXECUTE = X_OK; + }; + class OpenFlag { + public: + static constexpr uint32_t READ_ONLY = O_RDONLY; + static constexpr uint32_t WRITE_ONLY = O_WRONLY; + static constexpr uint32_t READ_WRITE = O_RDWR; + static constexpr uint32_t CREATE = O_CREAT; + static constexpr uint32_t DIRECT = O_DIRECT; + static constexpr uint32_t APPEND = O_APPEND; + static constexpr uint32_t EXCL = O_EXCL; + }; + using FileStat = struct stat64; + +public: + IFile(const std::string& path) : path_{path} {} + virtual ~IFile() = default; + const std::string& Path() const { return this->path_; } + virtual Status MkDir() = 0; + virtual Status RmDir() = 0; + virtual Status Rename(const std::string& newName) = 0; + virtual Status Access(const int32_t mode) = 0; + virtual Status Open(const uint32_t flags) = 0; + virtual void Close() = 0; + virtual void Remove() = 0; + virtual Status Read(void* buffer, size_t size, off64_t offset = -1) = 0; + virtual Status Write(const void* buffer, size_t size, off64_t offset = -1) = 0; + virtual Status Truncate(size_t length) = 0; + virtual Status Stat(FileStat& st) = 0; + virtual Status ShmOpen(const uint32_t flags) = 0; + virtual Status MMap(void*& addr, size_t size, bool write, bool read, bool shared) = 0; + virtual void MUnmap(void* addr, size_t size) = 0; + virtual void ShmUnlink() = 0; + virtual Status UpdateTime() = 0; + +private: + std::string path_; +}; + +} // namespace UC + +#endif diff --git a/ucm/store/pcstore/cc/domain/file/posix_file.cc b/ucm/store/pcstore/cc/domain/file/posix_file.cc new file mode 100644 index 000000000..b550c0a43 --- /dev/null +++ b/ucm/store/pcstore/cc/domain/file/posix_file.cc @@ -0,0 +1,245 @@ +/** + * MIT License + * + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * */ +#include "posix_file.h" +#include +#include +#include +#include +#include +#include "logger/logger.h" + +namespace UC { + +static constexpr auto NewFilePerm = (S_IREAD | S_IWRITE | S_IRGRP | S_IROTH); + +PosixFile::~PosixFile() { this->Close(); } + +Status PosixFile::MkDir() +{ + constexpr auto permission = (S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IROTH); + auto ret = mkdir(this->Path().c_str(), permission); + auto eno = errno; + if (ret != 0) { + if (eno == EEXIST) { + return Status::DuplicateKey(); + } else { + UC_ERROR("Failed to create directory, path: {}, errcode: {}, errno: {}.", this->Path(), + ret, eno); + return Status::OsApiError(); + } + } + return Status::OK(); +} + +Status PosixFile::RmDir() +{ + auto ret = rmdir(this->Path().c_str()); + auto eno = errno; + if (ret != 0) { + if (eno != ENOTEMPTY) { UC_WARN("Failed to remove directory, path: {}.", this->Path()); } + return Status::OsApiError(); + } + return Status::OK(); +} + +Status PosixFile::Rename(const std::string& newName) +{ + auto ret = rename(this->Path().c_str(), newName.c_str()); + auto eno = errno; + if (ret != 0) { + if (eno == ENOENT) { + return Status::NotFound(); + } else { + UC_ERROR("Failed to rename file, old path: {}, new path: {}, errno: {}.", this->Path(), + newName, eno); + return Status::OsApiError(); + } + } + return Status::OK(); +} + +Status PosixFile::Access(const int32_t mode) +{ + auto ret = access(this->Path().c_str(), mode); + auto eno = errno; + if (ret != 0) { + if (eno == ENOENT) { + return Status::NotFound(); + } else { + UC_ERROR("Failed to access file, path: {}, mode: {}, errcode: {}, errno: {}.", + this->Path(), mode, ret, eno); + return Status::OsApiError(); + } + } + return Status::OK(); +} + +Status PosixFile::Open(const uint32_t flags) +{ + this->handle_ = open(this->Path().c_str(), flags, NewFilePerm); + auto eno = errno; + auto status = this->handle_ >= 0 ? Status::OK() : Status::OsApiError(); + if (status.Failure()) { + if (eno == EEXIST) { + status = Status::DuplicateKey(); + } else { + UC_ERROR("Failed({},{}) to open file({}) with flags({}).", eno, status, this->Path(), + flags); + } + } + return status; +} + +void PosixFile::Close() +{ + if (this->handle_ != -1) { close(this->handle_); } + this->handle_ = -1; +} + +void PosixFile::Remove() +{ + auto ret = remove(this->Path().c_str()); + auto eno = errno; + if (ret != 0) { + if (eno != ENOENT) { UC_WARN("Failed({},{}) to remove file({}).", ret, eno, this->Path()); } + } +} + +Status PosixFile::Read(void* buffer, size_t size, off64_t offset) +{ + ssize_t nBytes = -1; + if (offset != -1) { + nBytes = pread(this->handle_, buffer, size, offset); + } else { + nBytes = read(this->handle_, buffer, size); + } + auto eno = errno; + if (nBytes != static_cast(size)) { + UC_ERROR("Failed to read file, path: {}, size: {}, offset: {}, errno: {}.", this->Path(), + size, offset, eno); + return Status::OsApiError(); + } + return Status::OK(); +} + +Status PosixFile::Write(const void* buffer, size_t size, off64_t offset) +{ + ssize_t nBytes = -1; + if (offset != -1) { + nBytes = pwrite(this->handle_, buffer, size, offset); + } else { + nBytes = write(this->handle_, buffer, size); + } + auto eno = errno; + if (nBytes != static_cast(size)) { + UC_ERROR("Failed to write file, path: {}, size: {}, offset: {}, errno: {}.", this->Path(), + size, offset, eno); + return Status::OsApiError(); + } + return Status::OK(); +} + +Status PosixFile::Truncate(size_t length) +{ + auto ret = ftruncate(this->handle_, length); + auto eno = errno; + if (ret != 0) { + UC_ERROR("Failed to truncate file, path: {}, length: {}, errno: {}.", this->Path(), length, + eno); + return Status::OsApiError(); + } + return Status::OK(); +} + +Status PosixFile::Stat(FileStat& st) +{ + auto ret = fstat64(this->handle_, &st); + auto eno = errno; + if (ret != 0) { + UC_ERROR("Failed({},{}) to stat file({}).", ret, eno, this->Path()); + return Status::OsApiError(); + } + return Status::OK(); +} + +Status PosixFile::ShmOpen(const uint32_t flags) +{ + this->handle_ = shm_open(this->Path().c_str(), flags, NewFilePerm); + auto eno = errno; + auto status = this->handle_ >= 0 ? Status::OK() : Status::OsApiError(); + if (status.Failure()) { + if (eno == EEXIST) { + status = Status::DuplicateKey(); + } else { + UC_ERROR("Failed({},{}) to shm_open file({}) with flags({}).", eno, status, + this->Path(), flags); + } + } + return status; +} + +Status PosixFile::MMap(void*& addr, size_t size, bool write, bool read, bool shared) +{ + auto prot = PROT_NONE; + if (write) { prot |= PROT_WRITE; } + if (read) { prot |= PROT_READ; } + auto flags = 0; + if (shared) { flags |= MAP_SHARED; } + addr = mmap(nullptr, size, prot, flags, this->handle_, 0); + auto eno = errno; + if (addr == MAP_FAILED) { + UC_ERROR("Failed({}) to mmap file({}) with flags({},{}).", eno, this->Path(), prot, flags); + return Status::OsApiError(); + } + return Status::OK(); +} + +void PosixFile::MUnmap(void* addr, size_t size) +{ + auto ret = munmap(addr, size); + auto eno = errno; + if (ret < 0) { UC_WARN("Failed({},{}) to unmap memory({}).", ret, eno, size); } +} + +void PosixFile::ShmUnlink() +{ + auto ret = shm_unlink(this->Path().c_str()); + auto eno = errno; + if (ret < 0) { + if (eno != ENOENT) { UC_WARN("Failed({},{}) to unlink file({}).", ret, eno, this->Path()); } + } +} + +Status PosixFile::UpdateTime() +{ + auto ret = utime(this->Path().c_str(), nullptr); + auto eno = errno; + if (ret != 0) { + UC_ERROR("Failed({},{}) to update time file({}).", ret, eno, this->Path()); + return Status::OsApiError(); + } + return Status::OK(); +} + +} // namespace UC diff --git a/ucm/store/localstore/cc/domain/cache/cache_meta.h b/ucm/store/pcstore/cc/domain/file/posix_file.h similarity index 53% rename from ucm/store/localstore/cc/domain/cache/cache_meta.h rename to ucm/store/pcstore/cc/domain/file/posix_file.h index a205d0c11..75c6f0271 100644 --- a/ucm/store/localstore/cc/domain/cache/cache_meta.h +++ b/ucm/store/pcstore/cc/domain/file/posix_file.h @@ -21,38 +21,38 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * */ -#ifndef UNIFIEDCACHE_CACHE_META_H -#define UNIFIEDCACHE_CACHE_META_H +#ifndef UNIFIEDCACHE_POSIX_FILE_H +#define UNIFIEDCACHE_POSIX_FILE_H -#include "cache_hash.h" -#include "cache_index.h" -#include "file/ifile.h" -#include "status/status.h" +#include "ifile.h" namespace UC { -class CacheMeta { - using Index = uint32_t; - void* addr_; - size_t size_; - CacheIndex index_; - CacheHash hash_; - +class PosixFile : public IFile { public: - static constexpr Index npos = std::numeric_limits::max(); - CacheMeta() : addr_{nullptr}, size_{0} {} - ~CacheMeta(); - Status Setup(const Index capacity) noexcept; - Index Alloc(const std::string& id, const size_t offset) noexcept; - Index Find(const std::string& id, const size_t offset) noexcept; - void PutRef(const uint32_t index) noexcept; - void PutRef(const std::string& id, const size_t offset) noexcept; + PosixFile(const std::string& path) : IFile{path}, handle_{-1} {} + ~PosixFile() override; + Status MkDir() override; + Status RmDir() override; + Status Rename(const std::string& newName) override; + Status Access(const int32_t mode) override; + Status Open(const uint32_t flags) override; + void Close() override; + void Remove() override; + Status Read(void* buffer, size_t size, off64_t offset = -1) override; + Status Write(const void* buffer, size_t size, off64_t offset = -1) override; + Status Truncate(size_t length) override; + Status Stat(FileStat& st) override; + Status ShmOpen(const uint32_t flags) override; + Status MMap(void*& addr, size_t size, bool write, bool read, bool shared) override; + void MUnmap(void* addr, size_t size) override; + void ShmUnlink() override; + Status UpdateTime() override; private: - Status InitShmMeta(IFile* shmMetaFile); - Status LoadShmMeta(IFile* shmMetaFile); + int32_t handle_; }; -} // namespace UC +} // namespace UC -#endif +#endif // UNIFIEDCACHE_POSIX_FILE_H diff --git a/ucm/store/pcstore/cc/domain/trans/share_buffer.cc b/ucm/store/pcstore/cc/domain/trans/share_buffer.cc index 35e9ae3da..541da9404 100644 --- a/ucm/store/pcstore/cc/domain/trans/share_buffer.cc +++ b/ucm/store/pcstore/cc/domain/trans/share_buffer.cc @@ -24,6 +24,7 @@ #include "share_buffer.h" #include #include +#include #include #include #include "file/file.h" @@ -97,21 +98,36 @@ struct ShareBufferHeader { ShareBlockHeader headers[0]; }; -inline std::string GenShareBufferName(const size_t blockSize, const size_t blockNumber, - const bool ioDirect, const size_t nSharer) +const inline std::string& ShmPrefix() noexcept { - return fmt::format("uc.buf-{}-{}-{}-{:04x}", blockSize, blockNumber, ioDirect, nSharer); + static std::string prefix{"uc_shm_pcstore_"}; + return prefix; +} +void CleanUpShmFileExceptMe(const std::string& me) +{ + namespace fs = std::filesystem; + std::string_view prefix = ShmPrefix(); + fs::path shmDir = "/dev/shm"; + if (!fs::exists(shmDir)) { return; } + for (const auto& entry : fs::directory_iterator(shmDir)) { + const auto& name = entry.path().filename().string(); + if (entry.is_regular_file() && (name.compare(0, prefix.length(), prefix) == 0) && + name != me) { + fs::remove(entry.path()); + } + } } Status ShareBuffer::Setup(const size_t blockSize, const size_t blockNumber, const bool ioDirect, - const size_t nSharer) + const size_t nSharer, const std::string& uniqueId) { this->blockSize_ = blockSize; this->blockNumber_ = blockNumber; this->ioDirect_ = ioDirect; this->nSharer_ = nSharer; this->addr_ = nullptr; - this->shmName_ = GenShareBufferName(blockSize, blockNumber, ioDirect, nSharer); + this->shmName_ = ShmPrefix() + uniqueId; + CleanUpShmFileExceptMe(this->shmName_); auto file = File::Make(this->shmName_); if (!file) { return Status::OutOfMemory(); } auto flags = IFile::OpenFlag::CREATE | IFile::OpenFlag::EXCL | IFile::OpenFlag::READ_WRITE; @@ -305,4 +321,4 @@ uintptr_t ShareBuffer::Reader::GetData() return (uintptr_t)header->Data(); } -} // namespace UC +} // namespace UC diff --git a/ucm/store/pcstore/cc/domain/trans/share_buffer.h b/ucm/store/pcstore/cc/domain/trans/share_buffer.h index 3fce7a87c..8827468d0 100644 --- a/ucm/store/pcstore/cc/domain/trans/share_buffer.h +++ b/ucm/store/pcstore/cc/domain/trans/share_buffer.h @@ -49,7 +49,11 @@ class ShareBuffer { private: Reader(const std::string& block, const std::string& path, const size_t length, const bool ioDirect, const size_t nSharer, void* addr) - : block_{block}, path_{path}, length_{length}, ioDirect_{ioDirect}, nSharer_{nSharer}, + : block_{block}, + path_{path}, + length_{length}, + ioDirect_{ioDirect}, + nSharer_{nSharer}, addr_{addr} { } @@ -58,7 +62,7 @@ class ShareBuffer { public: Status Setup(const size_t blockSize, const size_t blockNumber, const bool ioDirect, - const size_t nSharer); + const size_t nSharer, const std::string& uniqueId); ~ShareBuffer(); std::shared_ptr MakeReader(const std::string& block, const std::string& path); @@ -80,6 +84,6 @@ class ShareBuffer { void* addr_; }; -} // namespace UC +} // namespace UC #endif diff --git a/ucm/store/pcstore/cc/domain/trans/trans_manager.cc b/ucm/store/pcstore/cc/domain/trans/trans_manager.cc index 9ef3370f8..aeb30543b 100644 --- a/ucm/store/pcstore/cc/domain/trans/trans_manager.cc +++ b/ucm/store/pcstore/cc/domain/trans/trans_manager.cc @@ -29,16 +29,17 @@ namespace UC { Status TransManager::Setup(const size_t rankSize, const int32_t deviceId, const size_t streamNumber, const size_t blockSize, const size_t ioSize, const bool ioDirect, const size_t bufferNumber, const SpaceLayout* layout, - const size_t timeoutMs, const bool scatterGatherEnable) + const size_t timeoutMs, const bool scatterGatherEnable, + const std::string& uniqueId) { auto s = Status::OK(); if (rankSize > 1) { s = this->shareQueue_.Setup(rankSize, deviceId, streamNumber, blockSize, ioSize, ioDirect, - bufferNumber, layout, &this->failureSet_); + bufferNumber, layout, &this->failureSet_, uniqueId); if (s.Failure()) { return s; } } s = this->queue_.Setup(deviceId, streamNumber, blockSize, ioSize, ioDirect, bufferNumber, - layout, &this->failureSet_, scatterGatherEnable); + layout, &this->failureSet_, scatterGatherEnable, timeoutMs); if (s.Failure()) { return s; } this->rankSize_ = rankSize; this->timeoutMs_ = timeoutMs; @@ -115,4 +116,4 @@ Status TransManager::Check(const size_t taskId, bool& finish) noexcept return Status::OK(); } -} // namespace UC +} // namespace UC diff --git a/ucm/store/pcstore/cc/domain/trans/trans_manager.h b/ucm/store/pcstore/cc/domain/trans/trans_manager.h index 9a806e6f3..8f14c257a 100644 --- a/ucm/store/pcstore/cc/domain/trans/trans_manager.h +++ b/ucm/store/pcstore/cc/domain/trans/trans_manager.h @@ -34,7 +34,7 @@ class TransManager { Status Setup(const size_t rankSize, const int32_t deviceId, const size_t streamNumber, const size_t blockSize, const size_t ioSize, const bool ioDirect, const size_t bufferNumber, const SpaceLayout* layout, const size_t timeoutMs, - const bool scatterGatherEnable); + const bool scatterGatherEnable, const std::string& uniqueId); Status Submit(TransTask task, size_t& taskId) noexcept; Status Wait(const size_t taskId) noexcept; Status Check(const size_t taskId, bool& finish) noexcept; @@ -52,6 +52,6 @@ class TransManager { TaskSet failureSet_; }; -} // namespace UC +} // namespace UC #endif diff --git a/ucm/store/pcstore/cc/domain/trans/trans_queue.cc b/ucm/store/pcstore/cc/domain/trans/trans_queue.cc index b69ebfe29..a58a196aa 100644 --- a/ucm/store/pcstore/cc/domain/trans/trans_queue.cc +++ b/ucm/store/pcstore/cc/domain/trans/trans_queue.cc @@ -51,10 +51,10 @@ void TransQueue::DeviceWorker(BlockTask&& task) return; } -void TransQueue::FileWorker(BlockTask&& task) +void TransQueue::FileWorker(BlockTask& task) { if (this->failureSet_->Contains(task.owner)) { - task.done(false); + if (task.type != TransTask::Type::DUMP) { task.done(false); } return; } auto hostPtr = (uintptr_t)task.buffer.get(); @@ -75,10 +75,22 @@ void TransQueue::FileWorker(BlockTask&& task) task.done(false); } +void TransQueue::FileWorkerTimeout(BlockTask& task) +{ + static size_t lastTaskId = 0; + if (lastTaskId != task.owner) { + lastTaskId = task.owner; + UC_WARN("Task({}) timeout.", task.owner); + } + + if (task.type != TransTask::Type::DUMP) { this->failureSet_->Insert(task.owner); } + if (task.done) { task.done(false); } +} + Status TransQueue::Setup(const int32_t deviceId, const size_t streamNumber, const size_t blockSize, const size_t ioSize, const bool ioDirect, const size_t bufferNumber, const SpaceLayout* layout, TaskSet* failureSet_, - const bool scatterGatherEnable) + const bool scatterGatherEnable, const size_t timeoutMs) { Trans::Device device; auto ts = device.Setup(deviceId); @@ -105,13 +117,21 @@ Status TransQueue::Setup(const int32_t deviceId, const size_t streamNumber, cons UC_ERROR("Failed({}) to make host buffer({},{}).", ts.ToString(), blockSize, bufferNumber); return Status::Error(); } - auto success = - this->devPool_.SetWorkerFn([this](auto t, auto) { this->DeviceWorker(std::move(t)); }) - .Run(); + auto success = this->devPool_ + .SetWorkerInitFn([deviceId](auto&) { + Trans::Device device; + auto ts = device.Setup(deviceId); + return ts.Success(); + }) + .SetWorkerFn([this](auto t, auto) { this->DeviceWorker(std::move(t)); }) + .SetNWorker(streamNumber) + .Run(); if (!success) { return Status::Error(); } - success = this->filePool_.SetWorkerFn([this](auto t, auto) { this->FileWorker(std::move(t)); }) - .SetNWorker(streamNumber) - .Run(); + success = + this->filePool_.SetWorkerFn([this](auto t, auto) { this->FileWorker(t); }) + .SetWorkerTimeoutFn([this](auto t, auto) { this->FileWorkerTimeout(t); }, timeoutMs) + .SetNWorker(streamNumber) + .Run(); if (!success) { return Status::Error(); } this->layout_ = layout; this->ioSize_ = ioSize; @@ -221,4 +241,4 @@ void TransQueue::DispatchSatterGatherDump(TaskPtr task, WaiterPtr waiter) } } -} // namespace UC +} // namespace UC diff --git a/ucm/store/pcstore/cc/domain/trans/trans_queue.h b/ucm/store/pcstore/cc/domain/trans/trans_queue.h index 377f09d5d..abf6b1f78 100644 --- a/ucm/store/pcstore/cc/domain/trans/trans_queue.h +++ b/ucm/store/pcstore/cc/domain/trans/trans_queue.h @@ -46,12 +46,14 @@ class TransQueue { std::function done; }; void DeviceWorker(BlockTask&& task); - void FileWorker(BlockTask&& task); + void FileWorker(BlockTask& task); + void FileWorkerTimeout(BlockTask& task); public: Status Setup(const int32_t deviceId, const size_t streamNumber, const size_t blockSize, const size_t ioSize, const bool ioDirect, const size_t bufferNumber, - const SpaceLayout* layout, TaskSet* failureSet_, const bool scatterGatherEnable); + const SpaceLayout* layout, TaskSet* failureSet_, const bool scatterGatherEnable, + const size_t timeoutMs); void Dispatch(TaskPtr task, WaiterPtr waiter); void DispatchDump(TaskPtr task, WaiterPtr waiter); void DispatchSatterGatherDump(TaskPtr task, WaiterPtr waiter); @@ -70,6 +72,6 @@ class TransQueue { bool scatterGatherEnable_; }; -} // namespace UC +} // namespace UC #endif diff --git a/ucm/store/pcstore/cc/domain/trans/trans_share_queue.cc b/ucm/store/pcstore/cc/domain/trans/trans_share_queue.cc index c43d16a85..840bb6b5e 100644 --- a/ucm/store/pcstore/cc/domain/trans/trans_share_queue.cc +++ b/ucm/store/pcstore/cc/domain/trans/trans_share_queue.cc @@ -42,14 +42,15 @@ TransShareQueue::~TransShareQueue() Status TransShareQueue::Setup(const size_t nSharer, const int32_t deviceId, const size_t streamNumber, const size_t blockSize, const size_t ioSize, const bool ioDirect, const size_t bufferNumber, - const SpaceLayout* layout, TaskSet* failureSet) + const SpaceLayout* layout, TaskSet* failureSet, + const std::string& uniqueId) { this->deviceId_ = deviceId; this->streamNumber_ = streamNumber; this->ioSize_ = ioSize; this->layout_ = layout; this->failureSet_ = failureSet; - auto status = this->buffer_.Setup(blockSize, bufferNumber, ioDirect, nSharer); + auto status = this->buffer_.Setup(blockSize, bufferNumber, ioDirect, nSharer, uniqueId); if (status.Failure()) { return status; } std::list> start(streamNumber); std::list> fut; @@ -166,4 +167,4 @@ void TransShareQueue::HandleLoadTask(BlockTask& task, Trans::Stream& stream) this->HandleReadyTask(s, task, stream); } -} // namespace UC +} // namespace UC diff --git a/ucm/store/pcstore/cc/domain/trans/trans_share_queue.h b/ucm/store/pcstore/cc/domain/trans/trans_share_queue.h index 7c40b0542..7d76cad0a 100644 --- a/ucm/store/pcstore/cc/domain/trans/trans_share_queue.h +++ b/ucm/store/pcstore/cc/domain/trans/trans_share_queue.h @@ -63,7 +63,8 @@ class TransShareQueue { ~TransShareQueue(); Status Setup(const size_t nSharer, const int32_t deviceId, const size_t streamNumber, const size_t blockSize, const size_t ioSize, const bool ioDirect, - const size_t bufferNumber, const SpaceLayout* layout, TaskSet* failureSet); + const size_t bufferNumber, const SpaceLayout* layout, TaskSet* failureSet, + const std::string& uniqueId); void Dispatch(TaskPtr task, WaiterPtr waiter); private: @@ -73,6 +74,6 @@ class TransShareQueue { void HandleLoadTask(BlockTask& task, Trans::Stream& stream); }; -} // namespace UC +} // namespace UC #endif diff --git a/ucm/store/pcstore/cpy/pcstore.py.cc b/ucm/store/pcstore/cpy/pcstore.py.cc index e8a11346c..cadb1ebe2 100644 --- a/ucm/store/pcstore/cpy/pcstore.py.cc +++ b/ucm/store/pcstore/cpy/pcstore.py.cc @@ -94,6 +94,7 @@ PYBIND11_MODULE(ucmpcstore, module) config.def_readwrite("storageBackends", &UC::PcStorePy::Config::storageBackends); config.def_readwrite("kvcacheBlockSize", &UC::PcStorePy::Config::kvcacheBlockSize); config.def_readwrite("transferEnable", &UC::PcStorePy::Config::transferEnable); + config.def_readwrite("uniqueId", &UC::PcStorePy::Config::uniqueId); config.def_readwrite("transferIoDirect", &UC::PcStorePy::Config::transferIoDirect); config.def_readwrite("transferLocalRankSize", &UC::PcStorePy::Config::transferLocalRankSize); config.def_readwrite("transferDeviceId", &UC::PcStorePy::Config::transferDeviceId); diff --git a/ucm/store/pcstore/pcstore_connector.py b/ucm/store/pcstore/pcstore_connector.py index 13b0b0b6e..7217620ec 100644 --- a/ucm/store/pcstore/pcstore_connector.py +++ b/ucm/store/pcstore/pcstore_connector.py @@ -47,12 +47,13 @@ def __init__(self, config: Dict): transfer_enable = True if config["role"] == "worker" else False param = ucmpcstore.PcStore.Config(storage_backends, block_size, transfer_enable) if transfer_enable: + param.uniqueId = config["unique_id"] param.transferDeviceId = config["device"] param.transferIoSize = config["io_size"] param.transferIoDirect = config.get("use_direct", False) param.transferStreamNumber = config.get("stream_number", 8) param.transferBufferNumber = config.get("buffer_number", 4096) - param.transferLocalRankSize = config.get("local_rank_size", 8) + param.transferLocalRankSize = config.get("local_rank_size", 1) param.transferScatterGatherEnable = config.get("use_scatter_gatter", False) ret = self.store.Setup(param) if ret != 0: diff --git a/ucm/store/pcstore/pcstore_connector_v1.py b/ucm/store/pcstore/pcstore_connector_v1.py new file mode 100644 index 000000000..39f0589ff --- /dev/null +++ b/ucm/store/pcstore/pcstore_connector_v1.py @@ -0,0 +1,222 @@ +# -*- coding: utf-8 -*- +# +# MIT License +# +# Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +from dataclasses import dataclass +from typing import Dict, List, Tuple + +import numpy as np +import torch + +from ucm.store.pcstore import ucmpcstore +from ucm.store.ucmstore_v1 import Task, UcmKVStoreBaseV1 + + +@dataclass +class UcmPcTask(Task): + task_id: int + + +class UcmPcStoreV1(UcmKVStoreBaseV1): + def __init__(self, config: Dict): + super().__init__(config) + self.store = ucmpcstore.PcStore() + storage_backends = [ + path for path in config["storage_backends"].split(":") if path + ] + block_size = config.get("kv_block_size", 33554432) + transfer_enable = True if config["role"] == "worker" else False + param = ucmpcstore.PcStore.Config(storage_backends, block_size, transfer_enable) + if transfer_enable: + param.uniqueId = config["unique_id"] + param.transferDeviceId = config["device"] + param.transferIoSize = config["io_size"] + param.transferIoDirect = config.get("use_direct", False) + param.transferStreamNumber = config.get("stream_number", 8) + param.transferBufferNumber = config.get("buffer_number", 4096) + param.transferLocalRankSize = config.get("local_rank_size", 1) + param.transferScatterGatherEnable = config.get("use_scatter_gatter", False) + ret = self.store.Setup(param) + if ret != 0: + msg = f"Failed to initialize ucmpcstore, errcode: {ret}." + raise RuntimeError(msg) + + def cc_store(self) -> int: + """Return a low-level C/C++ pointer to the underlying store. + + Returns: + An opaque ``int`` representing the ``Store*`` instance that can + be passed to native code. + """ + return self.store.CCStoreImpl() + + def lookup(self, block_ids: List[bytes]) -> List[bool]: + """Check presence of blocks in external storage. + + Args: + block_ids: List of vLLM block hashes (raw bytes). + + Returns: + A list of booleans, ``True`` if the corresponding block exists in + storage, ``False`` otherwise. The order matches ``block_ids``. + """ + return self.store.LookupBatch(block_ids) + + def prefetch(self, block_ids: List[bytes]) -> None: + """Asynchronously prefetch blocks into high-speed cache. + + Args: + block_ids: List of vLLM block hashes to prefetch. + """ + pass + + def _flatten_tensor_ptrs( + self, block_ids: List[bytes], tensors: List[List[torch.Tensor]] + ) -> Tuple[List[bytes], List[int]]: + n_blocks = len(block_ids) + m_addrs = len(tensors[0]) + total_len = n_blocks * m_addrs + flat_ids = [None] * total_len + flat_ptrs = [None] * total_len + ids_arr = flat_ids + ptrs_arr = flat_ptrs + data_ptr_method = torch.Tensor.data_ptr + idx = 0 + for bid, tensor_list in zip(block_ids, tensors): + for t in tensor_list: + ids_arr[idx] = bid + ptrs_arr[idx] = data_ptr_method(t) + idx += 1 + return flat_ids, flat_ptrs + + def load( + self, + block_ids: List[bytes], + shard_index: List[int], + dst_tensor: List[List[torch.Tensor]], + ) -> Task: + """Initiate transfer of KV cache from storage to device. + + Args: + block_ids: Hashes of the blocks to load. + shard_index: Shard index for each block. + dst_tensor: Double-list structure where ``dst_tensor[i][j]`` is the + destination PyTorch tensor on device for block ``i``, tensor ``j``. + + Returns: + A ``Task`` handle that can be used to check or wait for completion. + """ + ids, addrs = self._flatten_tensor_ptrs(block_ids, dst_tensor) + task_id = self.store.LoadToDevice(ids, addrs) + return UcmPcTask(task_id=task_id) + + def dump( + self, + block_ids: List[bytes], + shard_index: List[int], + src_tensor: List[List[torch.Tensor]], + ) -> Task: + """Initiate transfer of KV cache from device to storage. + + Args: + block_ids: Hashes of the blocks to write. + shard_index: Shard index for each block. + src_tensor: Double-list structure where ``src_tensor[i][j]`` is the + source PyTorch tensor on device for block ``i``, tensor ``j``. + + Returns: + A ``Task`` handle that can be used to check or wait for completion. + """ + ids, addrs = self._flatten_tensor_ptrs(block_ids, src_tensor) + task_id = self.store.DumpFromDevice(ids, addrs) + return UcmPcTask(task_id=task_id) + + def load_data( + self, + block_ids: List[bytes], + shard_index: List[int], + dst_addr: List[List[int]], + ) -> Task: + """Low-level fetch: copy KV data to device pointers. + + Args: + block_ids: Block hashes to load. + shard_index: Shard index for each block. + dst_addr: Double-list of ``int`` pointers (as Python ``int``) to + pre-allocated device buffers. + + Returns: + A ``Task`` handle for the asynchronous copy. + """ + block_ids_np = np.array(block_ids, dtype=object) + dst_addr_np = np.array(dst_addr, dtype=int) + ids = np.repeat(block_ids_np, dst_addr_np.shape[1]) + addrs = dst_addr_np.ravel() + task_id = self.store.LoadToDevice(ids, addrs) + return UcmPcTask(task_id=task_id) + + def dump_data( + self, + block_ids: List[bytes], + shard_index: List[int], + src_addr: List[List[int]], + ) -> Task: + """Low-level dump: copy KV data from device pointers. + + Args: + block_ids: Block hashes to store. + shard_index: Shard index for each block. + src_addr: Double-list of ``int`` pointers to device buffers. + + Returns: + A ``Task`` handle for the asynchronous copy. + """ + block_ids_np = np.array(block_ids, dtype=object) + src_addr_np = np.array(src_addr, dtype=int) + ids = np.repeat(block_ids_np, src_addr_np.shape[1]) + addrs = src_addr_np.ravel() + task_id = self.store.DumpFromDevice(ids, addrs) + return UcmPcTask(task_id=task_id) + + def wait(self, task: Task) -> None: + """Block until the given transfer task completes. + + Args: + task: Task handle returned by ``load``, ``dump``, ``load_data``, + or ``dump_data``. + """ + ret = self.store.Wait(task.task_id) + if ret != 0: + msg = f"Failed to wait task({task.task_id}), errcode: {ret}." + raise RuntimeError(msg) + + def check(self, task: Task) -> bool: + """Non-blocking poll for task completion. + + Args: + task: Task handle returned by any transfer method. + + Returns: + ``True`` if the task has finished, ``False`` if still in-flight. + """ + return self.store.Check(task.task_id) diff --git a/ucm/store/task/CMakeLists.txt b/ucm/store/task/CMakeLists.txt deleted file mode 100644 index 543e78bc7..000000000 --- a/ucm/store/task/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -add_library(storetask STATIC task_manager.cc) -target_include_directories(storetask PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) -target_link_libraries(storetask PUBLIC storeinfra) diff --git a/ucm/store/task/task_manager.cc b/ucm/store/task/task_manager.cc deleted file mode 100644 index 38c780ccb..000000000 --- a/ucm/store/task/task_manager.cc +++ /dev/null @@ -1,98 +0,0 @@ -/** - * MIT License - * - * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * */ -#include "task_manager.h" - -namespace UC { - -Status TaskManager::Submit(Task&& task, size_t& taskId) noexcept -{ - taskId = task.Id(); - const auto taskStr = task.Str(); - TaskPtr taskPtr = nullptr; - WaiterPtr waiterPtr = nullptr; - try { - taskPtr = std::make_shared(std::move(task)); - waiterPtr = std::make_shared(0, task.StartTp()); - } catch (const std::exception& e) { - UC_ERROR("Failed({}) to submit task({}).", e.what(), taskStr); - return Status::OutOfMemory(); - } - std::lock_guard lg(mutex_); - const auto& [iter, success] = - tasks_.emplace(taskId, std::make_pair(std::move(taskPtr), std::move(waiterPtr))); - if (!success) { - UC_ERROR("Failed to submit task({}).", taskStr); - return Status::OutOfMemory(); - } - auto shards = iter->second.first->Split(queues_.size(), iter->second.second); - for (auto& shard : shards) { - auto& q = queues_[qIndex_++]; - if (qIndex_ == queues_.size()) { qIndex_ = 0; } - q->Push(shard); - } - return Status::OK(); -} - -Status TaskManager::Wait(const size_t taskId) noexcept -{ - TaskPtr task = nullptr; - WaiterPtr waiter = nullptr; - { - std::lock_guard lg(mutex_); - auto iter = tasks_.find(taskId); - if (iter == tasks_.end()) { - UC_ERROR("Not found task by id({}).", taskId); - return Status::NotFound(); - } - task = iter->second.first; - waiter = iter->second.second; - tasks_.erase(iter); - } - if (!waiter->Wait(timeoutMs_)) { - UC_ERROR("Task({}) timeout({}).", task->Str(), timeoutMs_); - failureSet_.Insert(taskId); - waiter->Wait(); - } - auto failure = failureSet_.Contains(taskId); - if (failure) { - failureSet_.Remove(taskId); - UC_ERROR("Task({}) failed.", task->Str()); - return Status::Error(); - } - return Status::OK(); -} - -Status TaskManager::Check(const size_t taskId, bool& finish) noexcept -{ - std::lock_guard lg(mutex_); - auto iter = tasks_.find(taskId); - if (iter == tasks_.end()) { - UC_ERROR("Not found task by id({}).", taskId); - return Status::NotFound(); - } - finish = iter->second.second->Finish(); - return Status::OK(); -} - -} // namespace UC diff --git a/ucm/store/task/task_manager.h b/ucm/store/task/task_manager.h deleted file mode 100644 index 513e3d65c..000000000 --- a/ucm/store/task/task_manager.h +++ /dev/null @@ -1,57 +0,0 @@ -/** - * MIT License - * - * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * */ -#ifndef UNIFIEDCACHE_TASK_MANAGER_H -#define UNIFIEDCACHE_TASK_MANAGER_H - -#include -#include "status/status.h" -#include "task_queue.h" -#include "task_set.h" - -namespace UC { - -class TaskManager { - using TaskPtr = std::shared_ptr; - using WaiterPtr = std::shared_ptr; - using TaskPair = std::pair; - using QueuePtr = std::shared_ptr; - -public: - virtual ~TaskManager() = default; - virtual Status Submit(Task&& task, size_t& taskId) noexcept; - virtual Status Wait(const size_t taskId) noexcept; - virtual Status Check(const size_t taskId, bool& finish) noexcept; - -protected: - std::mutex mutex_; - std::unordered_map tasks_; - size_t qIndex_{0}; - std::vector queues_; - size_t timeoutMs_{0}; - TaskSet failureSet_; -}; - -} // namespace UC - -#endif diff --git a/ucm/store/task/task_waiter.h b/ucm/store/task/task_waiter.h deleted file mode 100644 index 96358b8db..000000000 --- a/ucm/store/task/task_waiter.h +++ /dev/null @@ -1,68 +0,0 @@ -/** - * MIT License - * - * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * */ -#ifndef UNIFIEDCACHE_ITASK_WAITER_H -#define UNIFIEDCACHE_ITASK_WAITER_H - -#include -#include "thread/latch.h" - -namespace UC { - -class TaskWaiter : public Latch { -protected: - double startTp_; - -public: - TaskWaiter(const size_t expected, const double startTp) : Latch{expected}, startTp_{startTp} {} - virtual ~TaskWaiter() = default; - virtual void Set(const size_t expected) noexcept { this->counter_.store(expected); } - using Latch::Wait; - virtual bool Wait(const size_t timeoutMs) noexcept - { - if (timeoutMs == 0) { - this->Wait(); - return true; - } - std::unique_lock ul(this->mutex_); - if (this->counter_ == 0) { return true; } - auto elapsed = std::chrono::duration(NowTp() - startTp_); - auto elapsedMs = std::chrono::duration_cast(elapsed); - auto timeMs = std::chrono::milliseconds(timeoutMs); - if (timeMs <= elapsedMs) { return false; } - auto remainMs = timeMs - elapsedMs; - return this->cv_.wait_for(ul, remainMs, [this] { return this->counter_ == 0; }); - } - virtual bool Finish() noexcept { return this->counter_ == 0; } - -private: - static double NowTp() noexcept - { - auto now = std::chrono::steady_clock::now().time_since_epoch(); - return std::chrono::duration(now).count(); - } -}; - -} // namespace UC - -#endif diff --git a/ucm/store/test/CMakeLists.txt b/ucm/store/test/CMakeLists.txt index 0c4974efd..0a93239ec 100644 --- a/ucm/store/test/CMakeLists.txt +++ b/ucm/store/test/CMakeLists.txt @@ -1,11 +1,3 @@ if(BUILD_UNIT_TESTS) include(GoogleTest) - file(GLOB_RECURSE UCMSTORE_TEST_SOURCE_FILES "./case/*.cc") - add_executable(ucmstore.test ${UCMSTORE_TEST_SOURCE_FILES}) - target_include_directories(ucmstore.test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/case) - target_link_libraries(ucmstore.test PRIVATE - nfsstore localstore storeinfra storedevice - gtest_main gtest mockcpp - ) - gtest_discover_tests(ucmstore.test) endif() diff --git a/ucm/store/test/case/cmn/path_base.h b/ucm/store/test/case/cmn/path_base.h deleted file mode 100644 index ec709e577..000000000 --- a/ucm/store/test/case/cmn/path_base.h +++ /dev/null @@ -1,58 +0,0 @@ -/** - * MIT License - * - * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * */ -#ifndef UNIFIEDCACHE_TEST_PATH_BASE_H -#define UNIFIEDCACHE_TEST_PATH_BASE_H - -#include -#include "random.h" - -namespace UC { - -class PathBase : public ::testing::Test { -public: - void SetUp() override - { - testing::Test::SetUp(); - const auto info = testing::UnitTest::GetInstance()->current_test_info(); - std::string testCaceName = info->test_case_name(); - std::string testName = info->name(); - this->path_ = "./" + testCaceName + "_" + testName + "_" + this->rd_.RandomString(20) + "/"; - system((std::string("rm -rf ") + this->path_).c_str()); - system((std::string("mkdir -p ") + this->path_).c_str()); - } - void TearDown() override - { - system((std::string("rm -rf ") + this->path_).c_str()); - testing::Test::TearDown(); - } - std::string Path() const { return this->path_; } - -private: - Random rd_; - std::string path_; -}; - -} // namespace UC - -#endif diff --git a/ucm/store/test/case/cmn/random.h b/ucm/store/test/case/cmn/random.h deleted file mode 100644 index e0fd69a74..000000000 --- a/ucm/store/test/case/cmn/random.h +++ /dev/null @@ -1,50 +0,0 @@ -/** - * MIT License - * - * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * */ -#ifndef UNIFIEDCACHE_TEST_RANDOM_H -#define UNIFIEDCACHE_TEST_RANDOM_H - -#include - -namespace UC { - -class Random { -public: - std::string RandomString(const size_t length) - { - const std::string allowedChars = - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; - std::mt19937 gen(this->rd_()); - std::uniform_int_distribution<> dis(0, allowedChars.length() - 1); - std::string randomString(length, 0); - for (size_t i = 0; i < length; i++) { randomString[i] = allowedChars[dis(gen)]; } - return randomString; - } - -private: - std::random_device rd_; -}; - -} // namespace UC - -#endif diff --git a/ucm/store/test/case/infra/file_test.cc b/ucm/store/test/case/infra/file_test.cc deleted file mode 100644 index 36f4b086a..000000000 --- a/ucm/store/test/case/infra/file_test.cc +++ /dev/null @@ -1,42 +0,0 @@ -/** - * MIT License - * - * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * */ -#include "cmn/path_base.h" -#include "file/file.h" - -class UCFileTest : public UC::PathBase {}; - -TEST_F(UCFileTest, DirCreateAndRemove) -{ - auto path1 = this->Path() + "dir1"; - ASSERT_EQ(UC::File::Access(path1, UC::IFile::AccessMode::EXIST), UC::Status::NotFound()); - ASSERT_EQ(UC::File::MkDir(path1), UC::Status::OK()); - ASSERT_EQ(UC::File::Access(path1, UC::IFile::AccessMode::EXIST), UC::Status::OK()); - auto path2 = this->Path() + "dir2"; - ASSERT_EQ(UC::File::Access(path2, UC::IFile::AccessMode::EXIST), UC::Status::NotFound()); - ASSERT_EQ(UC::File::Rename(path1, path2), UC::Status::OK()); - ASSERT_EQ(UC::File::Access(path1, UC::IFile::AccessMode::EXIST), UC::Status::NotFound()); - ASSERT_EQ(UC::File::Access(path2, UC::IFile::AccessMode::EXIST), UC::Status::OK()); - ASSERT_EQ(UC::File::RmDir(path2), UC::Status::OK()); - ASSERT_EQ(UC::File::Access(path2, UC::IFile::AccessMode::EXIST), UC::Status::NotFound()); -} diff --git a/ucm/store/test/case/infra/posix_file_test.cc b/ucm/store/test/case/infra/posix_file_test.cc deleted file mode 100644 index bb92515d9..000000000 --- a/ucm/store/test/case/infra/posix_file_test.cc +++ /dev/null @@ -1,149 +0,0 @@ -/** - * MIT License - * - * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * */ -#include "cmn/path_base.h" -#include "file/posix_file.h" - -class UCPosixFileTest : public UC::PathBase { -public: - class Data { - public: - explicit Data(const size_t nPage, const size_t pageSize = 4096) - : nPage_{nPage}, pageSize_{pageSize}, data_{nullptr} - { - } - ~Data() - { - if (this->data_) { - free(this->data_); - this->data_ = nullptr; - } - } - void Generate() - { - this->data_ = malloc(this->Size()); - assert(this->data_ != nullptr); - } - void GenerateRandom() - { - this->Generate(); - for (size_t i = 0; i < this->nPage_; i++) { - *(size_t*)((char*)this->data_ + this->pageSize_ * i) = i; - } - } - int32_t Compare(const Data& other) - { - if (this->nPage_ < other.nPage_) { return -1; } - if (this->nPage_ < other.nPage_) { return 1; } - for (size_t i = 0; i < this->nPage_; i++) { - auto ret = memcmp((char*)this->data_ + this->pageSize_ * i, - (char*)other.data_ + this->pageSize_ * i, this->pageSize_); - if (ret != 0) { return ret; } - } - return 0; - } - size_t Size() const { return this->pageSize_ * this->nPage_; } - void* Buffer() const { return this->data_; } - - private: - size_t nPage_; - size_t pageSize_; - void* data_; - }; -}; - -TEST_F(UCPosixFileTest, DirCreateAndRemove) -{ - system((std::string("rm -rf ") + this->Path()).c_str()); - UC::PosixFile dir(this->Path()); - ASSERT_EQ(dir.Access(UC::IFile::AccessMode::EXIST), UC::Status::NotFound()); - ASSERT_EQ(dir.MkDir(), UC::Status::OK()); - ASSERT_EQ(dir.Access(UC::IFile::AccessMode::EXIST), UC::Status::OK()); - ASSERT_EQ(dir.Access(UC::IFile::AccessMode::READ), UC::Status::OK()); - ASSERT_EQ(dir.Access(UC::IFile::AccessMode::WRITE), UC::Status::OK()); - ASSERT_EQ(dir.MkDir(), UC::Status::DuplicateKey()); - ASSERT_EQ(dir.RmDir(), UC::Status::OK()); - ASSERT_EQ(dir.Access(UC::IFile::AccessMode::EXIST), UC::Status::NotFound()); -} - -TEST_F(UCPosixFileTest, FileCreateAndRemove) -{ - UC::PosixFile file(this->Path() + "file"); - ASSERT_EQ(file.Access(UC::IFile::AccessMode::EXIST), UC::Status::NotFound()); - ASSERT_EQ(file.Open(UC::IFile::OpenFlag::WRITE_ONLY), UC::Status::OsApiError()); - ASSERT_EQ(file.Open(UC::IFile::OpenFlag::WRITE_ONLY | UC::IFile::OpenFlag::CREATE), - UC::Status::OK()); - ASSERT_EQ(file.Open(UC::IFile::OpenFlag::WRITE_ONLY | UC::IFile::OpenFlag::CREATE | - UC::IFile::OpenFlag::EXCL), - UC::Status::DuplicateKey()); - ASSERT_EQ(file.Access(UC::IFile::AccessMode::EXIST), UC::Status::OK()); - ASSERT_EQ(file.Access(UC::IFile::AccessMode::READ), UC::Status::OK()); - ASSERT_EQ(file.Access(UC::IFile::AccessMode::WRITE), UC::Status::OK()); - file.Remove(); - ASSERT_EQ(file.Access(UC::IFile::AccessMode::EXIST), UC::Status::NotFound()); -} - -TEST_F(UCPosixFileTest, FileWriteAndRead) -{ - UC::PosixFile file(this->Path() + "file"); - size_t nPage = 4; - UCPosixFileTest::Data data0{nPage}; - UCPosixFileTest::Data data1{nPage}; - data0.GenerateRandom(); - data1.Generate(); - ASSERT_EQ(file.Access(UC::IFile::AccessMode::EXIST), UC::Status::NotFound()); - ASSERT_EQ(file.Open(UC::IFile::OpenFlag::WRITE_ONLY | UC::IFile::OpenFlag::CREATE), - UC::Status::OK()); - ASSERT_EQ(file.Write(data0.Buffer(), data0.Size(), 0), UC::Status::OK()); - file.Close(); - ASSERT_EQ(file.Open(UC::IFile::OpenFlag::READ_ONLY), UC::Status::OK()); - ASSERT_EQ(file.Read(data1.Buffer(), data1.Size(), 0), UC::Status::OK()); - file.Close(); - file.Remove(); - ASSERT_EQ(file.Access(UC::IFile::AccessMode::EXIST), UC::Status::NotFound()); - EXPECT_EQ(data0.Compare(data1), 0); -} - -TEST_F(UCPosixFileTest, FileMMapAndMUnmap) -{ - UC::Random rd; - auto fileName = rd.RandomString(20) + ".file"; - UC::PosixFile file1{fileName}; - UC::PosixFile file2{fileName}; - const size_t data = 0xfffffffe; - const auto openFlags = UC::IFile::OpenFlag::READ_WRITE | UC::IFile::OpenFlag::CREATE; - void* addr1 = nullptr; - void* addr2 = nullptr; - ASSERT_EQ(file1.Path(), file2.Path()); - ASSERT_TRUE(file1.ShmOpen(openFlags).Success()); - ASSERT_TRUE(file1.Truncate(sizeof(data)).Success()); - ASSERT_TRUE(file1.MMap(addr1, sizeof(data), true, true, true).Success()); - file1.Close(); - ASSERT_TRUE(file2.ShmOpen(openFlags).Success()); - ASSERT_TRUE(file2.MMap(addr2, sizeof(data), false, true, true).Success()); - file2.Close(); - *((size_t*)addr1) = data; - ASSERT_EQ(*(size_t*)addr2, data); - file1.ShmUnlink(); - file2.ShmUnlink(); -} diff --git a/ucm/store/test/case/localstore/cache_hash_test.cc b/ucm/store/test/case/localstore/cache_hash_test.cc deleted file mode 100644 index 2dedd28d0..000000000 --- a/ucm/store/test/case/localstore/cache_hash_test.cc +++ /dev/null @@ -1,88 +0,0 @@ -/** - * MIT License - * - * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * */ -#include -#include "cache/cache_hash.h" - -class UCCacheHashTest : public testing::Test {}; - -TEST_F(UCCacheHashTest, InsertAndFind) -{ - constexpr uint32_t capacity = 10; - const std::string id = "1234567890123456"; - UC::CacheHash hash; - hash.Setup(capacity); - auto buffer = new (std::nothrow) uint8_t[hash.MemorySize()]; - ASSERT_NE(buffer, nullptr); - hash.Setup(buffer); - for (uint32_t i = 0; i < capacity; i++) { ASSERT_EQ(hash.Find(id, i), UC::CacheHash::npos); } - for (uint32_t i = 0; i < capacity; i++) { hash.Insert(id, i, i); } - for (uint32_t i = 0; i < capacity; i++) { ASSERT_NE(hash.Find(id, i), UC::CacheHash::npos); } - for (uint32_t i = 0; i < capacity; i++) { hash.Remove(id, i); } - for (uint32_t i = 0; i < capacity; i++) { ASSERT_EQ(hash.Find(id, i), UC::CacheHash::npos); } - delete[] buffer; -} - -TEST_F(UCCacheHashTest, SetupAtSharedMemory) -{ - constexpr uint32_t capacity = 10; - const std::string id = "1234567890123456"; - UC::CacheHash hash1; - hash1.Setup(capacity); - UC::CacheHash hash2; - hash2.Setup(capacity); - auto buffer = new (std::nothrow) uint8_t[hash1.MemorySize()]; - ASSERT_NE(buffer, nullptr); - hash1.Setup(buffer); - hash2.Setup(buffer); - for (uint32_t i = 0; i < capacity; i++) { ASSERT_EQ(hash2.Find(id, i), UC::CacheHash::npos); } - for (uint32_t i = 0; i < capacity; i++) { hash1.Insert(id, i, i); } - for (uint32_t i = 0; i < capacity; i++) { ASSERT_EQ(hash2.Find(id, i), i); } - delete[] buffer; -} - -TEST_F(UCCacheHashTest, Evict) -{ - constexpr uint32_t capacity = 10; - const std::string id = "1234567890123456"; - UC::CacheHash hash; - hash.Setup(capacity); - auto buffer = new (std::nothrow) uint8_t[hash.MemorySize()]; - ASSERT_NE(buffer, nullptr); - hash.Setup(buffer); - for (uint32_t i = 0; i < capacity; i++) { ASSERT_EQ(hash.Find(id, i), UC::CacheHash::npos); } - for (uint32_t i = 0; i < capacity; i++) { - hash.Insert(id, i, i); - hash.PutRef(i); - } - for (uint32_t i = 0; i < capacity; i++) { ASSERT_NE(hash.Find(id, i), UC::CacheHash::npos); } - ASSERT_EQ(hash.Evict(), UC::CacheHash::npos); - hash.PutRef(0); - ASSERT_EQ(hash.Evict(), 0); - hash.PutRef(id, 2); - ASSERT_EQ(hash.Evict(), 2); - ASSERT_EQ(hash.Evict(), UC::CacheHash::npos); - for (uint32_t i = 0; i < capacity; i++) { hash.PutRef(i); } - ASSERT_EQ(hash.Evict(), 1); - delete[] buffer; -} diff --git a/ucm/store/test/case/localstore/cache_index_test.cc b/ucm/store/test/case/localstore/cache_index_test.cc deleted file mode 100644 index 669b53915..000000000 --- a/ucm/store/test/case/localstore/cache_index_test.cc +++ /dev/null @@ -1,63 +0,0 @@ -/** - * MIT License - * - * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * */ -#include -#include "cache/cache_index.h" - -class UCCacheIndexTest : public testing::Test {}; - -TEST_F(UCCacheIndexTest, AcquireAllIndex) -{ - constexpr uint32_t capacity = 10; - UC::CacheIndex index; - index.Setup(capacity); - auto size = index.MemorySize(); - ASSERT_GT(size, 0); - auto buffer = new (std::nothrow) uint8_t[size]; - ASSERT_NE(buffer, nullptr); - index.Setup(buffer); - for (uint32_t i = 0; i < capacity; i++) { ASSERT_EQ(index.Acquire(), i); } - ASSERT_EQ(index.Acquire(), UC::CacheIndex::npos); - delete[] buffer; -} - -TEST_F(UCCacheIndexTest, AtSharedMemory) -{ - constexpr uint32_t capacity = 10; - UC::CacheIndex index1; - index1.Setup(capacity); - auto size = index1.MemorySize(); - ASSERT_GT(size, 0); - auto buffer = new (std::nothrow) uint8_t[size]; - ASSERT_NE(buffer, nullptr); - index1.Setup(buffer); - for (uint32_t i = 0; i < capacity; i++) { ASSERT_EQ(index1.Acquire(), i); } - ASSERT_EQ(index1.Acquire(), UC::CacheIndex::npos); - UC::CacheIndex index2; - index2.Setup(capacity); - ASSERT_EQ(index1.MemorySize(), index2.MemorySize()); - index2.Setup(buffer); - ASSERT_EQ(index2.Acquire(), UC::CacheIndex::npos); - for (uint32_t i = 0; i < capacity; i++) { index2.Release(i); } - delete[] buffer; -} diff --git a/ucm/store/test/case/localstore/cache_instance_test.cc b/ucm/store/test/case/localstore/cache_instance_test.cc deleted file mode 100644 index 27ec8f5c6..000000000 --- a/ucm/store/test/case/localstore/cache_instance_test.cc +++ /dev/null @@ -1,82 +0,0 @@ -/** - * MIT License - * - * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * */ -#include -#include -#include "cache/cache_instance.h" - -class UCCacheInstanceTest : public testing::Test {}; - -TEST_F(UCCacheInstanceTest, StoreAndLoad) -{ - using Data = size_t; - constexpr Data data = 2048; - constexpr auto ioSize = sizeof(Data); - constexpr uint32_t capacity = 1024; - constexpr size_t totalSize = ioSize * capacity; - MOCKER(UC::CacheSegment::TotalSize).stubs().will(returnValue(totalSize)); - { - UC::CacheInstance instance; - ASSERT_TRUE(instance.Setup(ioSize, capacity).Success()); - const std::string id = "1234567890987654"; - const size_t offset = 1024; - ASSERT_EQ(instance.Find(id, offset), nullptr); - auto addr1 = instance.Alloc(id, offset); - ASSERT_NE(addr1, nullptr); - *(Data*)addr1 = data; - instance.PutRef(id, offset); - auto addr2 = instance.Find(id, offset); - ASSERT_NE(addr2, nullptr); - ASSERT_EQ(*(Data*)addr2, data); - instance.PutRef(id, offset); - } - GlobalMockObject::verify(); -} - -TEST_F(UCCacheInstanceTest, CacheShare) -{ - using Data = size_t; - constexpr Data data = 2048; - constexpr auto ioSize = sizeof(Data); - constexpr uint32_t capacity = 1024; - constexpr size_t totalSize = ioSize * capacity; - MOCKER(UC::CacheSegment::TotalSize).stubs().will(returnValue(totalSize)); - { - UC::CacheInstance instance1; - ASSERT_TRUE(instance1.Setup(ioSize, capacity).Success()); - UC::CacheInstance instance2; - ASSERT_TRUE(instance2.Setup(ioSize, capacity).Success()); - const std::string id = "1234567890987654"; - const size_t offset = 1024; - ASSERT_EQ(instance2.Find(id, offset), nullptr); - auto addr1 = instance1.Alloc(id, offset); - ASSERT_NE(addr1, nullptr); - *(Data*)addr1 = data; - instance1.PutRef(id, offset); - auto addr2 = instance2.Find(id, offset); - ASSERT_NE(addr2, nullptr); - ASSERT_EQ(*(Data*)addr2, data); - instance2.PutRef(id, offset); - } - GlobalMockObject::verify(); -} diff --git a/ucm/store/test/case/localstore/cache_layout_test.cc b/ucm/store/test/case/localstore/cache_layout_test.cc deleted file mode 100644 index 12dd1fddd..000000000 --- a/ucm/store/test/case/localstore/cache_layout_test.cc +++ /dev/null @@ -1,35 +0,0 @@ -/** - * MIT License - * - * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * */ -#include -#include "cache/cache_layout.h" - -class UCCacheLayoutTest : public testing::Test {}; - -TEST_F(UCCacheLayoutTest, Layout) -{ - ASSERT_EQ(UC::CacheLayout::MetaShmFile(), "/ucmlocal.meta"); - ASSERT_EQ(UC::CacheLayout::DataShmFile(0), "/ucmlocal.dat000000"); - ASSERT_EQ(UC::CacheLayout::DataShmFile(11), "/ucmlocal.dat000011"); - ASSERT_EQ(UC::CacheLayout::DataShmFile(101), "/ucmlocal.dat000101"); -} diff --git a/ucm/store/test/case/localstore/cache_segment_test.cc b/ucm/store/test/case/localstore/cache_segment_test.cc deleted file mode 100644 index 80014ea7e..000000000 --- a/ucm/store/test/case/localstore/cache_segment_test.cc +++ /dev/null @@ -1,66 +0,0 @@ -/** - * MIT License - * - * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * */ -#include -#include -#include -#include "cache/cache_segment.h" - -class UCCacheSegmentTest : public testing::Test {}; - -TEST_F(UCCacheSegmentTest, SetupOnSharedMemory) -{ - using T = size_t; - constexpr T data = 100; - constexpr size_t ioSize = sizeof(T); - constexpr size_t index = 15; - constexpr size_t ioNumber = index + 1; - constexpr size_t totalSize = ioSize * ioNumber; - MOCKER(UC::CacheSegment::TotalSize).stubs().will(returnValue(totalSize)); - auto seg1 = new (std::nothrow) UC::CacheSegment(); - auto seg2 = new (std::nothrow) UC::CacheSegment(); - ASSERT_NE(seg1, nullptr); - ASSERT_NE(seg2, nullptr); - ASSERT_TRUE(seg1->Setup(10, ioSize).Success()); - ASSERT_TRUE(seg2->Setup(10, ioSize).Success()); - ASSERT_NE(*(T*)seg2->At(index), data); - *(T*)(seg1->At(index)) = data; - delete seg1; - ASSERT_EQ(*(T*)seg2->At(index), data); - delete seg2; - GlobalMockObject::verify(); -} - -TEST_F(UCCacheSegmentTest, SetupWhileShmOpenFailed) -{ - using T = size_t; - constexpr size_t ioSize = sizeof(T); - constexpr size_t index = 15; - constexpr size_t ioNumber = index + 1; - constexpr size_t totalSize = ioSize * ioNumber; - MOCKER(UC::CacheSegment::TotalSize).stubs().will(returnValue(totalSize)); - MOCKER(shm_open).stubs().will(returnValue(-1)); - UC::CacheSegment seg; - ASSERT_TRUE(seg.Setup(0, ioSize).Failure()); - GlobalMockObject::verify(); -} diff --git a/ucm/store/test/case/nfsstore/hotness_test.cc b/ucm/store/test/case/nfsstore/hotness_test.cc deleted file mode 100644 index 21cc83cdd..000000000 --- a/ucm/store/test/case/nfsstore/hotness_test.cc +++ /dev/null @@ -1,54 +0,0 @@ -/** - * MIT License - * - * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * */ - -#include -#include -#include "hotness/hotness_set.h" -#include "hotness/hotness_timer.h" -#include "cmn/path_base.h" -#include "file/file.h" -#include "space/space_manager.h" - -class UCHotnessTest : public UC::PathBase {}; - -TEST_F(UCHotnessTest, UpdateHotness) -{ - UC::SpaceManager mgr; - ASSERT_EQ(mgr.Setup({this->Path()}, 1024 * 1024, false), UC::Status::OK()); - - std::string block1 = "a1b2c3d4e5f6789012345678901234ab"; - ASSERT_EQ(mgr.NewBlock(block1), UC::Status::OK()); - ASSERT_EQ(mgr.CommitBlock(block1), UC::Status::OK()); - - UC::HotnessSet hotness_set; - hotness_set.Insert(block1); - auto space_layout = mgr.GetSpaceLayout(); - auto path = space_layout->DataFilePath(block1, false); - auto currentTime = std::filesystem::last_write_time(path); - std::filesystem::last_write_time(path, currentTime - std::chrono::seconds(2)); - auto lastTime = std::filesystem::last_write_time(path); - hotness_set.UpdateHotness(space_layout); - auto newTime = std::filesystem::last_write_time(path); - ASSERT_GT(newTime, lastTime); -} \ No newline at end of file diff --git a/ucm/store/test/case/nfsstore/space_manager_test.cc b/ucm/store/test/case/nfsstore/space_manager_test.cc deleted file mode 100644 index 958f6464a..000000000 --- a/ucm/store/test/case/nfsstore/space_manager_test.cc +++ /dev/null @@ -1,125 +0,0 @@ -/** - * MIT License - * - * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * */ -#include -#include "cmn/path_base.h" -#include "space/space_manager.h" -#include "file/file.h" - -class UCSpaceManagerTest : public UC::PathBase {}; - -TEST_F(UCSpaceManagerTest, NewBlockTwice) -{ - UC::SpaceManager spaceMgr; - ASSERT_EQ(spaceMgr.Setup({this->Path()}, 1024 * 1024, false), UC::Status::OK()); - const std::string block1 = "block1"; - ASSERT_FALSE(spaceMgr.LookupBlock(block1)); - ASSERT_EQ(spaceMgr.NewBlock(block1), UC::Status::OK()); - ASSERT_FALSE(spaceMgr.LookupBlock(block1)); - ASSERT_EQ(spaceMgr.NewBlock(block1), UC::Status::DuplicateKey()); - ASSERT_EQ(spaceMgr.CommitBlock(block1), UC::Status::OK()); - ASSERT_TRUE(spaceMgr.LookupBlock(block1)); - ASSERT_EQ(spaceMgr.NewBlock(block1), UC::Status::DuplicateKey()); -} - -TEST_F(UCSpaceManagerTest, NewBlockTwiceWithTempDir) -{ - UC::SpaceManager spaceMgr; - ASSERT_EQ(spaceMgr.Setup({this->Path()}, 1024 * 1024, true), UC::Status::OK()); - const std::string block1 = "block1"; - ASSERT_FALSE(spaceMgr.LookupBlock(block1)); - ASSERT_EQ(spaceMgr.NewBlock(block1), UC::Status::OK()); - ASSERT_FALSE(spaceMgr.LookupBlock(block1)); - ASSERT_EQ(spaceMgr.NewBlock(block1), UC::Status::DuplicateKey()); - ASSERT_EQ(spaceMgr.CommitBlock(block1), UC::Status::OK()); - ASSERT_TRUE(spaceMgr.LookupBlock(block1)); - ASSERT_EQ(spaceMgr.NewBlock(block1), UC::Status::DuplicateKey()); -} - -TEST_F(UCSpaceManagerTest, CreateBlockWhenNoSpace) -{ - UC::SpaceManager spaceMgr; - size_t blockSize = 1024 * 1024; - size_t capacity = blockSize; - ASSERT_EQ(spaceMgr.Setup({this->Path()}, blockSize, false, capacity), UC::Status::OK()); - ASSERT_EQ(spaceMgr.NewBlock("block3"), UC::Status::OK()); - ASSERT_EQ(spaceMgr.NewBlock("block4"), UC::Status::NoSpace()); -} - -TEST_F(UCSpaceManagerTest, IterAllBlockFile) -{ - constexpr size_t blockSize = 1024 * 1024; - constexpr size_t capacity = blockSize * 1024; - UC::SpaceManager spaceMgr; - ASSERT_EQ(spaceMgr.Setup({this->Path()}, blockSize, false, capacity), UC::Status::OK()); - const std::string block1 = "a1b2c3d4e5f6789012345678901234ab"; - const std::string block2 = "a2b2c3d4e5f6789012345678901234ab"; - const std::string block3 = "a3b2c3d4e5f6789012345678901234ab"; - ASSERT_EQ(spaceMgr.NewBlock(block1), UC::Status::OK()); - ASSERT_EQ(spaceMgr.NewBlock(block2), UC::Status::OK()); - ASSERT_EQ(spaceMgr.NewBlock(block3), UC::Status::OK()); - auto layout = spaceMgr.GetSpaceLayout(); - auto iter = layout->CreateFilePathIterator(); - size_t count = 0; - while (!layout->NextDataFilePath(iter).empty()) { count++; } - ASSERT_EQ(count, 0); - ASSERT_EQ(spaceMgr.CommitBlock(block1), UC::Status::OK()); - ASSERT_EQ(spaceMgr.CommitBlock(block2), UC::Status::OK()); - ASSERT_EQ(spaceMgr.CommitBlock(block3), UC::Status::OK()); - iter = layout->CreateFilePathIterator(); - count = 0; - while (!layout->NextDataFilePath(iter).empty()) { count++; } - ASSERT_EQ(count, 3); -} - -TEST_F(UCSpaceManagerTest, NewBlockReuseIfActiveAccessedLongAgo) -{ - UC::SpaceManager spaceMgr; - constexpr size_t blockSize = 1024 * 1024; - constexpr size_t capacity = blockSize * 1024; - ASSERT_EQ(spaceMgr.Setup({this->Path()}, blockSize, false, capacity), UC::Status::OK()); - const auto* layout = spaceMgr.GetSpaceLayout(); - ASSERT_NE(layout, nullptr); - - const std::string block1 = "a1b2c3d4e5f6789012345678901234ab"; - auto parent = UC::File::Make(layout->DataFileParent(block1, /*activated=*/true)); - ASSERT_NE(parent, nullptr); - ASSERT_EQ(parent->MkDir(), UC::Status::OK()); - - const auto activePath = layout->DataFilePath(block1, /*activated=*/true); - auto activeFile = UC::File::Make(activePath); - ASSERT_NE(activeFile, nullptr); - ASSERT_EQ(activeFile->Open(UC::IFile::OpenFlag::CREATE | UC::IFile::OpenFlag::READ_WRITE), UC::Status::OK()); - activeFile->Close(); - - // NewBlock should return DuplicateKey because the file is recent - ASSERT_EQ(spaceMgr.NewBlock(block1), UC::Status::DuplicateKey()); - - // Set atime to 10 minutes ago so it is not considered recent - struct utimbuf newTime; - auto tp = time(nullptr) - 600; - newTime.modtime = tp; - newTime.actime = tp; - utime(activePath.c_str(), &newTime); - ASSERT_EQ(spaceMgr.NewBlock(block1), UC::Status::OK()); -} \ No newline at end of file diff --git a/ucm/store/test/case/nfsstore/space_property_test.cc b/ucm/store/test/case/nfsstore/space_property_test.cc deleted file mode 100644 index 5b5beb619..000000000 --- a/ucm/store/test/case/nfsstore/space_property_test.cc +++ /dev/null @@ -1,61 +0,0 @@ -/** - * MIT License - * - * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * */ -#include "cmn/path_base.h" -#include "space/space_property.h" -#include "space/space_layout.h" -#include "space/space_shard_layout.h" -#include "space/space_manager.h" - -class UCSpacePropertyTest : public UC::PathBase {}; - -/* -* check the persistence of property -*/ -TEST_F(UCSpacePropertyTest, CapacityPersistence) -{ - size_t blocksize = 1024 * 1024; - UC::SpaceManager spaceMgr; - ASSERT_EQ(spaceMgr.Setup({this->Path()}, blocksize, false, blocksize * 5), UC::Status::OK()); - const UC::SpaceLayout* layout = spaceMgr.GetSpaceLayout(); - - const std::string path = layout->ClusterPropertyFilePath(); - - UC::SpaceProperty spaceProperty; - ASSERT_EQ(spaceProperty.Setup(path), UC::Status::OK()); - ASSERT_EQ(spaceProperty.GetCapacity(), 0); - - spaceProperty.IncreaseCapacity(blocksize * 2); - ASSERT_EQ(spaceProperty.GetCapacity(), blocksize*2); - - UC::SpaceProperty spaceProperty2; - ASSERT_EQ(spaceProperty2.Setup(path), UC::Status::OK()); - ASSERT_EQ(spaceProperty2.GetCapacity(), blocksize*2); - - spaceProperty2.DecreaseCapacity(blocksize); - ASSERT_EQ(spaceProperty2.GetCapacity(), blocksize); - - UC::SpaceProperty spaceProperty3; - ASSERT_EQ(spaceProperty3.Setup(path), UC::Status::OK()); - ASSERT_EQ(spaceProperty3.GetCapacity(), blocksize); - } \ No newline at end of file diff --git a/ucm/store/test/case/nfsstore/space_recycle_test.cc b/ucm/store/test/case/nfsstore/space_recycle_test.cc deleted file mode 100644 index a9fb862f7..000000000 --- a/ucm/store/test/case/nfsstore/space_recycle_test.cc +++ /dev/null @@ -1,124 +0,0 @@ -/** - * MIT License - * - * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * */ - -#include -#include "cmn/path_base.h" -#include "file/file.h" -#include "space/space_recycle.h" -#include "space/space_manager.h" -#include "thread/latch.h" - -namespace UC { - -void DoRecycle(const SpaceLayout* layout, const uint32_t recycleNum, - SpaceRecycle::RecycleOneBlockDone done); -} - -class UCSpaceRecycleTest : public UC::PathBase { -protected: - using OpenFlag = UC::IFile::OpenFlag; - using AccessMode = UC::IFile::AccessMode; - - void NewBlock(const UC::SpaceLayout* layout, const std::string& id) - { - std::string parent = layout->DataFileParent(id, false); - UC::File::MkDir(parent); - std::string path = layout->DataFilePath(id, false); - auto f = UC::File::Make(path); - f->Open(OpenFlag::CREATE | OpenFlag::READ_WRITE); - } - - bool ExistBlock(const UC::SpaceLayout* layout, const std::string& id) - { - std::string path = layout->DataFilePath(id, false); - return UC::File::Access(path, AccessMode::EXIST).Success(); - } - - void UpdateBlock(const UC::SpaceLayout* layout, const std::string& id) - { - struct utimbuf newTime; - auto tp = time(nullptr) + 3600; - newTime.modtime = tp; - newTime.actime = tp; - std::string path = layout->DataFilePath(id, false); - utime(path.c_str(), &newTime); - } -}; - -TEST_F(UCSpaceRecycleTest, TriggerRecycle) -{ - size_t blocksize = 1024 * 1024; - UC::SpaceManager spaceMgr; - ASSERT_EQ(spaceMgr.Setup({this->Path()}, blocksize, false, blocksize * 5), UC::Status::OK()); - const UC::SpaceLayout* layout = spaceMgr.GetSpaceLayout(); - std::string block1 = "a1b2c3d4e5f6789012345678901234ab"; - NewBlock(layout, block1); - ASSERT_TRUE(ExistBlock(layout, block1)); - - std::string block2 = "a2b2c3d4e5f6789012345678901234ab"; - NewBlock(layout, block2); - ASSERT_TRUE(ExistBlock(layout, block2)); - - UpdateBlock(layout, block1); - UC::SpaceRecycle recycle; - UC::Latch waiter{1}; - - ASSERT_TRUE(recycle.Setup(layout, 10, [&waiter] { waiter.Done([]{}); }).Success()); - recycle.Trigger(); - waiter.Wait(); - EXPECT_TRUE(ExistBlock(layout, block1)); - EXPECT_FALSE(ExistBlock(layout, block2)); -} - -TEST_F(UCSpaceRecycleTest, DoRecycle) -{ - size_t blocksize = 1024 * 1024; - UC::SpaceManager spaceMgr; - ASSERT_EQ(spaceMgr.Setup({this->Path()}, blocksize, false, blocksize * 5), UC::Status::OK()); - const UC::SpaceLayout* layout = spaceMgr.GetSpaceLayout(); - std::string recycleBlocks[] = { - "a1b2c3d4e5f6789012345678901234ab", - "a2b2c3d4e5f6789012345678901234ab", - "a3b2c3d4e5f6789012345678901234ab" - }; - std::string remainBlocks[] = { - "b1b2c3d4e5f6789012345678901234ab", - "b2b2c3d4e5f6789012345678901234ab", - "b3b2c3d4e5f6789012345678901234ab" - }; - for (auto &id: remainBlocks) - { - NewBlock(layout, id); - ASSERT_TRUE(ExistBlock(layout, id)); - } - for (auto &id: recycleBlocks) - { - NewBlock(layout, id); - ASSERT_TRUE(ExistBlock(layout, id)); - } - for (auto &id: remainBlocks) { UpdateBlock(layout, id); } - UC::DoRecycle(layout, 3, nullptr); - for (auto &id: remainBlocks) { EXPECT_TRUE(ExistBlock(layout, id)); } - for (auto &id: recycleBlocks) { EXPECT_FALSE(ExistBlock(layout, id)); } -} \ No newline at end of file diff --git a/ucm/store/test/e2e/pcstore_embed.py b/ucm/store/test/e2e/pcstore_embed.py index da3e9de86..3681ae2af 100644 --- a/ucm/store/test/e2e/pcstore_embed.py +++ b/ucm/store/test/e2e/pcstore_embed.py @@ -40,6 +40,7 @@ def setup_store(storage_backends, block_size, device_id, io_size) -> UcmKVStoreB config["role"] = "worker" config["device"] = device_id config["io_size"] = io_size + config["unique_id"] = secrets.token_hex(8) return UcmPcStore(config) diff --git a/ucm/store/test/e2e/dramstore_embed_and_fetch.py b/ucm/store/test/e2e/pcstore_embed_v1.py similarity index 54% rename from ucm/store/test/e2e/dramstore_embed_and_fetch.py rename to ucm/store/test/e2e/pcstore_embed_v1.py index 4f9acda19..68e5ffdc2 100644 --- a/ucm/store/test/e2e/dramstore_embed_and_fetch.py +++ b/ucm/store/test/e2e/pcstore_embed_v1.py @@ -24,30 +24,30 @@ # import os import secrets +import time from typing import List import torch -from ucm.store.dramstore.dramstore_connector import UcmDramStore -from ucm.store.ucmstore import UcmKVStoreBase +from ucm.store.pcstore.pcstore_connector_v1 import UcmPcStoreV1 +from ucm.store.ucmstore_v1 import UcmKVStoreBaseV1 -def setup_store( - capacity, block_size, stream_number, device_id, timeout_ms -) -> UcmKVStoreBase: +def setup_store(storage_backends, block_size, device_id, io_size) -> UcmKVStoreBaseV1: config = {} - config["capacity"] = capacity + config["storage_backends"] = storage_backends config["kv_block_size"] = block_size - config["stream_number"] = stream_number - config["device_id"] = device_id - config["timeout_ms"] = timeout_ms - return UcmDramStore(config) + config["role"] = "worker" + config["device"] = device_id + config["io_size"] = io_size + config["unique_id"] = secrets.token_hex(8) + return UcmPcStoreV1(config) def make_buffers( block_number, device_id, batch_size, block_dim, block_len, block_layer ): - hashes = [secrets.token_hex(16) for _ in range(block_number)] + hashes = [secrets.token_bytes(16) for _ in range(block_number)] tensors = [ [ torch.rand( @@ -62,79 +62,62 @@ def make_buffers( return hashes, tensors -def embed(store: UcmKVStoreBase, hashes: List[str], tensors: List[List[torch.Tensor]]): - results = store.create(hashes) - assert sum(results) == 0 - block_ids = [] - offsets = [] - layers = [] - for hash_id, block in zip(hashes, tensors): - offset = 0 - for layer in block: - block_ids.append(hash_id) - offsets.append(offset) - layers.append(layer) - offset += layer.untyped_storage().size() - task = store.dump(block_ids, offsets, layers) +def embed( + store: UcmKVStoreBaseV1, hashes: List[bytes], tensors: List[List[torch.Tensor]] +): + task = store.dump(hashes, [], tensors) assert task.task_id > 0 - ret = store.wait(task) - assert ret == 0 - store.commit(hashes, True) + store.wait(task) -def fetch(store: UcmKVStoreBase, hashes: List[str], tensors: List[List[torch.Tensor]]): +def fetch( + store: UcmKVStoreBaseV1, hashes: List[bytes], tensors: List[List[torch.Tensor]] +): founds = store.lookup(hashes) for found in founds: assert found - block_ids = [] - offsets = [] - layers = [] - for hash_id, block in zip(hashes, tensors): - offset = 0 - for layer in block: - block_ids.append(hash_id) - offsets.append(offset) - layers.append(layer) - offset += layer.untyped_storage().size() - task = store.load(block_ids, offsets, layers) + task = store.load(hashes, [], tensors) assert task.task_id > 0 - ret = store.wait(task) - assert ret == 0 + store.wait(task) + + +def cmp_and_print_diff(a, b, rtol=0.0, atol=0.0): + for r, (row_a, row_b) in enumerate(zip(a, b)): + for c, (ta, tb) in enumerate(zip(row_a, row_b)): + if not torch.allclose(ta, tb, rtol=rtol, atol=atol): + mask = ~torch.isclose(ta, tb, rtol=rtol, atol=atol) + diff_a = ta[mask].cpu() + diff_b = tb[mask].cpu() + print(f"DIFF at [{r}][{c}] total {mask.sum().item()} element(s)") + print(" a val:", diff_a.flatten()) + print(" b val:", diff_b.flatten()) + assert False def main(): + storage_backends = "." block_number = 4096 device_id = 1 block_dim = 576 - block_len = 128 + block_len = 64 block_elem_size = 2 block_layer = 61 io_size = block_dim * block_len * block_elem_size block_size = io_size * block_layer - batch_size = 256 - stream_number = 10 - timeout_ms = 1000000 - capacity = block_number * block_size * 2 - batch_number = 64 - - store = setup_store(capacity, block_size, stream_number, device_id, timeout_ms) + batch_size = 64 + store = setup_store(storage_backends, block_size, device_id, io_size) hashes, tensors = make_buffers( block_number, device_id, batch_size, block_dim, block_len, block_layer ) total_batches = (block_number + batch_size - 1) // batch_size - for batch in range(total_batches): start = batch_size * batch end = min(start + batch_size, block_number) + tensors2 = [[torch.empty_like(t) for t in row] for row in tensors] embed(store, hashes[start:end], tensors) - - _, new_tensors = make_buffers( - block_number, device_id, batch_size, block_dim, block_len, block_layer - ) - for batch in range(total_batches): - start = batch_size * batch - end = start + batch_size - fetch(store, hashes[start:end], new_tensors) + time.sleep(1) + fetch(store, hashes[start:end], tensors2) + cmp_and_print_diff(tensors, tensors2) if __name__ == "__main__": diff --git a/ucm/store/test/e2e/pcstore_fetch.py b/ucm/store/test/e2e/pcstore_fetch.py index 6299d387d..0b3cf1d2e 100644 --- a/ucm/store/test/e2e/pcstore_fetch.py +++ b/ucm/store/test/e2e/pcstore_fetch.py @@ -24,6 +24,7 @@ # import os import random +import secrets from typing import List import torch @@ -39,6 +40,7 @@ def setup_store(storage_backends, block_size, device_id, io_size) -> UcmKVStoreB config["role"] = "worker" config["device"] = device_id config["io_size"] = io_size + config["unique_id"] = secrets.token_hex(8) return UcmPcStore(config) diff --git a/ucm/store/ucmstore_v1.h b/ucm/store/ucmstore_v1.h new file mode 100644 index 000000000..0f6324c60 --- /dev/null +++ b/ucm/store/ucmstore_v1.h @@ -0,0 +1,122 @@ +/** + * MIT License + * + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * */ +#ifndef UNIFIEDCACHE_STORE_V1_H +#define UNIFIEDCACHE_STORE_V1_H + +#include "status/status.h" +#include "type/types.h" + +namespace UC { + +/** + * @brief Abstract interface for a key-value store that supports + * asynchronous load/dump of cached blocks. + * + * Thread safety: All public methods must be thread-safe. Concurrent calls + * are allowed; implementations are responsible for internal synchronization. + */ +class StoreV1 { +public: + virtual ~StoreV1() = default; + + /** + * @brief Check whether the given blocks exist in storage. + * + * @param blocks Array of block identifiers to test. + * @param num Number of block identifiers to test. + * @return Expected> + * - On success: a vector whose i-th element is **true** if blocks[i] + * is present, otherwise **false**. + * - On failure: appropriate Status code. + */ + virtual Expected> Lookup(const Detail::BlockId* blocks, size_t num) = 0; + + /** + * @brief Hint the store to prefetch given blocks into high-speed cache. + * + * This call is **non-blocking** and **fire-and-forget**; it returns + * immediately and carries no completion guarantee. Implementations may + * ignore the hint if prefetching is not supported or resources are + * unavailable. + * + * @param blocks Array of block identifiers to be prefetched. + * @param num Number of block identifiers to be prefetched. + * + * @note Thread-safe; may be called concurrently with other operations. + * @note Default implementation does nothing. + */ + virtual void Prefetch(const Detail::BlockId* blocks, size_t num) = 0; + + /** + * @brief Start an asynchronous load (storage → device) transfer. + * + * @param task Description of shards to be loaded. + * @return Expected + * - On success: a task handle that can be passed to Wait() or Check(). + * - On failure: relevant Status code. + */ + virtual Expected Load(Detail::TaskDesc task) = 0; + + /** + * @brief Start an asynchronous dump (device → storage) transfer. + * + * @param task Description of shards to be stored. + * @return Expected + * - On success: a task handle that can be passed to Wait() or Check(). + * - On failure: relevant Status code. + */ + virtual Expected Dump(Detail::TaskDesc task) = 0; + + /** + * @brief Poll for task completion without blocking. + * + * @param taskId Task handle returned by Load() or Dump(). + * @return Expected + * - **true** if the task has finished (successfully or with an error). + * - **false** if the task is still running. + * - Any other value indicates an error in the poll itself. + */ + virtual Expected Check(Detail::TaskHandle taskId) = 0; + + /** + * @brief Block until the specified task completes. + * + * @param taskId Task handle returned by Load() or Dump(). + * @return Status::OK on successful completion, otherwise an error code + * describing the failure. + */ + virtual Status Wait(Detail::TaskHandle taskId) = 0; + +protected: + /** + * @brief Protected default constructor. + * + * Prevents direct instantiation and enforces derivation. + */ + StoreV1() = default; +}; + +} // namespace UC + +#endif diff --git a/ucm/store/ucmstore_v1.py b/ucm/store/ucmstore_v1.py new file mode 100644 index 000000000..19438781a --- /dev/null +++ b/ucm/store/ucmstore_v1.py @@ -0,0 +1,187 @@ +# -*- coding: utf-8 -*- +# +# MIT License +# +# Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +from abc import ABC, abstractmethod +from typing import Dict, List + +import torch + + +class Task(ABC): + """Asynchronous task handle returned by transfer operations. + + This is an opaque token that can be polled or awaited. + """ + + pass + + +class UcmKVStoreBaseV1(ABC): + """Abstract base class for KV-cache-centric storage backends. + + A concrete storage vendor must implement this interface to participate in + the unified-cache-management (UCM) system. + """ + + def __init__(self, config: Dict[str, object]) -> None: + """Initialize the store with vendor-specific configuration. + + Args: + config: Key-value mapping containing vendor-specific parameters + (e.g., connection string, cache size, compression level). + """ + self.config = config + + @abstractmethod + def cc_store(self) -> int: + """Return a low-level C/C++ pointer to the underlying store. + + Returns: + An opaque ``int`` representing the ``Store*`` instance that can + be passed to native code. + """ + pass + + @abstractmethod + def lookup(self, block_ids: List[bytes]) -> List[bool]: + """Check presence of blocks in external storage. + + Args: + block_ids: List of vLLM block hashes (raw bytes). + + Returns: + A list of booleans, ``True`` if the corresponding block exists in + storage, ``False`` otherwise. The order matches ``block_ids``. + """ + pass + + @abstractmethod + def prefetch(self, block_ids: List[bytes]) -> None: + """Asynchronously prefetch blocks into high-speed cache. + + Args: + block_ids: List of vLLM block hashes to prefetch. + """ + pass + + @abstractmethod + def load( + self, + block_ids: List[bytes], + shard_index: List[int], + dst_tensor: List[List[torch.Tensor]], + ) -> Task: + """Initiate transfer of KV cache from storage to device. + + Args: + block_ids: Hashes of the blocks to load. + shard_index: Shard index for each block. + dst_tensor: Double-list structure where ``dst_tensor[i][j]`` is the + destination PyTorch tensor on device for block ``i``, tensor ``j``. + + Returns: + A ``Task`` handle that can be used to check or wait for completion. + """ + pass + + @abstractmethod + def dump( + self, + block_ids: List[bytes], + shard_index: List[int], + src_tensor: List[List[torch.Tensor]], + ) -> Task: + """Initiate transfer of KV cache from device to storage. + + Args: + block_ids: Hashes of the blocks to write. + shard_index: Shard index for each block. + src_tensor: Double-list structure where ``src_tensor[i][j]`` is the + source PyTorch tensor on device for block ``i``, tensor ``j``. + + Returns: + A ``Task`` handle that can be used to check or wait for completion. + """ + pass + + @abstractmethod + def load_data( + self, + block_ids: List[bytes], + shard_index: List[int], + dst_addr: List[List[int]], + ) -> Task: + """Low-level fetch: copy KV data to device pointers. + + Args: + block_ids: Block hashes to load. + shard_index: Shard index for each block. + dst_addr: Double-list of ``int`` pointers (as Python ``int``) to + pre-allocated device buffers. + + Returns: + A ``Task`` handle for the asynchronous copy. + """ + pass + + @abstractmethod + def dump_data( + self, + block_ids: List[bytes], + shard_index: List[int], + src_addr: List[List[int]], + ) -> Task: + """Low-level dump: copy KV data from device pointers. + + Args: + block_ids: Block hashes to store. + shard_index: Shard index for each block. + src_addr: Double-list of ``int`` pointers to device buffers. + + Returns: + A ``Task`` handle for the asynchronous copy. + """ + pass + + @abstractmethod + def wait(self, task: Task) -> None: + """Block until the given transfer task completes. + + Args: + task: Task handle returned by ``load``, ``dump``, ``load_data``, + or ``dump_data``. + """ + pass + + @abstractmethod + def check(self, task: Task) -> bool: + """Non-blocking poll for task completion. + + Args: + task: Task handle returned by any transfer method. + + Returns: + ``True`` if the task has finished, ``False`` if still in-flight. + """ + pass