diff --git a/changelog.d/20231208_152516_andrey_workarround_cache_corrupted_data.md b/changelog.d/20231208_152516_andrey_workarround_cache_corrupted_data.md new file mode 100644 index 000000000000..a0adc4b29077 --- /dev/null +++ b/changelog.d/20231208_152516_andrey_workarround_cache_corrupted_data.md @@ -0,0 +1,4 @@ +### Fixed + +- Added workaround for corrupted cached chunks + () diff --git a/cvat/apps/engine/cache.py b/cvat/apps/engine/cache.py index 0c04c030149c..778a0f07f1c0 100644 --- a/cvat/apps/engine/cache.py +++ b/cvat/apps/engine/cache.py @@ -10,6 +10,7 @@ from io import BytesIO import shutil import tempfile +import zlib from typing import Optional, Tuple @@ -43,17 +44,31 @@ def __init__(self, dimension=DimensionType.DIM_2D): self._cache = caches['media'] def _get_or_set_cache_item(self, key, create_function): - slogger.glob.info(f'Starting to get chunk from cache: key {key}') - item = self._cache.get(key) - slogger.glob.info(f'Ending to get chunk from cache: key {key}, is_cached {bool(item)}') - if not item: + def create_item(): slogger.glob.info(f'Starting to prepare chunk: key {key}') item = create_function() slogger.glob.info(f'Ending to prepare chunk: key {key}') + if item[0]: + item = (item[0], item[1], zlib.crc32(item[0].getbuffer())) self._cache.set(key, item) - return item + return item + + slogger.glob.info(f'Starting to get chunk from cache: key {key}') + item = self._cache.get(key) + slogger.glob.info(f'Ending to get chunk from cache: key {key}, is_cached {bool(item)}') + if not item: + item = create_item() + else: + # compare checksum + item_data = item[0].getbuffer() if isinstance(item[0], io.BytesIO) else item[0] + item_checksum = item[2] if len(item) == 3 else None + if item_checksum != zlib.crc32(item_data): + slogger.glob.info(f'Recreating cache item {key} due to checksum mismatch') + item = create_item() + + return item[0], item[1] def get_task_chunk_data_with_mime(self, chunk_number, quality, db_data): item = self._get_or_set_cache_item( @@ -323,6 +338,6 @@ def _prepare_context_image(self, db_data, frame_number): if not success: raise Exception('Failed to encode image to ".jpeg" format') zip_file.writestr(f'{name}.jpg', result.tobytes()) - buff = zip_buffer.getvalue() mime_type = 'application/zip' - return buff, mime_type + zip_buffer.seek(0) + return zip_buffer, mime_type diff --git a/cvat/apps/engine/views.py b/cvat/apps/engine/views.py index 21b618f4694f..d39eec7793a0 100644 --- a/cvat/apps/engine/views.py +++ b/cvat/apps/engine/views.py @@ -3,7 +3,6 @@ # # SPDX-License-Identifier: MIT -import io import os import os.path as osp from PIL import Image @@ -679,7 +678,7 @@ def __call__(self, request, start: int, stop: int, db_data: Optional[Data]): buff, mime = cache.get_frame_context_images(db_data, self.number) if not buff: return HttpResponseNotFound() - return HttpResponse(io.BytesIO(buff), content_type=mime) + return HttpResponse(buff, content_type=mime) else: return Response(data='unknown data type {}.'.format(self.type), status=status.HTTP_400_BAD_REQUEST)