MyFSIO v0.3.5 Release #28
@@ -215,6 +215,8 @@ class ObjectStorage:
|
|||||||
self._stats_lock = threading.Lock()
|
self._stats_lock = threading.Lock()
|
||||||
self._stats_dirty: set[str] = set()
|
self._stats_dirty: set[str] = set()
|
||||||
self._stats_flush_timer: Optional[threading.Timer] = None
|
self._stats_flush_timer: Optional[threading.Timer] = None
|
||||||
|
self._etag_index_dirty: set[str] = set()
|
||||||
|
self._etag_index_flush_timer: Optional[threading.Timer] = None
|
||||||
|
|
||||||
def _get_bucket_lock(self, bucket_id: str) -> threading.Lock:
|
def _get_bucket_lock(self, bucket_id: str) -> threading.Lock:
|
||||||
with self._registry_lock:
|
with self._registry_lock:
|
||||||
@@ -422,6 +424,9 @@ class ObjectStorage:
|
|||||||
if self._stats_flush_timer is not None:
|
if self._stats_flush_timer is not None:
|
||||||
self._stats_flush_timer.cancel()
|
self._stats_flush_timer.cancel()
|
||||||
self._flush_stats()
|
self._flush_stats()
|
||||||
|
if self._etag_index_flush_timer is not None:
|
||||||
|
self._etag_index_flush_timer.cancel()
|
||||||
|
self._flush_etag_indexes()
|
||||||
|
|
||||||
def delete_bucket(self, bucket_name: str) -> None:
|
def delete_bucket(self, bucket_name: str) -> None:
|
||||||
bucket_path = self._bucket_path(bucket_name)
|
bucket_path = self._bucket_path(bucket_name)
|
||||||
@@ -451,6 +456,7 @@ class ObjectStorage:
|
|||||||
self._stats_mem.pop(bucket_id, None)
|
self._stats_mem.pop(bucket_id, None)
|
||||||
self._stats_serial.pop(bucket_id, None)
|
self._stats_serial.pop(bucket_id, None)
|
||||||
self._stats_dirty.discard(bucket_id)
|
self._stats_dirty.discard(bucket_id)
|
||||||
|
self._etag_index_dirty.discard(bucket_id)
|
||||||
|
|
||||||
def list_objects(
|
def list_objects(
|
||||||
self,
|
self,
|
||||||
@@ -2169,10 +2175,10 @@ class ObjectStorage:
|
|||||||
if now - timestamp < self._cache_ttl and current_stats_mtime == cached_stats_mtime:
|
if now - timestamp < self._cache_ttl and current_stats_mtime == cached_stats_mtime:
|
||||||
self._object_cache.move_to_end(bucket_id)
|
self._object_cache.move_to_end(bucket_id)
|
||||||
return objects
|
return objects
|
||||||
cache_version = self._cache_version.get(bucket_id, 0)
|
|
||||||
|
|
||||||
bucket_lock = self._get_bucket_lock(bucket_id)
|
bucket_lock = self._get_bucket_lock(bucket_id)
|
||||||
with bucket_lock:
|
with bucket_lock:
|
||||||
|
now = time.time()
|
||||||
current_stats_mtime = self._get_cache_marker_mtime(bucket_id)
|
current_stats_mtime = self._get_cache_marker_mtime(bucket_id)
|
||||||
with self._obj_cache_lock:
|
with self._obj_cache_lock:
|
||||||
cached = self._object_cache.get(bucket_id)
|
cached = self._object_cache.get(bucket_id)
|
||||||
@@ -2186,16 +2192,12 @@ class ObjectStorage:
|
|||||||
new_stats_mtime = self._get_cache_marker_mtime(bucket_id)
|
new_stats_mtime = self._get_cache_marker_mtime(bucket_id)
|
||||||
|
|
||||||
with self._obj_cache_lock:
|
with self._obj_cache_lock:
|
||||||
current_version = self._cache_version.get(bucket_id, 0)
|
|
||||||
if current_version != cache_version:
|
|
||||||
objects = self._build_object_cache(bucket_path)
|
|
||||||
new_stats_mtime = self._get_cache_marker_mtime(bucket_id)
|
|
||||||
while len(self._object_cache) >= self._object_cache_max_size:
|
while len(self._object_cache) >= self._object_cache_max_size:
|
||||||
self._object_cache.popitem(last=False)
|
self._object_cache.popitem(last=False)
|
||||||
|
|
||||||
self._object_cache[bucket_id] = (objects, time.time(), new_stats_mtime)
|
self._object_cache[bucket_id] = (objects, time.time(), new_stats_mtime)
|
||||||
self._object_cache.move_to_end(bucket_id)
|
self._object_cache.move_to_end(bucket_id)
|
||||||
self._cache_version[bucket_id] = current_version + 1
|
self._cache_version[bucket_id] = self._cache_version.get(bucket_id, 0) + 1
|
||||||
self._sorted_key_cache.pop(bucket_id, None)
|
self._sorted_key_cache.pop(bucket_id, None)
|
||||||
|
|
||||||
return objects
|
return objects
|
||||||
@@ -2205,6 +2207,7 @@ class ObjectStorage:
|
|||||||
self._object_cache.pop(bucket_id, None)
|
self._object_cache.pop(bucket_id, None)
|
||||||
self._cache_version[bucket_id] = self._cache_version.get(bucket_id, 0) + 1
|
self._cache_version[bucket_id] = self._cache_version.get(bucket_id, 0) + 1
|
||||||
|
|
||||||
|
self._etag_index_dirty.discard(bucket_id)
|
||||||
etag_index_path = self._system_bucket_root(bucket_id) / "etag_index.json"
|
etag_index_path = self._system_bucket_root(bucket_id) / "etag_index.json"
|
||||||
try:
|
try:
|
||||||
etag_index_path.unlink(missing_ok=True)
|
etag_index_path.unlink(missing_ok=True)
|
||||||
@@ -2226,23 +2229,32 @@ class ObjectStorage:
|
|||||||
self._cache_version[bucket_id] = self._cache_version.get(bucket_id, 0) + 1
|
self._cache_version[bucket_id] = self._cache_version.get(bucket_id, 0) + 1
|
||||||
self._sorted_key_cache.pop(bucket_id, None)
|
self._sorted_key_cache.pop(bucket_id, None)
|
||||||
|
|
||||||
self._update_etag_index(bucket_id, key, meta.etag if meta else None)
|
self._etag_index_dirty.add(bucket_id)
|
||||||
|
self._schedule_etag_index_flush()
|
||||||
|
|
||||||
def _update_etag_index(self, bucket_id: str, key: str, etag: Optional[str]) -> None:
|
def _schedule_etag_index_flush(self) -> None:
|
||||||
etag_index_path = self._system_bucket_root(bucket_id) / "etag_index.json"
|
if self._etag_index_flush_timer is None or not self._etag_index_flush_timer.is_alive():
|
||||||
if not etag_index_path.exists():
|
self._etag_index_flush_timer = threading.Timer(5.0, self._flush_etag_indexes)
|
||||||
return
|
self._etag_index_flush_timer.daemon = True
|
||||||
try:
|
self._etag_index_flush_timer.start()
|
||||||
with open(etag_index_path, 'r', encoding='utf-8') as f:
|
|
||||||
index = json.load(f)
|
def _flush_etag_indexes(self) -> None:
|
||||||
if etag is None:
|
dirty = set(self._etag_index_dirty)
|
||||||
index.pop(key, None)
|
self._etag_index_dirty.clear()
|
||||||
else:
|
for bucket_id in dirty:
|
||||||
index[key] = etag
|
with self._obj_cache_lock:
|
||||||
with open(etag_index_path, 'w', encoding='utf-8') as f:
|
cached = self._object_cache.get(bucket_id)
|
||||||
json.dump(index, f)
|
if not cached:
|
||||||
except (OSError, json.JSONDecodeError):
|
continue
|
||||||
pass
|
objects = cached[0]
|
||||||
|
index = {k: v.etag for k, v in objects.items() if v.etag}
|
||||||
|
etag_index_path = self._system_bucket_root(bucket_id) / "etag_index.json"
|
||||||
|
try:
|
||||||
|
etag_index_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
with open(etag_index_path, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(index, f)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
def warm_cache(self, bucket_names: Optional[List[str]] = None) -> None:
|
def warm_cache(self, bucket_names: Optional[List[str]] = None) -> None:
|
||||||
"""Pre-warm the object cache for specified buckets or all buckets.
|
"""Pre-warm the object cache for specified buckets or all buckets.
|
||||||
@@ -2299,12 +2311,7 @@ class ObjectStorage:
|
|||||||
if cached:
|
if cached:
|
||||||
config, cached_time, cached_mtime = cached
|
config, cached_time, cached_mtime = cached
|
||||||
if now - cached_time < self._bucket_config_cache_ttl:
|
if now - cached_time < self._bucket_config_cache_ttl:
|
||||||
try:
|
return config.copy()
|
||||||
current_mtime = config_path.stat().st_mtime if config_path.exists() else 0.0
|
|
||||||
except OSError:
|
|
||||||
current_mtime = 0.0
|
|
||||||
if current_mtime == cached_mtime:
|
|
||||||
return config.copy()
|
|
||||||
|
|
||||||
if not config_path.exists():
|
if not config_path.exists():
|
||||||
self._bucket_config_cache[bucket_name] = ({}, now, 0.0)
|
self._bucket_config_cache[bucket_name] = ({}, now, 0.0)
|
||||||
|
|||||||
Reference in New Issue
Block a user