Skip to content

[flutter_tools] Retry and handle ERROR_DIR_NOT_EMPTY on Windows - #193620

Open
bkonyi wants to merge 3 commits into
flutter:masterfrom
bkonyi:issue-193358
Open

bkonyi wants to merge 3 commits into
flutter:masterfrom
bkonyi:issue-193358

Conversation

@bkonyi

@bkonyi bkonyi commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

Treat Windows error code 145 (ERROR_DIR_NOT_EMPTY) as a transient lock during recursive directory deletions in ErrorHandlingFileSystem.

On Windows NTFS, recursive directory deletion (such as cleaning up .plugin_symlinks during flutter pub get or createPluginSymlinks) requires deleting all child entries before removing the parent directory. When a child file is deleted while held open with FILE_SHARE_DELETE by background software (such as an antivirus scanner or search indexer), it enters a delete-pending state and is not immediately unlinked. Attempting to remove the parent directory before the child handle closes fails with Win32 error 145 (ERROR_DIR_NOT_EMPTY).

This change:

  • Adds kSystemCodeDirNotEmpty = 145 to _isWindowsTransientLock so recursive directory deletions automatically retry up to 5 times with exponential backoff (50ms, 100ms, 200ms, 400ms, 800ms).
  • Adds if (!recursive) kSystemCodeDirNotEmpty to ignoreErrorCodes in ErrorHandlingDirectory.delete and deleteSync so non-recursive deletions on non-empty directories continue to rethrow FileSystemException immediately without retrying.
  • Maps kDirNotEmpty in _handleWindowsException to a user-friendly ToolExit message if all retries are exhausted.

Fixes #193358

Pre-launch Checklist

bkonyi added 2 commits October 1, 2026 02:28
Treat Windows error code 145 (ERROR_DIR_NOT_EMPTY) as a transient lock
in ErrorHandlingFileSystem so recursive directory deletions (such as
.plugin_symlinks during tooling setup or pub get) retry with exponential
backoff when a child file is held in a delete-pending state or locked by
background processes. If retries are exhausted, map it to a user-friendly
ToolExit rather than crashing the tool.

Fixes flutter#193358
… deletes

- In ErrorHandlingDirectory.delete and deleteSync, add kSystemCodeDirNotEmpty
  to ignoreErrorCodes when recursive is false, preserving raw FileSystemException
  behavior for non-recursive directory deletions.
- Assert attemptsLeft == 0 in retry-exhaustion test to verify full retry count.
- Add regression test ensuring non-recursive directory deletions do not retry.
- Use kSystemCodeDirNotEmpty named constant in all tests.
- Maintain numeric ordering for error code constants.
@bkonyi bkonyi added the CICD Run CI/CD label Oct 1, 2026
@github-actions github-actions Bot added the tool Affects the "flutter" command-line tool. See also t: labels. label Oct 1, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces handling for the Windows error code 145 ('ERROR_DIR_NOT_EMPTY') during directory deletion in 'ErrorHandlingDirectory'. The error is treated as a transient lock, enabling retry attempts during recursive deletions, and is mapped to an error message indicating that the file is being used by another program. Additionally, unit tests are added to verify synchronous and asynchronous retry behavior, failure after maximum attempts, and that non-recursive deletions do not trigger retries. No review comments were provided, and there is no additional feedback.

Extract `createFileSystemWithTransientErrors` helper in `error_handling_io_test.dart` to eliminate duplicated `MemoryFileSystem.test` setup across transient lock retry tests.
@bkonyi
bkonyi requested a review from a team October 2, 2026 16:42

@mattkae mattkae left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A question, but looks good overall

'\n$e\n'
'Free up space and try again.',
kSharingViolation || kLockViolation || kUserMappedSectionOpened =>
kSharingViolation || kLockViolation || kUserMappedSectionOpened || kDirNotEmpty =>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should kDirNotEmpty give a different error message, maybe saying that the directory isn't empty so we couldn't make the deletion?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CICD Run CI/CD tool Affects the "flutter" command-line tool. See also t: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[tool_crash] FileSystemException: Deletion failed, OS Error: The directory is not empty, errno = 145

2 participants