--- /dev/null
+/*
+ * 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) && 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;
+ }
+}
* 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;
boolean tryAgain1() {
final PackList old = packList.get();
- if (old.tryAgain(packDirectory.lastModified()))
+ if (old.snapshot.isModified(packDirectory))
return old != scanPacks(old);
return false;
}
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));
}
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();
}
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;
// 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) {
}
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;
}
}