From ed2e515ddff192ee8fd121e8bb6566b4e5ab33f2 Mon Sep 17 00:00:00 2001 From: Matthias Sohn Date: Wed, 22 May 2019 08:03:29 +0200 Subject: Fix API problem filters Change-Id: I566391d7c51875f30cf580d64e6784819985709f Signed-off-by: Matthias Sohn --- org.eclipse.jgit/.settings/.api_filters | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'org.eclipse.jgit/.settings') diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters index fdacadf9a1..da0a3f44c4 100644 --- a/org.eclipse.jgit/.settings/.api_filters +++ b/org.eclipse.jgit/.settings/.api_filters @@ -3,7 +3,7 @@ - + -- cgit v1.2.3 From b513b7747713a505e19e237ac2e7f8d9c699bc4d Mon Sep 17 00:00:00 2001 From: Matthias Sohn Date: Sun, 5 May 2019 03:18:23 +0200 Subject: Measure file timestamp resolution used in FileSnapshot FileSnapshot.notRacyClean() assumed a worst case filesystem timestamp resolution of 2.5 sec (FAT has a resolution of 2 sec). Instead measure timestamp resolution to avoid unnecessary IO caused by false positives in detecting the racy git problem caused by finite filesystem timestamp resolution [1]. Cache the measured resolution per FileStore since timestamp resolution depends on the respective filesystem type. If timestamp resolution cannot be measured or fails due to an exception fallback to the worst case FAT timestamp resolution and avoid caching this value. Add a 10% safety margin in FileSnapshot.notRacyClean(), though running FsTest.testFsTimestampResolution() 1000 times which is not using a safety margin didn't fail on Mac using APFS and Java 8, 11, 12. Measured Java file timestamp resolution: [2] [1] https://github.com/git/git/blob/master/Documentation/technical/racy-git.txt [2] https://docs.google.com/spreadsheets/d/1imy0y6WmRqBf0kjCxzxj2X7M50eIVfa7oaUIzEOHmjo Bug: 546891 Change-Id: I493f3b57b6b306285ffa7d392339d253e5966ab8 Signed-off-by: Matthias Sohn --- .../org/eclipse/jgit/junit/RepositoryTestCase.java | 4 +- .../tst/org/eclipse/jgit/util/FSTest.java | 37 ++++++++ org.eclipse.jgit/.settings/.api_filters | 14 +++ .../jgit/internal/storage/file/FileSnapshot.java | 36 +++++--- org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java | 100 +++++++++++++++++++++ .../src/org/eclipse/jgit/util/FileUtils.java | 15 ++++ 6 files changed, 192 insertions(+), 14 deletions(-) (limited to 'org.eclipse.jgit/.settings') diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java index 95fe18b83c..5eddb3d08c 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java @@ -374,9 +374,7 @@ public abstract class RepositoryTestCase extends LocalDiskRepositoryTestCase { while (actTime <= startTime) { Thread.sleep(sleepTime); sleepTime *= 2; - try (FileOutputStream fos = new FileOutputStream(tmp)) { - // Do nothing - } + FileUtils.touch(tmp.toPath()); actTime = fs.lastModified(tmp); } return actTime; diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSTest.java index 2c8273d03c..59c8e31c03 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSTest.java @@ -52,9 +52,16 @@ import java.io.File; import java.io.IOException; import java.nio.charset.Charset; import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.FileTime; import java.nio.file.attribute.PosixFileAttributeView; import java.nio.file.attribute.PosixFilePermission; +import java.time.Duration; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.Locale; import java.util.Set; +import java.util.concurrent.TimeUnit; import org.eclipse.jgit.errors.CommandFailedException; import org.eclipse.jgit.junit.RepositoryTestCase; @@ -186,4 +193,34 @@ public class FSTest { new String[] { "this-command-does-not-exist" }, Charset.defaultCharset().name()); } + + @Test + public void testFsTimestampResolution() throws Exception { + DateTimeFormatter formatter = DateTimeFormatter + .ofPattern("uuuu-MMM-dd HH:mm:ss.nnnnnnnnn", Locale.ENGLISH) + .withZone(ZoneId.systemDefault()); + Path dir = Files.createTempDirectory("probe-filesystem"); + Duration resolution = FS.getFsTimerResolution(dir); + long resolutionNs = resolution.toNanos(); + assertTrue(resolutionNs > 0); + for (int i = 0; i < 10; i++) { + Path f = null; + try { + f = dir.resolve("testTimestampResolution" + i); + Files.createFile(f); + FileUtils.touch(f); + FileTime t1 = Files.getLastModifiedTime(f); + TimeUnit.NANOSECONDS.sleep(resolutionNs); + FileUtils.touch(f); + FileTime t2 = Files.getLastModifiedTime(f); + assertTrue(String.format( + "expected t2=%s to be larger than t1=%s\nsince file timestamp resolution was measured to be %,d ns", + formatter.format(t2.toInstant()), + formatter.format(t1.toInstant()), + Long.valueOf(resolutionNs)), t2.compareTo(t1) > 0); + } finally { + Files.delete(f); + } + } + } } diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters index da0a3f44c4..a0fafdb1b0 100644 --- a/org.eclipse.jgit/.settings/.api_filters +++ b/org.eclipse.jgit/.settings/.api_filters @@ -59,5 +59,19 @@ + + + + + + + + + + + + + + diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java index cd72c8198e..d6b5fe57e1 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java @@ -48,10 +48,12 @@ import java.io.IOException; import java.nio.file.attribute.BasicFileAttributes; import java.text.DateFormat; import java.text.SimpleDateFormat; +import java.time.Duration; import java.util.Date; import java.util.Locale; import java.util.Objects; +import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.util.FS; /** @@ -85,7 +87,8 @@ public class FileSnapshot { * 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, UNKNOWN_SIZE); + public static final FileSnapshot DIRTY = new FileSnapshot(-1, -1, + UNKNOWN_SIZE, Duration.ZERO); /** * A FileSnapshot that is clean if the file does not exist. @@ -94,7 +97,8 @@ public class FileSnapshot { * file to be clean. {@link #isModified(File)} will return false if the file * path does not exist. */ - public static final FileSnapshot MISSING_FILE = new FileSnapshot(0, 0, 0) { + public static final FileSnapshot MISSING_FILE = new FileSnapshot(0, 0, 0, + Duration.ZERO) { @Override public boolean isModified(File path) { return FS.DETECTED.exists(path); @@ -115,6 +119,8 @@ public class FileSnapshot { long read = System.currentTimeMillis(); long modified; long size; + Duration fsTimerResolution = FS + .getFsTimerResolution(path.toPath().getParent()); try { BasicFileAttributes fileAttributes = FS.DETECTED.fileAttributes(path); modified = fileAttributes.lastModifiedTime().toMillis(); @@ -123,7 +129,7 @@ public class FileSnapshot { modified = path.lastModified(); size = path.length(); } - return new FileSnapshot(read, modified, size); + return new FileSnapshot(read, modified, size, fsTimerResolution); } /** @@ -131,6 +137,11 @@ public class FileSnapshot { * already known. *

* This method should be invoked before the file is accessed. + *

+ * Note that this method cannot rely on measuring file timestamp resolution + * to avoid racy git issues caused by finite file timestamp resolution since + * it's unknown in which filesystem the file is located. Hence the worst + * case fallback for timestamp resolution is used. * * @param modified * the last modification time of the file @@ -138,7 +149,7 @@ public class FileSnapshot { */ public static FileSnapshot save(long modified) { final long read = System.currentTimeMillis(); - return new FileSnapshot(read, modified, -1); + return new FileSnapshot(read, modified, -1, Duration.ZERO); } /** Last observed modification time of the path. */ @@ -155,11 +166,16 @@ public class FileSnapshot { * When set to {@link #UNKNOWN_SIZE} the size is not considered for modification checks. */ private final long size; - private FileSnapshot(long read, long modified, long size) { + /** measured filesystem timestamp resolution */ + private Duration fsTimestampResolution; + + private FileSnapshot(long read, long modified, long size, + @NonNull Duration fsTimestampResolution) { this.lastRead = read; this.lastModified = modified; - this.cannotBeRacilyClean = notRacyClean(read); + this.fsTimestampResolution = fsTimestampResolution; this.size = size; + this.cannotBeRacilyClean = notRacyClean(read); } /** @@ -279,11 +295,9 @@ public class FileSnapshot { } private boolean notRacyClean(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; + // add a 10% safety margin + long racyNanos = (fsTimestampResolution.toNanos() + 1) * 11 / 10; + return (read - lastModified) * 1_000_000 > racyNanos; } private boolean isModified(long currLastModified) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java index 7e854c750c..180123e091 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java @@ -53,23 +53,31 @@ import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintStream; import java.nio.charset.Charset; +import java.nio.file.AccessDeniedException; +import java.nio.file.FileStore; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileTime; import java.security.AccessController; import java.security.PrivilegedAction; import java.text.MessageFormat; +import java.time.Duration; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; +import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.errors.CommandFailedException; @@ -178,6 +186,83 @@ public abstract class FS { } } + private static final class FileStoreAttributeCache { + /** + * The last modified time granularity of FAT filesystems is 2 seconds. + */ + private static final Duration FALLBACK_TIMESTAMP_RESOLUTION = Duration + .ofMillis(2000); + + private static final Map attributeCache = new ConcurrentHashMap<>(); + + static Duration getFsTimestampResolution(Path file) { + try { + Path dir = Files.isDirectory(file) ? file : file.getParent(); + if (!dir.toFile().canWrite()) { + // can not determine FileStore of an unborn directory or in + // a read-only directory + return FALLBACK_TIMESTAMP_RESOLUTION; + } + FileStore s = Files.getFileStore(dir); + FileStoreAttributeCache c = attributeCache.get(s); + if (c == null) { + c = new FileStoreAttributeCache(dir); + attributeCache.put(s, c); + if (LOG.isDebugEnabled()) { + LOG.debug(c.toString()); + } + } + return c.getFsTimestampResolution(); + + } catch (IOException | InterruptedException e) { + LOG.warn(e.getMessage(), e); + return FALLBACK_TIMESTAMP_RESOLUTION; + } + } + + private Duration fsTimestampResolution; + + Duration getFsTimestampResolution() { + return fsTimestampResolution; + } + + private FileStoreAttributeCache(Path dir) + throws IOException, InterruptedException { + Path probe = dir.resolve(".probe-" + UUID.randomUUID()); //$NON-NLS-1$ + Files.createFile(probe); + try { + FileTime startTime = Files.getLastModifiedTime(probe); + FileTime actTime = startTime; + long sleepTime = 512; + while (actTime.compareTo(startTime) <= 0) { + TimeUnit.NANOSECONDS.sleep(sleepTime); + FileUtils.touch(probe); + actTime = Files.getLastModifiedTime(probe); + // limit sleep time to max. 100ms + if (sleepTime < 100_000_000L) { + sleepTime = sleepTime * 2; + } + } + fsTimestampResolution = Duration.between(startTime.toInstant(), + actTime.toInstant()); + } catch (AccessDeniedException e) { + LOG.error(e.getLocalizedMessage(), e); + } finally { + Files.delete(probe); + } + } + + @SuppressWarnings("nls") + @Override + public String toString() { + return "FileStoreAttributeCache[" + attributeCache.keySet() + .stream() + .map(key -> "FileStore[" + key + "]: fsTimestampResolution=" + + attributeCache.get(key).getFsTimestampResolution()) + .collect(Collectors.joining(",\n")) + "]"; + } + } + /** The auto-detected implementation selected for this operating system and JRE. */ public static final FS DETECTED = detect(); @@ -219,6 +304,21 @@ public abstract class FS { return factory.detect(cygwinUsed); } + /** + * Get an estimate for the filesystem timestamp resolution from a cache of + * timestamp resolution per FileStore, if not yet available it is measured + * for a probe file under the given directory. + * + * @param dir + * the directory under which the probe file will be created to + * measure the timer resolution. + * @return measured filesystem timestamp resolution + * @since 5.2.3 + */ + public static Duration getFsTimerResolution(@NonNull Path dir) { + return FileStoreAttributeCache.getFsTimestampResolution(dir); + } + private volatile Holder userHome; private volatile Holder gitSystemConfig; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java index 97f480dd36..9bba6ca8a3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java @@ -49,6 +49,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; import java.io.File; import java.io.IOException; +import java.io.OutputStream; import java.nio.file.AtomicMoveNotSupportedException; import java.nio.file.CopyOption; import java.nio.file.Files; @@ -908,4 +909,18 @@ public class FileUtils { } return path; } + + /** + * Touch the given file + * + * @param f + * the file to touch + * @throws IOException + * @since 5.2.3 + */ + public static void touch(Path f) throws IOException { + try (OutputStream fos = Files.newOutputStream(f)) { + // touch the file + } + } } -- cgit v1.2.3 From 8bf9c668adca33166057e7137d52a509d232acb3 Mon Sep 17 00:00:00 2001 From: Matthias Sohn Date: Thu, 9 May 2019 01:23:15 +0200 Subject: Wait opening new packfile until it can't be racy anymore If - pack.waitPreventRacyPack = true (default is false) - packfile size > pack.minSizePreventRacyPack (default is 100 MB) wait after a new packfile was written and before it is opened until it cannot be racy anymore. If a new packfile is accessed while it's still racy at least the pack's index will be reread by ObjectDirectory.scanPacksImpl(). Hence it may save resources to wait one tick of the file system timer to avoid this reloading. On filesystems with a coarse timestamp resolution it may be beneficial to skip this wait for small packfiles. Bug: 546891 Change-Id: I0e8bf3d7677a025edd2e397dd2c9134ba59b1a18 Signed-off-by: Matthias Sohn --- org.eclipse.jgit/.settings/.api_filters | 56 ++++++++++++ .../jgit/internal/storage/file/FileSnapshot.java | 14 +++ .../org/eclipse/jgit/internal/storage/file/GC.java | 19 +++- .../storage/file/ObjectDirectoryPackParser.java | 18 ++++ .../jgit/internal/storage/file/PackInserter.java | 26 +++++- .../org/eclipse/jgit/storage/pack/PackConfig.java | 100 ++++++++++++++++++++- 6 files changed, 227 insertions(+), 6 deletions(-) (limited to 'org.eclipse.jgit/.settings') diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters index a0fafdb1b0..524c591912 100644 --- a/org.eclipse.jgit/.settings/.api_filters +++ b/org.eclipse.jgit/.settings/.api_filters @@ -38,6 +38,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java index 23cd5ba310..b6837d2861 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java @@ -52,6 +52,7 @@ import java.time.Duration; import java.util.Date; import java.util.Locale; import java.util.Objects; +import java.util.concurrent.TimeUnit; import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.util.FS; @@ -286,6 +287,19 @@ public class FileSnapshot { lastRead = now; } + /** + * Wait until this snapshot's file can't be racy anymore + * + * @throws InterruptedException + * if sleep was interrupted + */ + public void waitUntilNotRacy() throws InterruptedException { + while (isRacyClean(System.currentTimeMillis())) { + TimeUnit.NANOSECONDS + .sleep((fsTimestampResolution.toNanos() + 1) * 11 / 10); + } + } + /** * Compare two snapshots to see if they cache the same information. * diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java index 0c94043e18..3c830e88c1 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java @@ -1256,8 +1256,23 @@ public class GC { realExt), e); } } - - return repo.getObjectDatabase().openPack(realPack); + boolean interrupted = false; + try { + FileSnapshot snapshot = FileSnapshot.save(realPack); + if (pconfig.doWaitPreventRacyPack(snapshot.size())) { + snapshot.waitUntilNotRacy(); + } + } catch (InterruptedException e) { + interrupted = true; + } + try { + return repo.getObjectDatabase().openPack(realPack); + } finally { + if (interrupted) { + // Re-set interrupted flag + Thread.currentThread().interrupt(); + } + } } finally { if (tmpPack != null && tmpPack.exists()) tmpPack.delete(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryPackParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryPackParser.java index 0cec2d5a85..6e8a15e86d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryPackParser.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryPackParser.java @@ -65,6 +65,7 @@ import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.CoreConfig; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ProgressMonitor; +import org.eclipse.jgit.storage.pack.PackConfig; import org.eclipse.jgit.transport.PackParser; import org.eclipse.jgit.transport.PackedObjectInfo; import org.eclipse.jgit.util.FileUtils; @@ -122,9 +123,12 @@ public class ObjectDirectoryPackParser extends PackParser { /** The pack that was created, if parsing was successful. */ private PackFile newPack; + private PackConfig pconfig; + ObjectDirectoryPackParser(FileObjectDatabase odb, InputStream src) { super(odb, src); this.db = odb; + this.pconfig = new PackConfig(odb.getConfig()); this.crc = new CRC32(); this.tailDigest = Constants.newMessageDigest(); @@ -514,6 +518,15 @@ public class ObjectDirectoryPackParser extends PackParser { JGitText.get().cannotMoveIndexTo, finalIdx), e); } + boolean interrupted = false; + try { + FileSnapshot snapshot = FileSnapshot.save(finalPack); + if (pconfig.doWaitPreventRacyPack(snapshot.size())) { + snapshot.waitUntilNotRacy(); + } + } catch (InterruptedException e) { + interrupted = true; + } try { newPack = db.openPack(finalPack); } catch (IOException err) { @@ -523,6 +536,11 @@ public class ObjectDirectoryPackParser extends PackParser { if (finalIdx.exists()) FileUtils.delete(finalIdx); throw err; + } finally { + if (interrupted) { + // Re-set interrupted flag + Thread.currentThread().interrupt(); + } } return lockMessage != null ? keep : null; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInserter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInserter.java index 0ce3cc93ce..a27a2b00c3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInserter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInserter.java @@ -86,6 +86,7 @@ import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.ObjectStream; +import org.eclipse.jgit.storage.pack.PackConfig; import org.eclipse.jgit.transport.PackParser; import org.eclipse.jgit.transport.PackedObjectInfo; import org.eclipse.jgit.util.BlockList; @@ -115,8 +116,11 @@ public class PackInserter extends ObjectInserter { private PackStream packOut; private Inflater cachedInflater; + private PackConfig pconfig; + PackInserter(ObjectDirectory db) { this.db = db; + this.pconfig = new PackConfig(db.getConfig()); } /** @@ -296,9 +300,25 @@ public class PackInserter extends ObjectInserter { realIdx), e); } - db.openPack(realPack); - rollback = false; - clear(); + boolean interrupted = false; + try { + FileSnapshot snapshot = FileSnapshot.save(realPack); + if (pconfig.doWaitPreventRacyPack(snapshot.size())) { + snapshot.waitUntilNotRacy(); + } + } catch (InterruptedException e) { + interrupted = true; + } + try { + db.openPack(realPack); + rollback = false; + } finally { + clear(); + if (interrupted) { + // Re-set interrupted flag + Thread.currentThread().interrupt(); + } + } } private static void writePackIndex(File idx, byte[] packHash, diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java index 256e41d22b..6bd32dd873 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java @@ -116,12 +116,30 @@ public class PackConfig { */ public static final int DEFAULT_DELTA_SEARCH_WINDOW_SIZE = 10; + private static final int MB = 1 << 20; + /** * Default big file threshold: {@value} * * @see #setBigFileThreshold(int) */ - public static final int DEFAULT_BIG_FILE_THRESHOLD = 50 * 1024 * 1024; + public static final int DEFAULT_BIG_FILE_THRESHOLD = 50 * MB; + + /** + * Default if we wait before opening a newly written pack to prevent its + * lastModified timestamp could be racy + * + * @since 5.1.8 + */ + public static final boolean DEFAULT_WAIT_PREVENT_RACY_PACK = false; + + /** + * Default if we wait before opening a newly written pack to prevent its + * lastModified timestamp could be racy + * + * @since 5.1.8 + */ + public static final long DEFAULT_MINSIZE_PREVENT_RACY_PACK = 100 * MB; /** * Default delta cache size: {@value} @@ -238,6 +256,10 @@ public class PackConfig { private int bigFileThreshold = DEFAULT_BIG_FILE_THRESHOLD; + private boolean waitPreventRacyPack = DEFAULT_WAIT_PREVENT_RACY_PACK; + + private long minSizePreventRacyPack = DEFAULT_MINSIZE_PREVENT_RACY_PACK; + private int threads; private Executor executor; @@ -314,6 +336,8 @@ public class PackConfig { this.deltaCacheSize = cfg.deltaCacheSize; this.deltaCacheLimit = cfg.deltaCacheLimit; this.bigFileThreshold = cfg.bigFileThreshold; + this.waitPreventRacyPack = cfg.waitPreventRacyPack; + this.minSizePreventRacyPack = cfg.minSizePreventRacyPack; this.threads = cfg.threads; this.executor = cfg.executor; this.indexVersion = cfg.indexVersion; @@ -736,6 +760,76 @@ public class PackConfig { this.bigFileThreshold = bigFileThreshold; } + /** + * Get whether we wait before opening a newly written pack to prevent its + * lastModified timestamp could be racy + * + * @return whether we wait before opening a newly written pack to prevent + * its lastModified timestamp could be racy + * @since 5.1.8 + */ + public boolean isWaitPreventRacyPack() { + return waitPreventRacyPack; + } + + /** + * Get whether we wait before opening a newly written pack to prevent its + * lastModified timestamp could be racy. Returns {@code true} if + * {@code waitToPreventRacyPack = true} and + * {@code packSize > minSizePreventRacyPack}, {@code false} otherwise. + * + * @param packSize + * size of the pack file + * + * @return whether we wait before opening a newly written pack to prevent + * its lastModified timestamp could be racy + * @since 5.1.8 + */ + public boolean doWaitPreventRacyPack(long packSize) { + return isWaitPreventRacyPack() + && packSize > getMinSizePreventRacyPack(); + } + + /** + * Set whether we wait before opening a newly written pack to prevent its + * lastModified timestamp could be racy + * + * @param waitPreventRacyPack + * whether we wait before opening a newly written pack to prevent + * its lastModified timestamp could be racy + * @since 5.1.8 + */ + public void setWaitPreventRacyPack(boolean waitPreventRacyPack) { + this.waitPreventRacyPack = waitPreventRacyPack; + } + + /** + * Get minimum packfile size for which we wait before opening a newly + * written pack to prevent its lastModified timestamp could be racy if + * {@code isWaitToPreventRacyPack} is {@code true}. + * + * @return minimum packfile size, default is 100 MiB + * + * @since 5.1.8 + */ + public long getMinSizePreventRacyPack() { + return minSizePreventRacyPack; + } + + /** + * Set minimum packfile size for which we wait before opening a newly + * written pack to prevent its lastModified timestamp could be racy if + * {@code isWaitToPreventRacyPack} is {@code true}. + * + * @param minSizePreventRacyPack + * minimum packfile size, default is 100 MiB + * + * @since 5.1.8 + */ + public void setMinSizePreventRacyPack(long minSizePreventRacyPack) { + this.minSizePreventRacyPack = minSizePreventRacyPack; + } + /** * Get the compression level applied to objects in the pack. * @@ -1083,6 +1177,10 @@ public class PackConfig { setBitmapInactiveBranchAgeInDays( rc.getInt("pack", "bitmapinactivebranchageindays", //$NON-NLS-1$ //$NON-NLS-2$ getBitmapInactiveBranchAgeInDays())); + setWaitPreventRacyPack(rc.getBoolean("pack", "waitpreventracypack", //$NON-NLS-1$ //$NON-NLS-2$ + isWaitPreventRacyPack())); + setMinSizePreventRacyPack(rc.getLong("pack", "minsizepreventracypack", //$NON-NLS-1$//$NON-NLS-2$ + getMinSizePreventRacyPack())); } /** {@inheritDoc} */ -- cgit v1.2.3