mirror of
https://github.com/wizarrrr/wizarr.git
synced 2026-04-19 05:28:33 -04:00
119 lines
4.6 KiB
Python
119 lines
4.6 KiB
Python
"""
|
|
Robust session cache wrapper that handles stale file handle errors gracefully.
|
|
"""
|
|
|
|
import logging
|
|
from pathlib import Path
|
|
|
|
from cachelib.file import FileSystemCache
|
|
|
|
|
|
class RobustFileSystemCache(FileSystemCache):
|
|
"""
|
|
A FileSystemCache wrapper that handles OSError errno 116 (stale file handle)
|
|
by recreating the cache directory and continuing operation.
|
|
"""
|
|
|
|
def __init__(self, cache_dir, **kwargs):
|
|
self.cache_dir = cache_dir
|
|
super().__init__(cache_dir, **kwargs)
|
|
self._cleanup_stale_files()
|
|
|
|
def _handle_stale_file_error(
|
|
self, operation_name: str, filename: str, error: OSError
|
|
):
|
|
"""Handle stale file handle errors by logging and attempting recovery."""
|
|
if error.errno == 116: # Stale file handle
|
|
logging.warning(
|
|
f"Stale file handle in session cache during {operation_name}: {filename}. "
|
|
f"This typically happens after container restarts or network storage issues. "
|
|
f"Attempting to continue..."
|
|
)
|
|
try:
|
|
# Try to remove the problematic file
|
|
if Path(filename).exists():
|
|
Path(filename).unlink()
|
|
logging.info(f"Removed stale session file: {filename}")
|
|
except OSError as cleanup_error:
|
|
logging.warning(
|
|
f"Could not cleanup stale file {filename}: {cleanup_error}"
|
|
)
|
|
else:
|
|
# Re-raise non-stale file handle errors
|
|
raise error
|
|
|
|
def set(self, key, value, timeout=None, mgmt_element=False):
|
|
"""Set a cache value with stale file handle error recovery."""
|
|
try:
|
|
return super().set(key, value, timeout, mgmt_element=mgmt_element)
|
|
except OSError as e:
|
|
filename = self._get_filename(key)
|
|
self._handle_stale_file_error("set", filename, e)
|
|
# Retry once after cleanup
|
|
try:
|
|
return super().set(key, value, timeout, mgmt_element=mgmt_element)
|
|
except OSError as retry_error:
|
|
if retry_error.errno == 116:
|
|
logging.error(
|
|
f"Session cache set failed twice for {key}. "
|
|
f"Consider switching to a different session backend."
|
|
)
|
|
return False
|
|
raise retry_error
|
|
|
|
def get(self, key):
|
|
"""Get a cache value with stale file handle error recovery."""
|
|
try:
|
|
return super().get(key)
|
|
except OSError as e:
|
|
filename = self._get_filename(key)
|
|
self._handle_stale_file_error("get", filename, e)
|
|
# Return None for stale file handle errors (session will be recreated)
|
|
return None
|
|
|
|
def delete(self, key):
|
|
"""Delete a cache value with stale file handle error recovery."""
|
|
try:
|
|
return super().delete(key)
|
|
except OSError as e:
|
|
filename = self._get_filename(key)
|
|
self._handle_stale_file_error("delete", filename, e)
|
|
# Consider delete successful for stale files (they're gone anyway)
|
|
return True
|
|
|
|
def _cleanup_stale_files(self):
|
|
"""Clean up any stale session files at startup."""
|
|
try:
|
|
session_dir = Path(self.cache_dir)
|
|
if not session_dir.exists():
|
|
return
|
|
|
|
stale_files = []
|
|
for cache_file in session_dir.glob("*"):
|
|
if cache_file.is_file():
|
|
try:
|
|
# Try to access file stats to detect stale handles
|
|
cache_file.stat()
|
|
# Try to open/read the file briefly
|
|
with cache_file.open("rb") as f:
|
|
f.read(1)
|
|
except OSError as e:
|
|
if e.errno == 116: # Stale file handle
|
|
stale_files.append(cache_file)
|
|
|
|
if stale_files:
|
|
logging.info(
|
|
f"Found {len(stale_files)} stale session files at startup, cleaning up..."
|
|
)
|
|
for stale_file in stale_files:
|
|
try:
|
|
stale_file.unlink()
|
|
logging.debug(f"Removed stale session file: {stale_file}")
|
|
except OSError as cleanup_error:
|
|
logging.warning(
|
|
f"Could not cleanup stale session file {stale_file}: {cleanup_error}"
|
|
)
|
|
|
|
except Exception as e:
|
|
logging.warning(f"Session cache startup cleanup failed: {e}")
|