From e94c184c7c4a18f3de569c97caeb83f4ff98a4b2 Mon Sep 17 00:00:00 2001 From: Bartosz Sosnowski Date: Thu, 6 Sep 2018 14:21:24 +0200 Subject: [PATCH] win,fs: retry if uv_fs_rename fails On Windows rename operation can fail randomly in presence of antivirus or indexing software. Make `uv_fs_rename` retry up to four times with 250ms delay between attempts before giving up. PR-URL: https://github.com/libuv/libuv/pull/1981 Reviewed-By: Sakthipriyan Vairamani Reviewed-By: Santiago Gimeno Reviewed-By: Colin Ihrig --- docs/src/fs.rst | 9 +++++++ src/win/fs.c | 72 +++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/docs/src/fs.rst b/docs/src/fs.rst index af97ec3a648..21f9e27cb40 100644 --- a/docs/src/fs.rst +++ b/docs/src/fs.rst @@ -233,6 +233,15 @@ API Equivalent to :man:`rename(2)`. + .. note:: + On Windows if this function fails with ``UV_EBUSY``, ``UV_EPERM`` or + ``UV_EACCES``, it will retry to rename the file up to four times with + 250ms wait between attempts before giving up. If both `path` and + `new_path` are existing directories this function will work only if + target directory is empty. + + .. versionchanged:: 1.24.0 Added retrying and directory move support on Windows. + .. c:function:: int uv_fs_fsync(uv_loop_t* loop, uv_fs_t* req, uv_file file, uv_fs_cb cb) Equivalent to :man:`fsync(2)`. diff --git a/src/win/fs.c b/src/win/fs.c index 812c1a6de58..7ad0d077a64 100644 --- a/src/win/fs.c +++ b/src/win/fs.c @@ -42,6 +42,8 @@ #define UV_FS_FREE_PTR 0x0008 #define UV_FS_CLEANEDUP 0x0010 +#define UV__RENAME_RETRIES 4 +#define UV__RENAME_WAIT 250 #define INIT(subtype) \ do { \ @@ -1329,12 +1331,78 @@ static void fs__fstat(uv_fs_t* req) { static void fs__rename(uv_fs_t* req) { - if (!MoveFileExW(req->file.pathw, req->fs.info.new_pathw, MOVEFILE_REPLACE_EXISTING)) { + int tries; + int sys_errno; + int result; + int try_rmdir; + WCHAR* src, *dst; + DWORD src_attrib, dst_attrib; + + src = req->file.pathw; + dst = req->fs.info.new_pathw; + try_rmdir = 0; + + /* Do some checks to fail early. */ + src_attrib = GetFileAttributesW(src); + if (src_attrib == INVALID_FILE_ATTRIBUTES) { SET_REQ_WIN32_ERROR(req, GetLastError()); return; } + dst_attrib = GetFileAttributesW(dst); + if (dst_attrib != INVALID_FILE_ATTRIBUTES) { + if (dst_attrib & FILE_ATTRIBUTE_READONLY) { + req->result = UV_EPERM; + return; + } + /* Renaming folder to a folder name that already exist will fail on + * Windows. We will try to delete target folder first. + */ + if (src_attrib & FILE_ATTRIBUTE_DIRECTORY && + dst_attrib & FILE_ATTRIBUTE_DIRECTORY) + try_rmdir = 1; + } - SET_REQ_RESULT(req, 0); + /* Sometimes an antivirus or indexing software can lock the target or the + * source file/directory. This is annoying for users, in such cases we will + * retry couple of times with some delay before failing. + */ + for (tries = 0; tries < UV__RENAME_RETRIES; ++tries) { + if (tries > 0) + Sleep(UV__RENAME_WAIT); + + if (try_rmdir) { + result = _wrmdir(dst) == 0 ? 0 : uv_translate_sys_error(_doserrno); + switch (result) + { + case 0: + case UV_ENOENT: + /* Folder removed or did not exist at all. */ + try_rmdir = 0; + break; + case UV_ENOTEMPTY: + /* Non-empty target folder, fail instantly. */ + SET_REQ_RESULT(req, -1); + return; + default: + /* All other errors - try to move file anyway and handle the error + * there, retrying folder deletion next time around. + */ + break; + } + } + + if (MoveFileExW(src, dst, MOVEFILE_REPLACE_EXISTING) != 0) { + SET_REQ_RESULT(req, 0); + return; + } + + sys_errno = GetLastError(); + result = uv_translate_sys_error(sys_errno); + if (result != UV_EBUSY && result != UV_EPERM && result != UV_EACCES) + break; + } + req->sys_errno_ = sys_errno; + req->result = result; }