##// END OF EJS Templates
revlog: improve the robustness of the splitting process...
revlog: improve the robustness of the splitting process The previous "in-place" splitting, preserving the splitting on transaction failure had a couple of issue in case of transaction rollback: - a race windows that could still lead to a crash and data loss - it corrupted the `fncache`. So instead, we use a new approach that we summarized as "we do a backup of the inline revlog pre-split, and we restore this in case of failure". To make readers live easier, we don't overwrite the inline index file until transaction finalization. (once the transaction get into its finalization phase, it is not expected to rollback, unless some crash happens). To do so, we write the index of the split index in a temporary file that we use until transaction finalization. We also keep a backup of the initial inline file to be able to rollback the split if needed. As a result, transaction rollback cancel the split and no longer corrupt fncache. We also no longer have a small inconsistency windows where the transaction could be unrecoverable.

File last commit:

r51228:0d3690f8 stable
r51242:87f0155d stable
Show More
dirs.c
326 lines | 7.2 KiB | text/x-c | CLexer
Yuya Nishihara
parsers: switch to policy importer...
r32372 /*
dirs.c - dynamic directory diddling for dirstates
Copyright 2013 Facebook
This software may be used and distributed according to the terms of
the GNU General Public License, incorporated herein by reference.
*/
#define PY_SSIZE_T_CLEAN
#include <Python.h>
Augie Fackler
dirs: resolve fuzzer OOM situation by disallowing deep directory hierarchies...
r44057 #include <string.h>
Gregory Szorc
cext: reorder #include...
r34439
Yuya Nishihara
parsers: switch to policy importer...
r32372 #include "util.h"
Mads Kiilerich
cext: fix for PyLong refactoring in CPython 3.12...
r51228 #if PY_VERSION_HEX >= 0x030C00A5
#define PYLONG_VALUE(o) ((PyLongObject *)o)->long_value.ob_digit[0]
#else
Martin von Zweigbergk
dirs: fix out-of-bounds access in Py3...
r44290 #define PYLONG_VALUE(o) ((PyLongObject *)o)->ob_digit[0]
Mads Kiilerich
cext: fix for PyLong refactoring in CPython 3.12...
r51228 #endif
Yuya Nishihara
parsers: switch to policy importer...
r32372
/*
* This is a multiset of directory names, built from the files that
* appear in a dirstate or manifest.
*
* A few implementation notes:
*
* We modify Python integers for refcounting, but those integers are
* never visible to Python code.
*/
Augie Fackler
dirs: tag a struct as not being formattable...
r43506 /* clang-format off */
Yuya Nishihara
parsers: switch to policy importer...
r32372 typedef struct {
PyObject_HEAD
PyObject *dict;
} dirsObject;
Augie Fackler
dirs: tag a struct as not being formattable...
r43506 /* clang-format on */
Yuya Nishihara
parsers: switch to policy importer...
r32372
static inline Py_ssize_t _finddir(const char *path, Py_ssize_t pos)
{
while (pos != -1) {
if (path[pos] == '/')
break;
pos -= 1;
}
Martin von Zweigbergk
util: make util.dirs() and util.finddirs() include root directory (API)...
r42530 if (pos == -1) {
Augie Fackler
dirs: give formatting oversight to clang-format...
r43507 return 0;
Martin von Zweigbergk
util: make util.dirs() and util.finddirs() include root directory (API)...
r42530 }
Yuya Nishihara
parsers: switch to policy importer...
r32372
return pos;
}
Augie Fackler
dirs: resolve fuzzer OOM situation by disallowing deep directory hierarchies...
r44057 /* Mercurial will fail to run on directory hierarchies deeper than
* this constant, so we should try and keep this constant as big as
* possible.
*/
#define MAX_DIRS_DEPTH 2048
Yuya Nishihara
parsers: switch to policy importer...
r32372 static int _addpath(PyObject *dirs, PyObject *path)
{
const char *cpath = PyBytes_AS_STRING(path);
Py_ssize_t pos = PyBytes_GET_SIZE(path);
PyObject *key = NULL;
int ret = -1;
Augie Fackler
dirs: resolve fuzzer OOM situation by disallowing deep directory hierarchies...
r44057 size_t num_slashes = 0;
Yuya Nishihara
parsers: switch to policy importer...
r32372
/* This loop is super critical for performance. That's why we inline
Augie Fackler
dirs: give formatting oversight to clang-format...
r43507 * access to Python structs instead of going through a supported API.
* The implementation, therefore, is heavily dependent on CPython
* implementation details. We also commit violations of the Python
* "protocol" such as mutating immutable objects. But since we only
* mutate objects created in this function or in other well-defined
* locations, the references are known so these violations should go
* unnoticed. */
Yuya Nishihara
parsers: switch to policy importer...
r32372 while ((pos = _finddir(cpath, pos - 1)) != -1) {
PyObject *val;
Augie Fackler
dirs: resolve fuzzer OOM situation by disallowing deep directory hierarchies...
r44057 ++num_slashes;
if (num_slashes > MAX_DIRS_DEPTH) {
PyErr_SetString(PyExc_ValueError,
"Directory hierarchy too deep.");
goto bail;
}
Yuya Nishihara
parsers: switch to policy importer...
r32372
Augie Fackler
dirs: reject consecutive slashes in paths...
r43799 /* Sniff for trailing slashes, a marker of an invalid input. */
if (pos > 0 && cpath[pos - 1] == '/') {
PyErr_SetString(
PyExc_ValueError,
"found invalid consecutive slashes in path");
goto bail;
}
Yuya Nishihara
dirs: remove mutable string optimization at all...
r43503 key = PyBytes_FromStringAndSize(cpath, pos);
if (key == NULL)
goto bail;
Yuya Nishihara
parsers: switch to policy importer...
r32372
val = PyDict_GetItem(dirs, key);
if (val != NULL) {
PYLONG_VALUE(val) += 1;
Yuya Nishihara
dirs: remove mutable string optimization at all...
r43503 Py_CLEAR(key);
Yuya Nishihara
parsers: switch to policy importer...
r32372 break;
}
/* Force Python to not reuse a small shared int. */
val = PyLong_FromLong(0x1eadbeef);
if (val == NULL)
goto bail;
PYLONG_VALUE(val) = 1;
ret = PyDict_SetItem(dirs, key, val);
Py_DECREF(val);
if (ret == -1)
goto bail;
Py_CLEAR(key);
}
ret = 0;
bail:
Py_XDECREF(key);
return ret;
}
static int _delpath(PyObject *dirs, PyObject *path)
{
char *cpath = PyBytes_AS_STRING(path);
Py_ssize_t pos = PyBytes_GET_SIZE(path);
PyObject *key = NULL;
int ret = -1;
while ((pos = _finddir(cpath, pos - 1)) != -1) {
PyObject *val;
key = PyBytes_FromStringAndSize(cpath, pos);
if (key == NULL)
goto bail;
val = PyDict_GetItem(dirs, key);
if (val == NULL) {
PyErr_SetString(PyExc_ValueError,
Augie Fackler
dirs: give formatting oversight to clang-format...
r43507 "expected a value, found none");
Yuya Nishihara
parsers: switch to policy importer...
r32372 goto bail;
}
if (--PYLONG_VALUE(val) <= 0) {
if (PyDict_DelItem(dirs, key) == -1)
goto bail;
} else
break;
Py_CLEAR(key);
}
ret = 0;
bail:
Py_XDECREF(key);
return ret;
}
pathutil: replace the `skip` argument of `dirs` with a boolean...
r48756 static int dirs_fromdict(PyObject *dirs, PyObject *source, bool only_tracked)
Yuya Nishihara
parsers: switch to policy importer...
r32372 {
PyObject *key, *value;
Py_ssize_t pos = 0;
while (PyDict_Next(source, &pos, &key, &value)) {
if (!PyBytes_Check(key)) {
PyErr_SetString(PyExc_TypeError, "expected string key");
return -1;
}
pathutil: replace the `skip` argument of `dirs` with a boolean...
r48756 if (only_tracked) {
Yuya Nishihara
parsers: switch to policy importer...
r32372 if (!dirstate_tuple_check(value)) {
PyErr_SetString(PyExc_TypeError,
Augie Fackler
dirs: give formatting oversight to clang-format...
r43507 "expected a dirstate tuple");
Yuya Nishihara
parsers: switch to policy importer...
r32372 return -1;
}
dirstate-item: move the C implementation to the same logic...
r48760 if (!(((dirstateItemObject *)value)->flags &
dirstate_flag_wc_tracked))
Yuya Nishihara
parsers: switch to policy importer...
r32372 continue;
}
if (_addpath(dirs, key) == -1)
return -1;
}
return 0;
}
static int dirs_fromiter(PyObject *dirs, PyObject *source)
{
PyObject *iter, *item = NULL;
int ret;
iter = PyObject_GetIter(source);
if (iter == NULL)
return -1;
while ((item = PyIter_Next(iter)) != NULL) {
if (!PyBytes_Check(item)) {
PyErr_SetString(PyExc_TypeError, "expected string");
break;
}
if (_addpath(dirs, item) == -1)
break;
Py_CLEAR(item);
}
ret = PyErr_Occurred() ? -1 : 0;
Py_DECREF(iter);
Py_XDECREF(item);
return ret;
}
/*
* Calculate a refcounted set of directory names for the files in a
* dirstate.
*/
pathutil: replace the `skip` argument of `dirs` with a boolean...
r48756 static int dirs_init(dirsObject *self, PyObject *args, PyObject *kwargs)
Yuya Nishihara
parsers: switch to policy importer...
r32372 {
PyObject *dirs = NULL, *source = NULL;
pathutil: replace the `skip` argument of `dirs` with a boolean...
r48756 int only_tracked = 0;
Yuya Nishihara
parsers: switch to policy importer...
r32372 int ret = -1;
pathutil: replace the `skip` argument of `dirs` with a boolean...
r48756 static char *keywords_name[] = {"map", "only_tracked", NULL};
Yuya Nishihara
parsers: switch to policy importer...
r32372
self->dict = NULL;
pathutil: replace the `skip` argument of `dirs` with a boolean...
r48756 if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|Oi:__init__",
keywords_name, &source, &only_tracked))
Yuya Nishihara
parsers: switch to policy importer...
r32372 return -1;
dirs = PyDict_New();
if (dirs == NULL)
return -1;
if (source == NULL)
ret = 0;
else if (PyDict_Check(source))
pathutil: replace the `skip` argument of `dirs` with a boolean...
r48756 ret = dirs_fromdict(dirs, source, (bool)only_tracked);
else if (only_tracked)
Yuya Nishihara
parsers: switch to policy importer...
r32372 PyErr_SetString(PyExc_ValueError,
pathutil: replace the `skip` argument of `dirs` with a boolean...
r48756 "`only_tracked` is only supported "
Augie Fackler
dirs: give formatting oversight to clang-format...
r43507 "with a dict source");
Yuya Nishihara
parsers: switch to policy importer...
r32372 else
ret = dirs_fromiter(dirs, source);
if (ret == -1)
Py_XDECREF(dirs);
else
self->dict = dirs;
return ret;
}
PyObject *dirs_addpath(dirsObject *self, PyObject *args)
{
PyObject *path;
if (!PyArg_ParseTuple(args, "O!:addpath", &PyBytes_Type, &path))
return NULL;
if (_addpath(self->dict, path) == -1)
return NULL;
Py_RETURN_NONE;
}
static PyObject *dirs_delpath(dirsObject *self, PyObject *args)
{
PyObject *path;
if (!PyArg_ParseTuple(args, "O!:delpath", &PyBytes_Type, &path))
return NULL;
if (_delpath(self->dict, path) == -1)
return NULL;
Py_RETURN_NONE;
}
static int dirs_contains(dirsObject *self, PyObject *value)
{
return PyBytes_Check(value) ? PyDict_Contains(self->dict, value) : 0;
}
static void dirs_dealloc(dirsObject *self)
{
Py_XDECREF(self->dict);
PyObject_Del(self);
}
static PyObject *dirs_iter(dirsObject *self)
{
return PyObject_GetIter(self->dict);
}
static PySequenceMethods dirs_sequence_methods;
static PyMethodDef dirs_methods[] = {
Augie Fackler
dirs: give formatting oversight to clang-format...
r43507 {"addpath", (PyCFunction)dirs_addpath, METH_VARARGS, "add a path"},
{"delpath", (PyCFunction)dirs_delpath, METH_VARARGS, "remove a path"},
{NULL} /* Sentinel */
Yuya Nishihara
parsers: switch to policy importer...
r32372 };
Augie Fackler
dirs: give formatting oversight to clang-format...
r43507 static PyTypeObject dirsType = {PyVarObject_HEAD_INIT(NULL, 0)};
Yuya Nishihara
parsers: switch to policy importer...
r32372
void dirs_module_init(PyObject *mod)
{
dirs_sequence_methods.sq_contains = (objobjproc)dirs_contains;
dirsType.tp_name = "parsers.dirs";
dirsType.tp_new = PyType_GenericNew;
dirsType.tp_basicsize = sizeof(dirsObject);
dirsType.tp_dealloc = (destructor)dirs_dealloc;
dirsType.tp_as_sequence = &dirs_sequence_methods;
dirsType.tp_flags = Py_TPFLAGS_DEFAULT;
dirsType.tp_doc = "dirs";
dirsType.tp_iter = (getiterfunc)dirs_iter;
dirsType.tp_methods = dirs_methods;
dirsType.tp_init = (initproc)dirs_init;
if (PyType_Ready(&dirsType) < 0)
return;
Py_INCREF(&dirsType);
PyModule_AddObject(mod, "dirs", (PyObject *)&dirsType);
}