Robustly remove a directory tree, handling race conditions and concurrent access. Args: path: Path to the directory to remove max_retries: Maximum number of retry attempts delay: Delay between retries in seconds Returns: True if removal was successful,
(path: Path, max_retries: int, delay: float)
| 159 | |
| 160 | |
| 161 | def robust_rmtree(path: Path, max_retries: int, delay: float) -> bool: |
| 162 | """ |
| 163 | Robustly remove a directory tree, handling race conditions and concurrent access. |
| 164 | |
| 165 | Args: |
| 166 | path: Path to the directory to remove |
| 167 | max_retries: Maximum number of retry attempts |
| 168 | delay: Delay between retries in seconds |
| 169 | |
| 170 | Returns: |
| 171 | True if removal was successful, False otherwise |
| 172 | """ |
| 173 | if not path.exists(): |
| 174 | locked_print(f"Directory {path} doesn't exist, skipping removal") |
| 175 | return True |
| 176 | |
| 177 | for attempt in range(max_retries): |
| 178 | try: |
| 179 | locked_print( |
| 180 | f"Attempting to remove directory {path} (attempt {attempt + 1}/{max_retries})" |
| 181 | ) |
| 182 | shutil.rmtree(path, onerror=remove_readonly) |
| 183 | locked_print(f"Successfully removed directory {path}") |
| 184 | return True |
| 185 | except OSError as e: |
| 186 | if attempt == max_retries - 1: |
| 187 | locked_print( |
| 188 | f"Failed to remove directory {path} after {max_retries} attempts: {e}" |
| 189 | ) |
| 190 | return False |
| 191 | |
| 192 | # Log the specific error and retry |
| 193 | locked_print( |
| 194 | f"Failed to remove directory {path} on attempt {attempt + 1}: {e}" |
| 195 | ) |
| 196 | |
| 197 | # Check if another process removed it |
| 198 | if not path.exists(): |
| 199 | locked_print(f"Directory {path} was removed by another process") |
| 200 | return True |
| 201 | |
| 202 | # Wait before retrying |
| 203 | time.sleep(delay * (2**attempt)) # Exponential backoff |
| 204 | |
| 205 | except KeyboardInterrupt as ki: |
| 206 | handle_keyboard_interrupt(ki) |
| 207 | raise |
| 208 | except Exception as e: |
| 209 | locked_print(f"Unexpected error removing directory {path}: {e}") |
| 210 | return False |
| 211 | |
| 212 | return False |
| 213 | |
| 214 | |
| 215 | def safe_file_removal(file_path: Path, max_retries: int) -> bool: |
no test coverage detected