Extract pack directory last modified check code
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>
This commit is contained in:
parent
6f3b4d5d04
commit
c8db22f355
|
@ -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) && 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;
|
||||
}
|
||||
}
|
|
@ -96,7 +96,8 @@
|
|||
* 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 @@ InsertLooseObjectResult insertUnpackedObject(File tmp, ObjectId id,
|
|||
|
||||
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 @@ private void insertPack(final PackFile pf) {
|
|||
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 @@ private void removePack(final PackFile deadPack) {
|
|||
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 @@ private PackList scanPacks(final PackList original) {
|
|||
|
||||
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 @@ private PackList scanPacksImpl(final PackList old) {
|
|||
// 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 @@ private AlternateHandle openAlternate(File objdir) throws IOException {
|
|||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue