]> source.dussan.org Git - jgit.git/commitdiff
Extract pack directory last modified check code 14/2114/2
authorShawn O. Pearce <spearce@spearce.org>
Mon, 13 Dec 2010 20:23:07 +0000 (12:23 -0800)
committerShawn O. Pearce <spearce@spearce.org>
Wed, 15 Dec 2010 23:14:05 +0000 (15:14 -0800)
Pulling the last modified checking logic out of ObjectDirectory
makes it possible to reuse this code for other files, such as
the $GIT_DIR/config or $GIT_DIR/packed-refs files.

Change-Id: If2f27a89fc3b7adde7e65ff40bbca5d55b98b772
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileSnapshot.java [new file with mode: 0644]
org.eclipse.jgit/src/org/eclipse/jgit/storage/file/ObjectDirectory.java

diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileSnapshot.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileSnapshot.java
new file mode 100644 (file)
index 0000000..c1ce449
--- /dev/null
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.storage.file;
+
+import java.io.File;
+
+import org.eclipse.jgit.util.SystemReader;
+
+/**
+ * Caches when a file was last read, making it possible to detect future edits.
+ * <p>
+ * This object tracks the last modified time of a file. Later during an
+ * invocation of {@link #isModified(File)} the object will return true if the
+ * file may have been modified and should be re-read from disk.
+ * <p>
+ * A snapshot does not "live update" when the underlying filesystem changes.
+ * Callers must poll for updates by periodically invoking
+ * {@link #isModified(File)}.
+ * <p>
+ * To work around the "racy git" problem (where a file may be modified multiple
+ * times within the granularity of the filesystem modification clock) this class
+ * may return true from isModified(File) if the last modification time of the
+ * file is less than 3 seconds ago.
+ */
+public class FileSnapshot {
+       /**
+        * A FileSnapshot that is considered to always be modified.
+        * <p>
+        * This instance is useful for application code that wants to lazily read a
+        * file, but only after {@link #isModified(File)} gets invoked. The returned
+        * snapshot contains only invalid status information.
+        */
+       public static final FileSnapshot DIRTY = new FileSnapshot(-1, -1);
+
+       /**
+        * Record a snapshot for a specific file path.
+        * <p>
+        * This method should be invoked before the file is accessed.
+        *
+        * @param path
+        *            the path to later remember. The path's current status
+        *            information is saved.
+        * @return the snapshot.
+        */
+       public static FileSnapshot save(File path) {
+               final long read = SystemReader.getInstance().getCurrentTime();
+               final long modified = path.lastModified();
+               return new FileSnapshot(read, modified);
+       }
+
+       /** Last observed modification time of the path. */
+       private final long lastModified;
+
+       /** Last wall-clock time the path was read. */
+       private volatile long lastRead;
+
+       /** True once {@link #lastRead} is far later than {@link #lastModified}. */
+       private boolean cannotBeRacilyClean;
+
+       private FileSnapshot(long read, long modified) {
+               this.lastRead = read;
+               this.lastModified = modified;
+               this.cannotBeRacilyClean = notRacyClean(read);
+       }
+
+       /**
+        * Check if the path has been modified since the snapshot was saved.
+        *
+        * @param path
+        *            the path the snapshot describes.
+        * @return true if the path needs to be read again.
+        */
+       public boolean isModified(File path) {
+               return isModified(path.lastModified());
+       }
+
+       /**
+        * Update this snapshot when the content hasn't changed.
+        * <p>
+        * If the caller gets true from {@link #isModified(File)}, re-reads the
+        * content, discovers the content is identical, and
+        * {@link #equals(FileSnapshot)} is true, it can use
+        * {@link #setClean(FileSnapshot)} to make a future
+        * {@link #isModified(File)} return false. The logic goes something like
+        * this:
+        *
+        * <pre>
+        * if (snapshot.isModified(path)) {
+        *  FileSnapshot other = FileSnapshot.save(path);
+        *  Content newContent = ...;
+        *  if (oldContent.equals(newContent) &amp;&amp; snapshot.equals(other))
+        *      snapshot.setClean(other);
+        * }
+        * </pre>
+        *
+        * @param other
+        *            the other snapshot.
+        */
+       public void setClean(FileSnapshot other) {
+               final long now = other.lastRead;
+               if (notRacyClean(now))
+                       cannotBeRacilyClean = true;
+               lastRead = now;
+       }
+
+       /**
+        * Compare two snapshots to see if they cache the same information.
+        *
+        * @param other
+        *            the other snapshot.
+        * @return true if the two snapshots share the same information.
+        */
+       public boolean equals(FileSnapshot other) {
+               return lastModified == other.lastModified;
+       }
+
+       @Override
+       public boolean equals(Object other) {
+               if (other instanceof FileSnapshot)
+                       return equals((FileSnapshot) other);
+               return false;
+       }
+
+       @Override
+       public int hashCode() {
+               // This is pretty pointless, but override hashCode to ensure that
+               // x.hashCode() == y.hashCode() when x.equals(y) is true.
+               //
+               return (int) lastModified;
+       }
+
+       private boolean notRacyClean(final long read) {
+               // The last modified time granularity of FAT filesystems is 2 seconds.
+               // Using 2.5 seconds here provides a reasonably high assurance that
+               // a modification was not missed.
+               //
+               return read - lastModified > 2500;
+       }
+
+       private boolean isModified(final long currLastModified) {
+               // Any difference indicates the path was modified.
+               //
+               if (lastModified != currLastModified)
+                       return true;
+
+               // We have already determined the last read was far enough
+               // after the last modification that any new modifications
+               // are certain to change the last modified time.
+               //
+               if (cannotBeRacilyClean)
+                       return false;
+
+               if (notRacyClean(lastRead)) {
+                       // Our last read should have marked cannotBeRacilyClean,
+                       // but this thread may not have seen the change. The read
+                       // of the volatile field lastRead should have fixed that.
+                       //
+                       return false;
+               }
+
+               // We last read this path too close to its last observed
+               // modification time. We may have missed a modification.
+               // Scan again, to ensure we still see the same state.
+               //
+               return true;
+       }
+}
index c61c773fd62c95e79932918a723dd03c03dad3cd..4620357fb3ddcddd1b9395d11e694ee66e280e68 100644 (file)
@@ -96,7 +96,8 @@ import org.eclipse.jgit.util.FileUtils;
  * considered.
  */
 public class ObjectDirectory extends FileObjectDatabase {
-       private static final PackList NO_PACKS = new PackList(-1, -1, new PackFile[0]);
+       private static final PackList NO_PACKS = new PackList(
+                       FileSnapshot.DIRTY, new PackFile[0]);
 
        /** Maximum number of candidates offered as resolutions of abbreviation. */
        private static final int RESOLVE_ABBREV_LIMIT = 256;
@@ -509,7 +510,7 @@ public class ObjectDirectory extends FileObjectDatabase {
 
        boolean tryAgain1() {
                final PackList old = packList.get();
-               if (old.tryAgain(packDirectory.lastModified()))
+               if (old.snapshot.isModified(packDirectory))
                        return old != scanPacks(old);
                return false;
        }
@@ -539,7 +540,7 @@ public class ObjectDirectory extends FileObjectDatabase {
                        final PackFile[] newList = new PackFile[1 + oldList.length];
                        newList[0] = pf;
                        System.arraycopy(oldList, 0, newList, 1, oldList.length);
-                       n = new PackList(o.lastRead, o.lastModified, newList);
+                       n = new PackList(o.snapshot, newList);
                } while (!packList.compareAndSet(o, n));
        }
 
@@ -556,7 +557,7 @@ public class ObjectDirectory extends FileObjectDatabase {
                        final PackFile[] newList = new PackFile[oldList.length - 1];
                        System.arraycopy(oldList, 0, newList, 0, j);
                        System.arraycopy(oldList, j + 1, newList, j, newList.length - j);
-                       n = new PackList(o.lastRead, o.lastModified, newList);
+                       n = new PackList(o.snapshot, newList);
                } while (!packList.compareAndSet(o, n));
                deadPack.close();
        }
@@ -590,8 +591,7 @@ public class ObjectDirectory extends FileObjectDatabase {
 
        private PackList scanPacksImpl(final PackList old) {
                final Map<String, PackFile> forReuse = reuseMap(old);
-               final long lastRead = System.currentTimeMillis();
-               final long lastModified = packDirectory.lastModified();
+               final FileSnapshot snapshot = FileSnapshot.save(packDirectory);
                final Set<String> names = listPackDirectory();
                final List<PackFile> list = new ArrayList<PackFile>(names.size() >> 2);
                boolean foundNew = false;
@@ -628,19 +628,21 @@ public class ObjectDirectory extends FileObjectDatabase {
                // the same as the set we were given. Instead of building a new object
                // return the same collection.
                //
-               if (!foundNew && lastModified == old.lastModified && forReuse.isEmpty())
-                       return old.updateLastRead(lastRead);
+               if (!foundNew && forReuse.isEmpty() && snapshot.equals(old.snapshot)) {
+                       old.snapshot.setClean(snapshot);
+                       return old;
+               }
 
                for (final PackFile p : forReuse.values()) {
                        p.close();
                }
 
                if (list.isEmpty())
-                       return new PackList(lastRead, lastModified, NO_PACKS.packs);
+                       return new PackList(snapshot, NO_PACKS.packs);
 
                final PackFile[] r = list.toArray(new PackFile[list.size()]);
                Arrays.sort(r, PackFile.SORT);
-               return new PackList(lastRead, lastModified, r);
+               return new PackList(snapshot, r);
        }
 
        private static Map<String, PackFile> reuseMap(final PackList old) {
@@ -737,62 +739,15 @@ public class ObjectDirectory extends FileObjectDatabase {
        }
 
        private static final class PackList {
-               /** Last wall-clock time the directory was read. */
-               volatile long lastRead;
-
-               /** Last modification time of {@link ObjectDirectory#packDirectory}. */
-               final long lastModified;
+               /** State just before reading the pack directory. */
+               final FileSnapshot snapshot;
 
                /** All known packs, sorted by {@link PackFile#SORT}. */
                final PackFile[] packs;
 
-               private boolean cannotBeRacilyClean;
-
-               PackList(final long lastRead, final long lastModified,
-                               final PackFile[] packs) {
-                       this.lastRead = lastRead;
-                       this.lastModified = lastModified;
+               PackList(final FileSnapshot monitor, final PackFile[] packs) {
+                       this.snapshot = monitor;
                        this.packs = packs;
-                       this.cannotBeRacilyClean = notRacyClean(lastRead);
-               }
-
-               private boolean notRacyClean(final long read) {
-                       return read - lastModified > 2 * 60 * 1000L;
-               }
-
-               PackList updateLastRead(final long now) {
-                       if (notRacyClean(now))
-                               cannotBeRacilyClean = true;
-                       lastRead = now;
-                       return this;
-               }
-
-               boolean tryAgain(final long currLastModified) {
-                       // Any difference indicates the directory was modified.
-                       //
-                       if (lastModified != currLastModified)
-                               return true;
-
-                       // We have already determined the last read was far enough
-                       // after the last modification that any new modifications
-                       // are certain to change the last modified time.
-                       //
-                       if (cannotBeRacilyClean)
-                               return false;
-
-                       if (notRacyClean(lastRead)) {
-                               // Our last read should have marked cannotBeRacilyClean,
-                               // but this thread may not have seen the change. The read
-                               // of the volatile field lastRead should have fixed that.
-                               //
-                               return false;
-                       }
-
-                       // We last read this directory too close to its last observed
-                       // modification time. We may have missed a modification. Scan
-                       // the directory again, to ensure we still see the same state.
-                       //
-                       return true;
                }
        }