66from contextlib import suppress
77import logging
88from os import (
9+ close as os_close ,
910 fsync as os_fsync ,
1011 getenv as os_getenv ,
1112 getpid as os_getpid ,
1213 name as os_name ,
14+ open as os_open ,
15+ O_RDONLY as os_O_RDONLY ,
1316)
1417from os .path import expanduser as os_path_expand_user , join as os_path_join
1518from pathlib import Path
2730_LOGGER = logging .getLogger (__name__ )
2831
2932
33+ def _fsync_parent_dir (path : Path ) -> None :
34+ """Helper function to ensure persisted on POSIX."""
35+ fd = os_open (str (path ), os_O_RDONLY )
36+ try :
37+ os_fsync (fd )
38+ finally :
39+ os_close (fd )
40+
41+
3042class PlugwiseCache :
3143 """Base class to cache plugwise information."""
3244
@@ -98,15 +110,15 @@ async def write_cache(self, data: dict[str, str], rewrite: bool = False) -> None
98110 if not rewrite :
99111 current_data = await self .read_cache ()
100112
101- processed_keys : list [str ] = []
113+ processed_keys : set [str ] = set ()
102114 data_to_write : list [str ] = []
103115
104116 # Prepare data exactly as in original implementation
105117 for _cur_key , _cur_val in current_data .items ():
106118 _write_val = _cur_val
107119 if _cur_key in data :
108120 _write_val = data [_cur_key ]
109- processed_keys .append (_cur_key )
121+ processed_keys .add (_cur_key )
110122 data_to_write .append (f"{ _cur_key } { CACHE_KEY_SEPARATOR } { _write_val } \n " )
111123
112124 # Write remaining new data
@@ -129,16 +141,25 @@ async def write_cache(self, data: dict[str, str], rewrite: bool = False) -> None
129141 file = str (temp_path ),
130142 mode = "w" ,
131143 encoding = UTF8 ,
144+ newline = "\n " ,
132145 ) as temp_file :
133146 await temp_file .writelines (data_to_write )
134147 await temp_file .flush ()
135148 # Ensure data reaches disk before rename
136- loop = get_running_loop ()
137- await loop .run_in_executor (None , os_fsync , temp_file .fileno ())
149+ try :
150+ loop = get_running_loop ()
151+ await loop .run_in_executor (None , os_fsync , temp_file .fileno ())
152+ except (OSError , TypeError , AttributeError ):
153+ # If fsync fails due to fileno() issues or other problems,
154+ # continue without it. flush() provides reasonable durability.
155+ pass
138156
139157 # Atomic rename (overwrites atomically on all platforms)
140158 temp_path .replace (cache_file_path )
141159 temp_path = None # Successfully renamed
160+ if os_name != "nt" :
161+ # Ensure directory entry is persisted on POSIX
162+ await loop .run_in_executor (None , _fsync_parent_dir , cache_file_path .parent )
142163
143164 if not self ._cache_file_exists :
144165 self ._cache_file_exists = True
@@ -165,7 +186,7 @@ async def read_cache(self) -> dict[str, str]:
165186 """Return current data from cache file."""
166187 if not self ._initialized :
167188 raise CacheError (
168- f"Unable to save cache. Initialize cache file '{ self ._file_name } ' first."
189+ f"Unable to read cache. Initialize cache file '{ self ._file_name } ' first."
169190 )
170191 current_data : dict [str , str ] = {}
171192 if self ._cache_file is None :
0 commit comments