Skip to content

Commit

Permalink
win,fs: retry if uv_fs_rename fails
Browse files Browse the repository at this point in the history
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: libuv#1981
Reviewed-By: Sakthipriyan Vairamani <thechargingvolcano@gmail.com>
Reviewed-By: Santiago Gimeno <santiago.gimeno@gmail.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
  • Loading branch information
bzoz committed Nov 1, 2018
1 parent ebb818b commit e94c184
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 2 deletions.
9 changes: 9 additions & 0 deletions docs/src/fs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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)`.
Expand Down
72 changes: 70 additions & 2 deletions src/win/fs.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 { \
Expand Down Expand Up @@ -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;
}


Expand Down

0 comments on commit e94c184

Please sign in to comment.