From 4738fe33f9869fa6246a1f799c3abd8656a6d200 Mon Sep 17 00:00:00 2001 From: Kirill Likhodedov Date: Wed, 2 May 2012 17:04:40 +0400 Subject: [PATCH] IDEA-85521 Sometimes file appear both as ADDED and UNVERSIONED Root cause: Race condition around verifyPossiblyUntrackedFiles(): 1) AWT: File created. Added to myPossiblyUntrackedFiles. 2) T2: starts background thread T2 to check file for ignorance and add it to Git. 3) T1: File is marked dirty, its status is queried by GitChangeProvider in T1. 4) T1: verifyPossiblyUntrackedFiles asks the state of the file, it is untracked, and it is put into untrackedFiles temp variable. 5) T2: File is added to Git. 6) T2: File is removed from myDUF, it is not there, but nobody cares. 7) T1: File is added to myDUF from verifyPossiblyUntrackedFiles. Solution: Check untracked files under the lock. The LOCK is used in after() => not to lock the AWT, use a separate lock for myDefinitelyUntrackedFiles, leaving the LOCK for myReady and myPossiblyUntrackedFiles. Don't check for myReady in add()/remove() - it is not necessary. --- .../repo/GitUntrackedFilesHolder.java | 49 +++++++++++-------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/plugins/git4idea/src/git4idea/repo/GitUntrackedFilesHolder.java b/plugins/git4idea/src/git4idea/repo/GitUntrackedFilesHolder.java index cd91d5aa2c02..c748728f852e 100644 --- a/plugins/git4idea/src/git4idea/repo/GitUntrackedFilesHolder.java +++ b/plugins/git4idea/src/git4idea/repo/GitUntrackedFilesHolder.java @@ -68,10 +68,18 @@ import java.util.Set; * In some cases (file creation/deletion) the file is not silently added/removed from the list - instead the file is marked as * "possibly untracked" and Git is asked for the exact status of this file. * It is needed, since the file may be created and added to the index independently, and events may race. - *
+ *

+ *

* Also, if .git/index changes, then a full refresh is initiated. The reason is not only untracked files tracking, but also handling * committing outside IDEA, etc. *

+ *

+ * Synchronization policy used in this class:
+ * myDefinitelyUntrackedFiles is accessed under the myDefinitelyUntrackedFiles lock.
+ * myPossiblyUntrackedFiles and myReady is accessed under the LOCK lock.
+ * This is done so, because the latter two variables are accessed from the AWT in after() and we don't want to lock the AWT long, + * while myDefinitelyUntrackedFiles is modified along with native request to Git. + *

* * @author Kirill Likhodedov */ @@ -84,8 +92,8 @@ public class GitUntrackedFilesHolder implements Disposable, BulkFileListener { private final GitRepositoryFiles myRepositoryFiles; private final Git myGit; - private Set myDefinitelyUntrackedFiles = new HashSet(); - private Set myPossiblyUntrackedFiles = new HashSet(); + private final Set myDefinitelyUntrackedFiles = new HashSet(); + private final Set myPossiblyUntrackedFiles = new HashSet(); private boolean myReady; // if false, total refresh is needed private final Object LOCK = new Object(); private final GitRepositoryManager myRepositoryManager; @@ -111,8 +119,10 @@ public class GitUntrackedFilesHolder implements Disposable, BulkFileListener { @Override public void dispose() { - synchronized (LOCK) { + synchronized (myDefinitelyUntrackedFiles) { myDefinitelyUntrackedFiles.clear(); + } + synchronized (LOCK) { myPossiblyUntrackedFiles.clear(); } } @@ -121,10 +131,8 @@ public class GitUntrackedFilesHolder implements Disposable, BulkFileListener { * Adds the file to the list of untracked. */ public void add(@NotNull VirtualFile file) { - synchronized (LOCK) { - if (myReady) { - myDefinitelyUntrackedFiles.add(file); - } + synchronized (myDefinitelyUntrackedFiles) { + myDefinitelyUntrackedFiles.add(file); } } @@ -132,10 +140,8 @@ public class GitUntrackedFilesHolder implements Disposable, BulkFileListener { * Removes several files from untracked. */ public void remove(@NotNull Collection files) { - synchronized (LOCK) { - if (myReady) { - myDefinitelyUntrackedFiles.removeAll(files); - } + synchronized (myDefinitelyUntrackedFiles) { + myDefinitelyUntrackedFiles.removeAll(files); } } @@ -152,7 +158,7 @@ public class GitUntrackedFilesHolder implements Disposable, BulkFileListener { } else { rescanAll(); } - synchronized (LOCK) { + synchronized (myDefinitelyUntrackedFiles) { return myDefinitelyUntrackedFiles; } } @@ -168,8 +174,11 @@ public class GitUntrackedFilesHolder implements Disposable, BulkFileListener { */ public void rescanAll() throws VcsException { Set untrackedFiles = myGit.untrackedFiles(myProject, myRoot, null); + synchronized (myDefinitelyUntrackedFiles) { + myDefinitelyUntrackedFiles.clear(); + myDefinitelyUntrackedFiles.addAll(untrackedFiles); + } synchronized (LOCK) { - myDefinitelyUntrackedFiles = untrackedFiles; myPossiblyUntrackedFiles.clear(); myReady = true; } @@ -191,15 +200,15 @@ public class GitUntrackedFilesHolder implements Disposable, BulkFileListener { Set suspiciousFiles = new HashSet(); synchronized (LOCK) { suspiciousFiles.addAll(myPossiblyUntrackedFiles); + myPossiblyUntrackedFiles.clear(); } - Set untrackedFiles = myGit.untrackedFiles(myProject, myRoot, suspiciousFiles); - suspiciousFiles.removeAll(untrackedFiles); - // files that were suspicious (and thus passed to 'git ls-files'), but are not untracked, are definitely tracked. - Set trackedFiles = suspiciousFiles; + synchronized (myDefinitelyUntrackedFiles) { + Set untrackedFiles = myGit.untrackedFiles(myProject, myRoot, suspiciousFiles); + suspiciousFiles.removeAll(untrackedFiles); + // files that were suspicious (and thus passed to 'git ls-files'), but are not untracked, are definitely tracked. + Set trackedFiles = suspiciousFiles; - synchronized (LOCK) { - myPossiblyUntrackedFiles.clear(); myDefinitelyUntrackedFiles.addAll(untrackedFiles); myDefinitelyUntrackedFiles.removeAll(trackedFiles); }