[Python-checkins] cpython: Issue #13392: Writing a pyc file should now be atomic under Windows as well.

antoine.pitrou python-checkins at python.org
Tue Nov 15 19:20:01 CET 2011


http://hg.python.org/cpython/rev/b75b41237380
changeset:   73573:b75b41237380
user:        Antoine Pitrou <solipsis at pitrou.net>
date:        Tue Nov 15 19:15:19 2011 +0100
summary:
  Issue #13392: Writing a pyc file should now be atomic under Windows as well.

files:
  Lib/importlib/_bootstrap.py |  35 ++++++++++-------
  Misc/NEWS                   |   2 +
  Python/import.c             |  48 +++++++++++++++++++-----
  3 files changed, 60 insertions(+), 25 deletions(-)


diff --git a/Lib/importlib/_bootstrap.py b/Lib/importlib/_bootstrap.py
--- a/Lib/importlib/_bootstrap.py
+++ b/Lib/importlib/_bootstrap.py
@@ -84,24 +84,29 @@
     """Best-effort function to write data to a path atomically.
     Be prepared to handle a FileExistsError if concurrent writing of the
     temporary file is attempted."""
-    if not sys.platform.startswith('win'):
-        # On POSIX-like platforms, renaming is atomic. id() is used to generate
-        # a pseudo-random filename.
-        path_tmp = '{}.{}'.format(path, id(path))
-        fd = _os.open(path_tmp, _os.O_EXCL | _os.O_CREAT | _os.O_WRONLY, 0o666)
+    # Renaming should be atomic on most platforms (including Windows).
+    # Under Windows, the limitation is that we can't rename() to an existing
+    # path, while POSIX will overwrite it. But here we don't really care
+    # if there is a glimpse of time during which the final pyc file doesn't
+    # exist.
+    # id() is used to generate a pseudo-random filename.
+    path_tmp = '{}.{}'.format(path, id(path))
+    fd = _os.open(path_tmp, _os.O_EXCL | _os.O_CREAT | _os.O_WRONLY, 0o666)
+    try:
+        with _io.FileIO(fd, 'wb') as file:
+            file.write(data)
         try:
-            with _io.FileIO(fd, 'wb') as file:
-                file.write(data)
             _os.rename(path_tmp, path)
+        except FileExistsError:
+            # Windows (if we had access to MoveFileEx, we could overwrite)
+            _os.unlink(path)
+            _os.rename(path_tmp, path)
+    except OSError:
+        try:
+            _os.unlink(path_tmp)
         except OSError:
-            try:
-                _os.unlink(path_tmp)
-            except OSError:
-                pass
-            raise
-    else:
-        with _io.FileIO(path, 'wb') as file:
-            file.write(data)
+            pass
+        raise
 
 
 def _wrap(new, old):
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -10,6 +10,8 @@
 Core and Builtins
 -----------------
 
+- Issue #13392: Writing a pyc file should now be atomic under Windows as well.
+
 - Issue #13333: The UTF-7 decoder now accepts lone surrogates (the encoder
   already accepts them).
 
diff --git a/Python/import.c b/Python/import.c
--- a/Python/import.c
+++ b/Python/import.c
@@ -1197,6 +1197,8 @@
     time_t mtime = srcstat->st_mtime;
 #ifdef MS_WINDOWS   /* since Windows uses different permissions  */
     mode_t mode = srcstat->st_mode & ~S_IEXEC;
+    PyObject *cpathname_tmp;
+    Py_ssize_t cpathname_len;
 #else
     mode_t dirmode = (srcstat->st_mode |
                       S_IXUSR | S_IXGRP | S_IXOTH |
@@ -1255,18 +1257,29 @@
     }
     Py_DECREF(dirname);
 
+    /* We first write to a tmp file and then take advantage
+       of atomic renaming (which *should* be true even under Windows). */
 #ifdef MS_WINDOWS
-    (void)DeleteFileW(PyUnicode_AS_UNICODE(cpathname));
-    fd = _wopen(PyUnicode_AS_UNICODE(cpathname),
-                 O_EXCL | O_CREAT | O_WRONLY | O_TRUNC | O_BINARY,
-                 mode);
+    cpathname_len = PyUnicode_GET_LENGTH(cpathname);
+    cpathname_tmp = PyUnicode_New(cpathname_len + 4,
+                                  PyUnicode_MAX_CHAR_VALUE(cpathname));
+    if (cpathname_tmp == NULL) {
+        PyErr_Clear();
+        return;
+    }
+    PyUnicode_WriteChar(cpathname_tmp, cpathname_len + 0, '.');
+    PyUnicode_WriteChar(cpathname_tmp, cpathname_len + 1, 't');
+    PyUnicode_WriteChar(cpathname_tmp, cpathname_len + 2, 'm');
+    PyUnicode_WriteChar(cpathname_tmp, cpathname_len + 3, 'p');
+    (void)DeleteFileW(PyUnicode_AS_UNICODE(cpathname_tmp));
+    fd = _wopen(PyUnicode_AS_UNICODE(cpathname_tmp),
+                O_EXCL | O_CREAT | O_WRONLY | O_BINARY,
+                mode);
     if (0 <= fd)
         fp = fdopen(fd, "wb");
     else
         fp = NULL;
 #else
-    /* Under POSIX, we first write to a tmp file and then take advantage
-       of atomic renaming. */
     cpathbytes = PyUnicode_EncodeFSDefault(cpathname);
     if (cpathbytes == NULL) {
         PyErr_Clear();
@@ -1294,7 +1307,9 @@
         if (Py_VerboseFlag)
             PySys_FormatStderr(
                 "# can't create %R\n", cpathname);
-#ifndef MS_WINDOWS
+#ifdef MS_WINDOWS
+        Py_DECREF(cpathname_tmp);
+#else
         Py_DECREF(cpathbytes);
         Py_DECREF(cpathbytes_tmp);
 #endif
@@ -1315,7 +1330,8 @@
         /* Don't keep partial file */
         fclose(fp);
 #ifdef MS_WINDOWS
-        (void)DeleteFileW(PyUnicode_AS_UNICODE(cpathname));
+        (void)DeleteFileW(PyUnicode_AS_UNICODE(cpathname_tmp));
+        Py_DECREF(cpathname_tmp);
 #else
         (void) unlink(PyBytes_AS_STRING(cpathbytes_tmp));
         Py_DECREF(cpathbytes);
@@ -1324,8 +1340,20 @@
         return;
     }
     fclose(fp);
-    /* Under POSIX, do an atomic rename */
-#ifndef MS_WINDOWS
+    /* Do a (hopefully) atomic rename */
+#ifdef MS_WINDOWS
+    if (!MoveFileExW(PyUnicode_AS_UNICODE(cpathname_tmp),
+                     PyUnicode_AS_UNICODE(cpathname),
+                     MOVEFILE_REPLACE_EXISTING)) {
+        if (Py_VerboseFlag)
+            PySys_FormatStderr("# can't write %R\n", cpathname);
+        /* Don't keep tmp file */
+        (void) DeleteFileW(PyUnicode_AS_UNICODE(cpathname_tmp));
+        Py_DECREF(cpathname_tmp);
+        return;
+    }
+    Py_DECREF(cpathname_tmp);
+#else
     if (rename(PyBytes_AS_STRING(cpathbytes_tmp),
                PyBytes_AS_STRING(cpathbytes))) {
         if (Py_VerboseFlag)

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list