summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Lib/importlib/_bootstrap.py26
-rw-r--r--Misc/NEWS2
-rw-r--r--Python/import.c45
3 files changed, 61 insertions, 12 deletions
diff --git a/Lib/importlib/_bootstrap.py b/Lib/importlib/_bootstrap.py
index 18ea85c..824a31c 100644
--- a/Lib/importlib/_bootstrap.py
+++ b/Lib/importlib/_bootstrap.py
@@ -80,6 +80,27 @@ def _path_absolute(path):
return _path_join(_os.getcwd(), path)
+def _write_atomic(path, data):
+ """Best-effort function to write data to a path atomically."""
+ if not sys.platform.startswith('win'):
+ # On POSIX-like platforms, renaming is atomic
+ path_tmp = path + '.tmp'
+ try:
+ fd = _os.open(path_tmp, _os.O_EXCL | _os.O_CREAT | _os.O_WRONLY)
+ with _io.FileIO(fd, 'wb') as file:
+ file.write(data)
+ _os.rename(path_tmp, path)
+ except OSError:
+ try:
+ _os.unlink(path_tmp)
+ except OSError:
+ pass
+ raise
+ else:
+ with _io.FileIO(path, 'wb') as file:
+ file.write(data)
+
+
def _wrap(new, old):
"""Simple substitute for functools.wraps."""
for replace in ['__module__', '__name__', '__doc__']:
@@ -494,9 +515,8 @@ class _SourceFileLoader(_FileLoader, SourceLoader):
else:
raise
try:
- with _io.FileIO(path, 'wb') as file:
- file.write(data)
- except IOError as exc:
+ _write_atomic(path, data)
+ except OSError as exc:
# Don't worry if you can't write bytecode.
if exc.errno == errno.EACCES:
return
diff --git a/Misc/NEWS b/Misc/NEWS
index b8d43ba..ddd48c4 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -10,6 +10,8 @@ What's New in Python 3.3 Alpha 1?
Core and Builtins
-----------------
+- Issue #13146: Writing a pyc file is now atomic under POSIX.
+
- Issue #7833: Extension modules built using distutils on Windows will no
longer include a "manifest" to prevent them failing at import time in some
embedded situations.
diff --git a/Python/import.c b/Python/import.c
index 6e3e0f1..0db7e43 100644
--- a/Python/import.c
+++ b/Python/import.c
@@ -1284,7 +1284,8 @@ write_compiled_module(PyCodeObject *co, PyObject *cpathname,
#ifdef MS_WINDOWS
int fd;
#else
- PyObject *cpathbytes;
+ PyObject *cpathbytes, *cpathbytes_tmp;
+ Py_ssize_t cpathbytes_len;
#endif
PyObject *dirname;
Py_UCS4 *dirsep;
@@ -1345,13 +1346,25 @@ write_compiled_module(PyCodeObject *co, PyObject *cpathname,
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();
return;
}
+ cpathbytes_len = PyBytes_GET_SIZE(cpathbytes);
+ cpathbytes_tmp = PyBytes_FromStringAndSize(NULL, cpathbytes_len + 4);
+ if (cpathbytes_tmp == NULL) {
+ Py_DECREF(cpathbytes);
+ PyErr_Clear();
+ return;
+ }
+ memcpy(PyBytes_AS_STRING(cpathbytes_tmp), PyBytes_AS_STRING(cpathbytes),
+ cpathbytes_len);
+ memcpy(PyBytes_AS_STRING(cpathbytes_tmp) + cpathbytes_len, ".tmp", 4);
- fp = open_exclusive(PyBytes_AS_STRING(cpathbytes), mode);
+ fp = open_exclusive(PyBytes_AS_STRING(cpathbytes_tmp), mode);
#endif
if (fp == NULL) {
if (Py_VerboseFlag)
@@ -1359,6 +1372,7 @@ write_compiled_module(PyCodeObject *co, PyObject *cpathname,
"# can't create %R\n", cpathname);
#ifndef MS_WINDOWS
Py_DECREF(cpathbytes);
+ Py_DECREF(cpathbytes_tmp);
#endif
return;
}
@@ -1366,6 +1380,11 @@ write_compiled_module(PyCodeObject *co, PyObject *cpathname,
/* First write a 0 for mtime */
PyMarshal_WriteLongToFile(0L, fp, Py_MARSHAL_VERSION);
PyMarshal_WriteObjectToFile((PyObject *)co, fp, Py_MARSHAL_VERSION);
+ fflush(fp);
+ /* Now write the true mtime */
+ fseek(fp, 4L, 0);
+ assert(mtime < LONG_MAX);
+ PyMarshal_WriteLongToFile((long)mtime, fp, Py_MARSHAL_VERSION);
if (fflush(fp) != 0 || ferror(fp)) {
if (Py_VerboseFlag)
PySys_FormatStderr("# can't write %R\n", cpathname);
@@ -1374,20 +1393,28 @@ write_compiled_module(PyCodeObject *co, PyObject *cpathname,
#ifdef MS_WINDOWS
(void)DeleteFileW(PyUnicode_AS_UNICODE(cpathname));
#else
- (void) unlink(PyBytes_AS_STRING(cpathbytes));
+ (void) unlink(PyBytes_AS_STRING(cpathbytes_tmp));
Py_DECREF(cpathbytes);
+ Py_DECREF(cpathbytes_tmp);
#endif
return;
}
+ fclose(fp);
+ /* Under POSIX, do an atomic rename */
#ifndef MS_WINDOWS
+ if (rename(PyBytes_AS_STRING(cpathbytes_tmp),
+ PyBytes_AS_STRING(cpathbytes))) {
+ if (Py_VerboseFlag)
+ PySys_FormatStderr("# can't write %R\n", cpathname);
+ /* Don't keep tmp file */
+ unlink(PyBytes_AS_STRING(cpathbytes_tmp));
+ Py_DECREF(cpathbytes);
+ Py_DECREF(cpathbytes_tmp);
+ return;
+ }
Py_DECREF(cpathbytes);
+ Py_DECREF(cpathbytes_tmp);
#endif
- /* Now write the true mtime */
- fseek(fp, 4L, 0);
- assert(mtime < LONG_MAX);
- PyMarshal_WriteLongToFile((long)mtime, fp, Py_MARSHAL_VERSION);
- fflush(fp);
- fclose(fp);
if (Py_VerboseFlag)
PySys_FormatStderr("# wrote %R\n", cpathname);
}