Documentation / technical / racy-git.txton commit Merge branch 'maint' (e27e609)
   1Use of index and Racy git problem
   2=================================
   3
   4Background
   5----------
   6
   7The index is one of the most important data structures in git.
   8It represents a virtual working tree state by recording list of
   9paths and their object names and serves as a staging area to
  10write out the next tree object to be committed.  The state is
  11"virtual" in the sense that it does not necessarily have to, and
  12often does not, match the files in the working tree.
  13
  14There are cases git needs to examine the differences between the
  15virtual working tree state in the index and the files in the
  16working tree.  The most obvious case is when the user asks `git
  17diff` (or its low level implementation, `git diff-files`) or
  18`git-ls-files --modified`.  In addition, git internally checks
  19if the files in the working tree are different from what are
  20recorded in the index to avoid stomping on local changes in them
  21during patch application, switching branches, and merging.
  22
  23In order to speed up this comparison between the files in the
  24working tree and the index entries, the index entries record the
  25information obtained from the filesystem via `lstat(2)` system
  26call when they were last updated.  When checking if they differ,
  27git first runs `lstat(2)` on the files and compares the result
  28with this information (this is what was originally done by the
  29`ce_match_stat()` function, but the current code does it in
  30`ce_match_stat_basic()` function).  If some of these "cached
  31stat information" fields do not match, git can tell that the
  32files are modified without even looking at their contents.
  33
  34Note: not all members in `struct stat` obtained via `lstat(2)`
  35are used for this comparison.  For example, `st_atime` obviously
  36is not useful.  Currently, git compares the file type (regular
  37files vs symbolic links) and executable bits (only for regular
  38files) from `st_mode` member, `st_mtime` and `st_ctime`
  39timestamps, `st_uid`, `st_gid`, `st_ino`, and `st_size` members.
  40With a `USE_STDEV` compile-time option, `st_dev` is also
  41compared, but this is not enabled by default because this member
  42is not stable on network filesystems.  With `USE_NSEC`
  43compile-time option, `st_mtim.tv_nsec` and `st_ctim.tv_nsec`
  44members are also compared, but this is not enabled by default
  45because the value of this member becomes meaningless once the
  46inode is evicted from the inode cache on filesystems that do not
  47store it on disk.
  48
  49
  50Racy git
  51--------
  52
  53There is one slight problem with the optimization based on the
  54cached stat information.  Consider this sequence:
  55
  56  : modify 'foo'
  57  $ git update-index 'foo'
  58  : modify 'foo' again, in-place, without changing its size
  59
  60The first `update-index` computes the object name of the
  61contents of file `foo` and updates the index entry for `foo`
  62along with the `struct stat` information.  If the modification
  63that follows it happens very fast so that the file's `st_mtime`
  64timestamp does not change, after this sequence, the cached stat
  65information the index entry records still exactly match what you
  66would see in the filesystem, even though the file `foo` is now
  67different.
  68This way, git can incorrectly think files in the working tree
  69are unmodified even though they actually are.  This is called
  70the "racy git" problem (discovered by Pasky), and the entries
  71that appear clean when they may not be because of this problem
  72are called "racily clean".
  73
  74To avoid this problem, git does two things:
  75
  76. When the cached stat information says the file has not been
  77  modified, and the `st_mtime` is the same as (or newer than)
  78  the timestamp of the index file itself (which is the time `git
  79  update-index foo` finished running in the above example), it
  80  also compares the contents with the object registered in the
  81  index entry to make sure they match.
  82
  83. When the index file is updated that contains racily clean
  84  entries, cached `st_size` information is truncated to zero
  85  before writing a new version of the index file.
  86
  87Because the index file itself is written after collecting all
  88the stat information from updated paths, `st_mtime` timestamp of
  89it is usually the same as or newer than any of the paths the
  90index contains.  And no matter how quick the modification that
  91follows `git update-index foo` finishes, the resulting
  92`st_mtime` timestamp on `foo` cannot get a value earlier
  93than the index file.  Therefore, index entries that can be
  94racily clean are limited to the ones that have the same
  95timestamp as the index file itself.
  96
  97The callers that want to check if an index entry matches the
  98corresponding file in the working tree continue to call
  99`ce_match_stat()`, but with this change, `ce_match_stat()` uses
 100`ce_modified_check_fs()` to see if racily clean ones are
 101actually clean after comparing the cached stat information using
 102`ce_match_stat_basic()`.
 103
 104The problem the latter solves is this sequence:
 105
 106  $ git update-index 'foo'
 107  : modify 'foo' in-place without changing its size
 108  : wait for enough time
 109  $ git update-index 'bar'
 110
 111Without the latter, the timestamp of the index file gets a newer
 112value, and falsely clean entry `foo` would not be caught by the
 113timestamp comparison check done with the former logic anymore.
 114The latter makes sure that the cached stat information for `foo`
 115would never match with the file in the working tree, so later
 116checks by `ce_match_stat_basic()` would report that the index entry
 117does not match the file and git does not have to fall back on more
 118expensive `ce_modified_check_fs()`.
 119
 120
 121Runtime penalty
 122---------------
 123
 124The runtime penalty of falling back to `ce_modified_check_fs()`
 125from `ce_match_stat()` can be very expensive when there are many
 126racily clean entries.  An obvious way to artificially create
 127this situation is to give the same timestamp to all the files in
 128the working tree in a large project, run `git update-index` on
 129them, and give the same timestamp to the index file:
 130
 131  $ date >.datestamp
 132  $ git ls-files | xargs touch -r .datestamp
 133  $ git ls-files | git update-index --stdin
 134  $ touch -r .datestamp .git/index
 135
 136This will make all index entries racily clean.  The linux-2.6
 137project, for example, there are over 20,000 files in the working
 138tree.  On my Athron 64X2 3800+, after the above:
 139
 140  $ /usr/bin/time git diff-files
 141  1.68user 0.54system 0:02.22elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k
 142  0inputs+0outputs (0major+67111minor)pagefaults 0swaps
 143  $ git update-index MAINTAINERS
 144  $ /usr/bin/time git diff-files
 145  0.02user 0.12system 0:00.14elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k
 146  0inputs+0outputs (0major+935minor)pagefaults 0swaps
 147
 148Running `git update-index` in the middle checked the racily
 149clean entries, and left the cached `st_mtime` for all the paths
 150intact because they were actually clean (so this step took about
 151the same amount of time as the first `git diff-files`).  After
 152that, they are not racily clean anymore but are truly clean, so
 153the second invocation of `git diff-files` fully took advantage
 154of the cached stat information.
 155
 156
 157Avoiding runtime penalty
 158------------------------
 159
 160In order to avoid the above runtime penalty, post 1.4.2 git used
 161to have a code that made sure the index file
 162got timestamp newer than the youngest files in the index when
 163there are many young files with the same timestamp as the
 164resulting index file would otherwise would have by waiting
 165before finishing writing the index file out.
 166
 167I suspected that in practice the situation where many paths in the
 168index are all racily clean was quite rare.  The only code paths
 169that can record recent timestamp for large number of paths are:
 170
 171. Initial `git add .` of a large project.
 172
 173. `git checkout` of a large project from an empty index into an
 174  unpopulated working tree.
 175
 176Note: switching branches with `git checkout` keeps the cached
 177stat information of existing working tree files that are the
 178same between the current branch and the new branch, which are
 179all older than the resulting index file, and they will not
 180become racily clean.  Only the files that are actually checked
 181out can become racily clean.
 182
 183In a large project where raciness avoidance cost really matters,
 184however, the initial computation of all object names in the
 185index takes more than one second, and the index file is written
 186out after all that happens.  Therefore the timestamp of the
 187index file will be more than one seconds later than the the
 188youngest file in the working tree.  This means that in these
 189cases there actually will not be any racily clean entry in
 190the resulting index.
 191
 192Based on this discussion, the current code does not use the
 193"workaround" to avoid the runtime penalty that does not exist in
 194practice anymore.  This was done with commit 0fc82cff on Aug 15,
 1952006.