broken racy detection and performance issues with nanosecond file times
Hi there, I think I found a few nasty problems with racy detection, as well as performance issues when using git implementations with different file time resolutions on the same repository (e.g. git compiled with and without USE_NSEC, libgit2 compiled with and without USE_NSEC, JGit executed in different Java implementations...). Let me start by listing relevant file time gotchas (skip this if it sounds too familiar) before diving into problem descriptions. Some ideas for potential solutions are at the end. Notable file time facts: The st_ctime discrepancy: * stat.st_ctime means "change time" (of file metadata) on POSIX systems and "creation time" on Windows * While some file systems may track all four time stamps (mtime, atime, change time and creation time), there are no public OS APIs to obtain creation time on POSIX / change time on Windows. Linux: * In-core file times may not be properly rounded to on-disk precision, causing spurious file time changes when the cache is refreshed from disk. This was fixed for typical Unix file systems in kernel 2.6.11. The fix for CEPH, CIFS, NTFS, UFS and FUSE will be in kernel 4.3. There's no fix for FAT-based file systems yet. * Maximum file time precision is 1 ns (or 1 s with really old glibc). Windows: * Maximum file time precision is 100 ns. Java <= 6: * Only exposes mtime in milliseconds (via File.getLastModifiedTime). Java >= 7: * Only exposes mtime, atime and creation time, no change time (see java.nio.file.attribute.BasicFileAttributes). * Maximum file time precision is implementation specific (OpenJDK: 1 microsecond on both Unix [1] and Windows [2]). * On platforms or file systems that don't support creation time, BasicFileAttribtes.creationTime() is implementation specific (OpenJDK returns mtime instead). There's no public API to detect whether creation time is supported or "emulated" in some way. Git Options: * NO_NSEC (git only): compile-time option that disables recording of nanoseconds in the index, implies USE_NSEC=false. * USE_NSEC (git and libgit2 with [3]): compile-time option that enables nanosecond comparison in both up-to-date and racy checks. * core.checkStat=minimal (git, libgit2, JGit): config-option that disables nanosecond comparison in up-to-date checks, but not in racy checks. JGit: * Only uses mtime, rounded to milliseconds. While there is a DirCacheEntry.setCreationTime() [4] to set the index entry's ctime field, AFAICT its not used anywhere. * Does not compare nanoseconds if the cached value recorded in the index is 0, to prevent performance issues with NO_NSEC git implementations [5]. Problem 1: Failure to detect racy files (without USE_NSEC) == Git may not detect racy changes when 'update-index' runs in parallel to work tree updates. Consider this (where timestamps are t.): t0.0$ echo "foo" > file1 t0.1$ git update-index file1 & # runs in background t0.2$ # update-index records stats and sha1 of file1 in new index t0.3$ echo "bar" > file1 $ # update-index writes other index entries t1.0$ # update-index finishes (sets mtime of the new index to t1.0!) t1.1$ git status # doesn't detect that file1 has changed The problem here is that racy checks in 'git status' compare against the new index file's mtime (t1.0), which may be newer than the last change of file1. Problem 2: Failure to detect racy files (mixed USE_NSEC) Git may fail to detect racy conditions if file times in .git/index have been recorded by another git implementation with better file time resolution. Consider the following sequence: t0.0$ echo "foo" > file1 t0.1$ use-nsec-git update-index file1 t0.2$ echo "bar" > file1 $ sleep 1 t1.0$ touch file2 t1.1$ use-nsec-git status # rewrites index, to store file2 change t1.2$ git status # doesn't detect that file1 has changed The problem here is that the first, nsec-enabled 'git status' does not consider file1 racy (with nanosecond precision, the file is dirty already (t0.0 != t0.2), so no racy-checks are performed). Thus, it will not squash the size field (as a second-precision-git would). However, it will rewrite the index to capture the status change of file2, and thus create a new index file with mtime = t1.1. Similar to problem 1, subsequent 'git status' with second-precision has no way to detect that file1 has changed. This problem would not be limited to USE_NSEC-enabled/disabled git, it occurs whenever different file time resolutions are at play, e.g.: * second-based git vs. millisecond-based JGit * millisecond-based JGit vs. nanosecond-enabled git * GIT_WORK_TREE on ext2 (1 s) and GIT_DIR on ext4 (1 ns) * JGit executed by different Java implementations (with different file time resolutions) Problem 3: Failure to detect racy files with core.checkStat=minimal ===
Re: broken racy detection and performance issues with nanosecond file times
Karsten Blees writes: > Problem 1: Failure to detect racy files (without USE_NSEC) > == > > Git may not detect racy changes when 'update-index' runs in parallel > to work tree updates. > > Consider this (where timestamps are t.): > > t0.0$ echo "foo" > file1 > t0.1$ git update-index file1 & # runs in background I just wonder after looking at the ampersand here ... > Please let me know what you think of this...maybe I've completely > screwed up and can no longer see the forest for all the trees. ... if your task would become much simpler if you declare "once you give Git the control, do not muck with the repository until you get the control back". -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
Re: broken racy detection and performance issues with nanosecond file times
Karsten Blees writes: > Ideas for potential solutions: > == > > Performance issues: > --- > > 1. Compare file times in minimum supported precision >When comparing file times, use the minimum precision supported by >both the writing and reading git implementations. > 1a. Simplest variant: Don't compare nanoseconds if the field in the >cached index entry is 0. JGit already does this [5], but at the >same time it is very unfriendly to USE_NSEC-enabled git by storing >only milliseconds in the nanosecond field. This "simple" solution >implies that git implementations that cannot provide full >nanosecond precision must leave the nanosecond field empty. > 1b. More involved: Store the precision in the index entry. >We only need 30 bits to encode nanoseconds, so the high 2 bits of >the nanosecond field could be used as follows: >00: second precision (i.e. ignore, for backward compatibility) >01: millisecond precision >10: microsecond precision >11: nanosecond precision >When reading the index, USE-NSEC-enabled git implementations would >do dirty checks with the minimum precision supported by themselves >and the creator of the index entry. Yeah, my gut feeling is that we should make sure that at least 1a is done by all implementations. I agree that 1b. is a bit more involved in that all binary that was built with USE_NSEC that is not aware of these 2-bits need to be eradicated for a new version to be deployed --- the transition for users who use multiple implementations will be a pain (those that use just one implementation of Git can just say "rm -f .git/index && git reset --hard" or something after updating to the new version of Git). > 2. Don't use ctime in dirty checks if ctime.sec == 0. OK. That is slightly less drastic than !trust_ctime, I guess. > Racy detection: > --- > > 3. Minimal racy solution >* Do all racy checks with second-precision only. >* When committing an index.lock file, reset mtime to the time > before git started reading the old index (i.e. time(null) when > calling read_cache()). > >I believe this should fix all three racy problems described above, >although restraining ourselves to second-precision somewhat >thwarts the ability to track nanoseconds in the first place. > >The problem with this solution is that files changed by git itself >will appear racy to the next git process, thus increasing the >performance penalty after e.g. a large checkout. Although I think >that re-reading the file after the file's mtime is the only way to >be really sure it hasn't been changed. ... the last of which is what is done anyway, so I think the above, especally the second bullet-point, is all sensible. -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
Re: broken racy detection and performance issues with nanosecond file times
Am 28.09.2015 um 19:38 schrieb Junio C Hamano: > Karsten Blees writes: > >> Problem 1: Failure to detect racy files (without USE_NSEC) >> == >> >> Git may not detect racy changes when 'update-index' runs in parallel >> to work tree updates. >> >> Consider this (where timestamps are t.): >> >> t0.0$ echo "foo" > file1 >> t0.1$ git update-index file1 & # runs in background > > I just wonder after looking at the ampersand here ... > >> Please let me know what you think of this...maybe I've completely >> screwed up and can no longer see the forest for all the trees. > > ... if your task would become much simpler if you declare "once you > give Git the control, do not muck with the repository until you get > the control back". > This is just to illustrate the problem. GUI-based applications will often do things in the background that you cannot control. E.g. gitk, git gui, Eclipse or TortoiseGit don't tell you when and how long you shouldn't touch the working copy. At the same time, IntelliJ IDEA and most office suits have the auto-save feature turned on by default, and you cannot tell them when *not* to auto-save. It may still be quite unlikely that this happens (you need two changes within a second, without changing the file size), but *if* it happens, the user may not even notice. And as git trusts the false stat data blindly, the problem won't go away automatically. You can mark all entries racy by setting index mtime to some value far in the past, but this implies that you noticed that something was wrong... -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html