Persist minimal racy threshold and allow manual configuration

To enable persisting the minimal racy threshold per FileStore add a
new config option to the user global git configuration:

- Config section is "filesystem"
- Config subsection is concatenation of
  - Java vendor (system property "java.vendor")
  - Java version (system property "java.version")
  - FileStore's name, on Windows we use the attribute volume:vsn instead
    since  the name is not necessarily unique.
  - separated by '|'
  e.g.
    "AdoptOpenJDK|1.8.0_212-b03|/dev/disk1s1"
  The same prefix is used as for filesystem timestamp resolution, so
  both values are stored in the same config section
- The config key for minmal racy threshold is "minRacyThreshold" as a
  time value, supported time units are those supported by
  DefaultTypedConfigGetter#getTimeUnit
- measure for 3 seconds to limit runtime which depends on hardware, OS
  and Java version being used

If the minimal racy threshold is configured for a given FileStore the
configured value is used instead of measuring it.

When the minimal racy threshold was measured it is persisted in the user
global git configuration.

Rename FileStoreAttributeCache to FileStoreAttributes since this class
is now declared public in order to enable exposing all attributes in one
object.

Example:

[filesystem "AdoptOpenJDK|11.0.3|/dev/disk1s1"]
	timestampResolution = 7000 nanoseconds
	minRacyThreshold = 3440 microseconds

Change-Id: I22195e488453aae8d011b0a8e3276fe3d99deaea
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
Also-By: Marc Strapetz <marc.strapetz@syntevo.com>
This commit is contained in:
Matthias Sohn 2019-07-17 16:31:42 +02:00
parent 5911521ba6
commit d45219baac
12 changed files with 195 additions and 94 deletions

View File

@ -66,7 +66,7 @@ public class GitCloneTaskTest extends LocalDiskRepositoryTestCase {
@Before @Before
public void before() throws IOException { public void before() throws IOException {
dest = createTempFile(); dest = createTempFile();
FS.getFileStoreAttributeCache(dest.toPath().getParent()); FS.getFileStoreAttributes(dest.toPath().getParent());
project = new Project(); project = new Project();
project.init(); project.init();
enableLogging(); enableLogging();

View File

@ -130,7 +130,7 @@ public void setUp() throws Exception {
// measure timer resolution before the test to avoid time critical tests // measure timer resolution before the test to avoid time critical tests
// are affected by time needed for measurement // are affected by time needed for measurement
FS.getFileStoreAttributeCache(tmp.toPath().getParent()); FS.getFileStoreAttributes(tmp.toPath().getParent());
mockSystemReader = new MockSystemReader(); mockSystemReader = new MockSystemReader();
mockSystemReader.userGitConfig = new FileBasedConfig(new File(tmp, mockSystemReader.userGitConfig = new FileBasedConfig(new File(tmp,

View File

@ -378,7 +378,7 @@ public static Instant fsTick(File lastFile)
tmp = File.createTempFile("fsTickTmpFile", null, tmp = File.createTempFile("fsTickTmpFile", null,
lastFile.getParentFile()); lastFile.getParentFile());
} }
long res = FS.getFileStoreAttributeCache(tmp.toPath()) long res = FS.getFileStoreAttributes(tmp.toPath())
.getFsTimestampResolution().toNanos(); .getFsTimestampResolution().toNanos();
long sleepTime = res / 10; long sleepTime = res / 10;
try { try {

View File

@ -123,7 +123,7 @@ public void setup() throws Exception {
// measure timer resolution before the test to avoid time critical tests // measure timer resolution before the test to avoid time critical tests
// are affected by time needed for measurement // are affected by time needed for measurement
FS.getFileStoreAttributeCache(tmp.getParent()); FS.getFileStoreAttributes(tmp.getParent());
server = new AppServer(); server = new AppServer();
ServletContextHandler app = server.addContext("/lfs"); ServletContextHandler app = server.addContext("/lfs");

View File

@ -62,7 +62,7 @@
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FS.FileStoreAttributeCache; import org.eclipse.jgit.util.FS.FileStoreAttributes;
import org.eclipse.jgit.util.FileUtils; import org.eclipse.jgit.util.FileUtils;
import org.eclipse.jgit.util.Stats; import org.eclipse.jgit.util.Stats;
import org.eclipse.jgit.util.SystemReader; import org.eclipse.jgit.util.SystemReader;
@ -79,7 +79,7 @@ public class FileSnapshotTest {
private Path trash; private Path trash;
private FileStoreAttributeCache fsAttrCache; private FileStoreAttributes fsAttrCache;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
@ -87,7 +87,7 @@ public void setUp() throws Exception {
// measure timer resolution before the test to avoid time critical tests // measure timer resolution before the test to avoid time critical tests
// are affected by time needed for measurement // are affected by time needed for measurement
fsAttrCache = FS fsAttrCache = FS
.getFileStoreAttributeCache(trash.getParent()); .getFileStoreAttributes(trash.getParent());
} }
@Before @Before

View File

@ -83,7 +83,7 @@ public class FileBasedConfigTest {
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
trash = Files.createTempDirectory("tmp_"); trash = Files.createTempDirectory("tmp_");
FS.getFileStoreAttributeCache(trash.getParent()); FS.getFileStoreAttributes(trash.getParent());
} }
@After @After

View File

@ -203,7 +203,7 @@ public void testFsTimestampResolution() throws Exception {
.ofPattern("uuuu-MMM-dd HH:mm:ss.nnnnnnnnn", Locale.ENGLISH) .ofPattern("uuuu-MMM-dd HH:mm:ss.nnnnnnnnn", Locale.ENGLISH)
.withZone(ZoneId.systemDefault()); .withZone(ZoneId.systemDefault());
Path dir = Files.createTempDirectory("probe-filesystem"); Path dir = Files.createTempDirectory("probe-filesystem");
Duration resolution = FS.getFileStoreAttributeCache(dir) Duration resolution = FS.getFileStoreAttributes(dir)
.getFsTimestampResolution(); .getFsTimestampResolution();
long resolutionNs = resolution.toNanos(); long resolutionNs = resolution.toNanos();
assertTrue(resolutionNs > 0); assertTrue(resolutionNs > 0);

View File

@ -49,6 +49,12 @@
<message_argument value="CONFIG_FILESYSTEM_SECTION"/> <message_argument value="CONFIG_FILESYSTEM_SECTION"/>
</message_arguments> </message_arguments>
</filter> </filter>
<filter id="1142947843">
<message_arguments>
<message_argument value="5.1.9"/>
<message_argument value="CONFIG_KEY_MIN_RACY_THRESHOLD"/>
</message_arguments>
</filter>
<filter id="1142947843"> <filter id="1142947843">
<message_arguments> <message_arguments>
<message_argument value="5.1.9"/> <message_argument value="5.1.9"/>
@ -72,6 +78,14 @@
</message_arguments> </message_arguments>
</filter> </filter>
</resource> </resource>
<resource path="src/org/eclipse/jgit/storage/file/FileBasedConfig.java" type="org.eclipse.jgit.storage.file.FileBasedConfig">
<filter id="1142947843">
<message_arguments>
<message_argument value="5.1.9"/>
<message_argument value="load(boolean)"/>
</message_arguments>
</filter>
</resource>
<resource path="src/org/eclipse/jgit/storage/pack/PackConfig.java" type="org.eclipse.jgit.storage.pack.PackConfig"> <resource path="src/org/eclipse/jgit/storage/pack/PackConfig.java" type="org.eclipse.jgit.storage.pack.PackConfig">
<filter id="336658481"> <filter id="336658481">
<message_arguments> <message_arguments>
@ -174,7 +188,7 @@
<filter id="1142947843"> <filter id="1142947843">
<message_arguments> <message_arguments>
<message_argument value="5.1.9"/> <message_argument value="5.1.9"/>
<message_argument value="getFileStoreAttributeCache(Path)"/> <message_argument value="getFileStoreAttributes(Path)"/>
</message_arguments> </message_arguments>
</filter> </filter>
<filter id="1142947843"> <filter id="1142947843">
@ -192,7 +206,7 @@
<filter id="1142947843"> <filter id="1142947843">
<message_arguments> <message_arguments>
<message_argument value="5.1.9"/> <message_argument value="5.1.9"/>
<message_argument value="setAsyncfileStoreAttrCache(boolean)"/> <message_argument value="setAsyncFileStoreAttributes(boolean)"/>
</message_arguments> </message_arguments>
</filter> </filter>
<filter id="1142947843"> <filter id="1142947843">
@ -210,11 +224,11 @@
</message_arguments> </message_arguments>
</filter> </filter>
</resource> </resource>
<resource path="src/org/eclipse/jgit/util/FS.java" type="org.eclipse.jgit.util.FS$FileStoreAttributeCache"> <resource path="src/org/eclipse/jgit/util/FS.java" type="org.eclipse.jgit.util.FS$FileStoreAttributes">
<filter id="1142947843"> <filter id="1142947843">
<message_arguments> <message_arguments>
<message_argument value="5.1.9"/> <message_argument value="5.1.9"/>
<message_argument value="FileStoreAttributeCache"/> <message_argument value="FileStoreAttributes"/>
</message_arguments> </message_arguments>
</filter> </filter>
</resource> </resource>

View File

@ -43,7 +43,7 @@
package org.eclipse.jgit.internal.storage.file; package org.eclipse.jgit.internal.storage.file;
import static org.eclipse.jgit.util.FS.FileStoreAttributeCache.FALLBACK_FILESTORE_ATTRIBUTES; import static org.eclipse.jgit.util.FS.FileStoreAttributes.FALLBACK_FILESTORE_ATTRIBUTES;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@ -58,7 +58,7 @@
import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FS.FileStoreAttributeCache; import org.eclipse.jgit.util.FS.FileStoreAttributes;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -215,7 +215,7 @@ public static FileSnapshot save(Instant modified) {
private final long size; private final long size;
/** measured FileStore attributes */ /** measured FileStore attributes */
private FileStoreAttributeCache fileStoreAttributeCache; private FileStoreAttributes fileStoreAttributeCache;
/** /**
* Object that uniquely identifies the given file, or {@code * Object that uniquely identifies the given file, or {@code
@ -254,7 +254,7 @@ protected FileSnapshot(File file, boolean useConfig) {
this.file = file; this.file = file;
this.lastRead = Instant.now(); this.lastRead = Instant.now();
this.fileStoreAttributeCache = useConfig this.fileStoreAttributeCache = useConfig
? FS.getFileStoreAttributeCache(file.toPath().getParent()) ? FS.getFileStoreAttributes(file.toPath().getParent())
: FALLBACK_FILESTORE_ATTRIBUTES; : FALLBACK_FILESTORE_ATTRIBUTES;
BasicFileAttributes fileAttributes = null; BasicFileAttributes fileAttributes = null;
try { try {
@ -293,7 +293,7 @@ private FileSnapshot(Instant read, Instant modified, long size,
this.file = null; this.file = null;
this.lastRead = read; this.lastRead = read;
this.lastModified = modified; this.lastModified = modified;
this.fileStoreAttributeCache = new FileStoreAttributeCache( this.fileStoreAttributeCache = new FileStoreAttributes(
fsTimestampResolution); fsTimestampResolution);
this.size = size; this.size = size;
this.fileKey = fileKey; this.fileKey = fileKey;

View File

@ -444,4 +444,11 @@ public final class ConfigConstants {
* @since 5.1.9 * @since 5.1.9
*/ */
public static final String CONFIG_KEY_TIMESTAMP_RESOLUTION = "timestampResolution"; public static final String CONFIG_KEY_TIMESTAMP_RESOLUTION = "timestampResolution";
/**
* The "minRacyThreshold" key
*
* @since 5.1.9
*/
public static final String CONFIG_KEY_MIN_RACY_THRESHOLD = "minRacyThreshold";
} }

View File

@ -149,13 +149,37 @@ public final File getFile() {
*/ */
@Override @Override
public void load() throws IOException, ConfigInvalidException { public void load() throws IOException, ConfigInvalidException {
load(true);
}
/**
* Load the configuration as a Git text style configuration file.
* <p>
* If the file does not exist, this configuration is cleared, and thus
* behaves the same as though the file exists, but is empty.
*
* @param useFileSnapshotWithConfig
* if {@code true} use the FileSnapshot with config, otherwise
* use it without config
* @throws IOException
* if IO failed
* @throws ConfigInvalidException
* if config is invalid
* @since 5.1.9
*/
public void load(boolean useFileSnapshotWithConfig)
throws IOException, ConfigInvalidException {
final int maxStaleRetries = 5; final int maxStaleRetries = 5;
int retries = 0; int retries = 0;
while (true) { while (true) {
final FileSnapshot oldSnapshot = snapshot; final FileSnapshot oldSnapshot = snapshot;
// don't use config in this snapshot to avoid endless recursion final FileSnapshot newSnapshot;
final FileSnapshot newSnapshot = FileSnapshot if (useFileSnapshotWithConfig) {
.saveNoConfig(getFile()); newSnapshot = FileSnapshot.save(getFile());
} else {
// don't use config in this snapshot to avoid endless recursion
newSnapshot = FileSnapshot.saveNoConfig(getFile());
}
try { try {
final byte[] in = IO.readFully(getFile()); final byte[] in = IO.readFully(getFile());
final ObjectId newHash = hash(in); final ObjectId newHash = hash(in);

View File

@ -208,9 +208,9 @@ public int getRc() {
* *
* @since 5.1.9 * @since 5.1.9
*/ */
public final static class FileStoreAttributeCache { public final static class FileStoreAttributes {
private static final Duration UNDEFINED_RESOLUTION = Duration private static final Duration UNDEFINED_DURATION = Duration
.ofNanos(Long.MAX_VALUE); .ofNanos(Long.MAX_VALUE);
/** /**
@ -218,10 +218,10 @@ public final static class FileStoreAttributeCache {
* filesystem timestamp resolution. The last modified time granularity * filesystem timestamp resolution. The last modified time granularity
* of FAT filesystems is 2 seconds. * of FAT filesystems is 2 seconds.
*/ */
public static final FileStoreAttributeCache FALLBACK_FILESTORE_ATTRIBUTES = new FileStoreAttributeCache( public static final FileStoreAttributes FALLBACK_FILESTORE_ATTRIBUTES = new FileStoreAttributes(
Duration.ofMillis(2000)); Duration.ofMillis(2000));
private static final Map<FileStore, FileStoreAttributeCache> attributeCache = new ConcurrentHashMap<>(); private static final Map<FileStore, FileStoreAttributes> attributeCache = new ConcurrentHashMap<>();
private static AtomicBoolean background = new AtomicBoolean(); private static AtomicBoolean background = new AtomicBoolean();
@ -239,22 +239,24 @@ private static void setBackground(boolean async) {
.ofMillis(10); .ofMillis(10);
/** /**
* Get the FileStoreAttributes for the given FileStore
*
* @param path * @param path
* file residing in the FileStore to get attributes for * file residing in the FileStore to get attributes for
* @return FileStoreAttributeCache entry for the given path. * @return FileStoreAttributes for the given path.
*/ */
public static FileStoreAttributeCache get(Path path) { public static FileStoreAttributes get(Path path) {
path = path.toAbsolutePath(); path = path.toAbsolutePath();
Path dir = Files.isDirectory(path) ? path : path.getParent(); Path dir = Files.isDirectory(path) ? path : path.getParent();
return getFileAttributeCache(dir); return getFileStoreAttributes(dir);
} }
private static FileStoreAttributeCache getFileAttributeCache(Path dir) { private static FileStoreAttributes getFileStoreAttributes(Path dir) {
FileStore s; FileStore s;
try { try {
if (Files.exists(dir)) { if (Files.exists(dir)) {
s = Files.getFileStore(dir); s = Files.getFileStore(dir);
FileStoreAttributeCache c = attributeCache.get(s); FileStoreAttributes c = attributeCache.get(s);
if (c != null) { if (c != null) {
return c; return c;
} }
@ -272,7 +274,8 @@ private static FileStoreAttributeCache getFileAttributeCache(Path dir) {
Thread.currentThread(), dir); Thread.currentThread(), dir);
return FALLBACK_FILESTORE_ATTRIBUTES; return FALLBACK_FILESTORE_ATTRIBUTES;
} }
CompletableFuture<Optional<FileStoreAttributeCache>> f = CompletableFuture
CompletableFuture<Optional<FileStoreAttributes>> f = CompletableFuture
.supplyAsync(() -> { .supplyAsync(() -> {
Lock lock = locks.computeIfAbsent(s, Lock lock = locks.computeIfAbsent(s,
l -> new ReentrantLock()); l -> new ReentrantLock());
@ -282,21 +285,27 @@ private static FileStoreAttributeCache getFileAttributeCache(Path dir) {
Thread.currentThread(), dir); Thread.currentThread(), dir);
return Optional.empty(); return Optional.empty();
} }
Optional<FileStoreAttributeCache> cache = Optional Optional<FileStoreAttributes> attributes = Optional
.empty(); .empty();
try { try {
// Some earlier future might have set the value // Some earlier future might have set the value
// and removed itself since we checked for the // and removed itself since we checked for the
// value above. Hence check cache again. // value above. Hence check cache again.
FileStoreAttributeCache c = attributeCache FileStoreAttributes c = attributeCache
.get(s); .get(s);
if (c != null) { if (c != null) {
return Optional.of(c); return Optional.of(c);
} }
attributes = readFromConfig(s);
if (attributes.isPresent()) {
attributeCache.put(s, attributes.get());
return attributes;
}
Optional<Duration> resolution = measureFsTimestampResolution( Optional<Duration> resolution = measureFsTimestampResolution(
s, dir); s, dir);
if (resolution.isPresent()) { if (resolution.isPresent()) {
c = new FileStoreAttributeCache( c = new FileStoreAttributes(
resolution.get()); resolution.get());
attributeCache.put(s, c); attributeCache.put(s, c);
// for high timestamp resolution measure // for high timestamp resolution measure
@ -304,24 +313,28 @@ private static FileStoreAttributeCache getFileAttributeCache(Path dir) {
if (c.fsTimestampResolution if (c.fsTimestampResolution
.toNanos() < 100_000_000L) { .toNanos() < 100_000_000L) {
c.minimalRacyInterval = measureMinimalRacyInterval( c.minimalRacyInterval = measureMinimalRacyInterval(
dir); dir);
} }
if (LOG.isDebugEnabled()) { if (LOG.isDebugEnabled()) {
LOG.debug(c.toString()); LOG.debug(c.toString());
} }
cache = Optional.of(c); saveToConfig(s, c);
} }
attributes = Optional.of(c);
} finally { } finally {
lock.unlock(); lock.unlock();
locks.remove(s); locks.remove(s);
} }
return cache; return attributes;
}); });
f.exceptionally(e -> {
LOG.error(e.getLocalizedMessage(), e);
return Optional.empty();
});
// even if measuring in background wait a little - if the result // even if measuring in background wait a little - if the result
// arrives, it's better than returning the large fallback // arrives, it's better than returning the large fallback
Optional<FileStoreAttributeCache> d = f.get( Optional<FileStoreAttributes> d = background.get() ? f.get(
background.get() ? 100 : 5000, 100, TimeUnit.MILLISECONDS) : f.get();
TimeUnit.MILLISECONDS);
if (d.isPresent()) { if (d.isPresent()) {
return d.get(); return d.get();
} }
@ -341,14 +354,16 @@ private static FileStoreAttributeCache getFileAttributeCache(Path dir) {
private static Duration measureMinimalRacyInterval(Path dir) { private static Duration measureMinimalRacyInterval(Path dir) {
LOG.debug("{}: start measure minimal racy interval in {}", //$NON-NLS-1$ LOG.debug("{}: start measure minimal racy interval in {}", //$NON-NLS-1$
Thread.currentThread(), dir); Thread.currentThread(), dir);
int n = 0;
int failures = 0; int failures = 0;
long racyNanos = 0; long racyNanos = 0;
final int COUNT = 1000;
ArrayList<Long> deltas = new ArrayList<>(); ArrayList<Long> deltas = new ArrayList<>();
Path probe = dir.resolve(".probe-" + UUID.randomUUID()); //$NON-NLS-1$ Path probe = dir.resolve(".probe-" + UUID.randomUUID()); //$NON-NLS-1$
Instant end = Instant.now().plusSeconds(3);
try { try {
Files.createFile(probe); Files.createFile(probe);
for (int i = 0; i < COUNT; i++) { do {
n++;
write(probe, "a"); //$NON-NLS-1$ write(probe, "a"); //$NON-NLS-1$
FileSnapshot snapshot = FileSnapshot.save(probe.toFile()); FileSnapshot snapshot = FileSnapshot.save(probe.toFile());
read(probe); read(probe);
@ -358,7 +373,7 @@ private static Duration measureMinimalRacyInterval(Path dir) {
racyNanos = snapshot.lastRacyThreshold(); racyNanos = snapshot.lastRacyThreshold();
failures++; failures++;
} }
} } while (Instant.now().compareTo(end) < 0);
} catch (IOException e) { } catch (IOException e) {
LOG.error(e.getMessage(), e); LOG.error(e.getMessage(), e);
return FALLBACK_MIN_RACY_INTERVAL; return FALLBACK_MIN_RACY_INTERVAL;
@ -376,7 +391,7 @@ private static Duration measureMinimalRacyInterval(Path dir) {
+ " delta max [ns], delta avg [ns]," //$NON-NLS-1$ + " delta max [ns], delta avg [ns]," //$NON-NLS-1$
+ " delta stddev [ns]\n" //$NON-NLS-1$ + " delta stddev [ns]\n" //$NON-NLS-1$
+ "{}, {}, {}, {}, {}, {}, {}", //$NON-NLS-1$ + "{}, {}, {}, {}, {}, {}, {}", //$NON-NLS-1$
COUNT, failures, racyNanos, stats.min(), stats.max(), n, failures, racyNanos, stats.min(), stats.max(),
stats.avg(), stats.stddev()); stats.avg(), stats.stddev());
return Duration return Duration
.ofNanos(Double.valueOf(stats.max()).longValue()); .ofNanos(Double.valueOf(stats.max()).longValue());
@ -405,10 +420,6 @@ private static Optional<Duration> measureFsTimestampResolution(
FileStore s, Path dir) { FileStore s, Path dir) {
LOG.debug("{}: start measure timestamp resolution {} in {}", //$NON-NLS-1$ LOG.debug("{}: start measure timestamp resolution {} in {}", //$NON-NLS-1$
Thread.currentThread(), s, dir); Thread.currentThread(), s, dir);
Duration configured = readFileTimeResolution(s);
if (!UNDEFINED_RESOLUTION.equals(configured)) {
return Optional.of(configured);
}
Path probe = dir.resolve(".probe-" + UUID.randomUUID()); //$NON-NLS-1$ Path probe = dir.resolve(".probe-" + UUID.randomUUID()); //$NON-NLS-1$
try { try {
Files.createFile(probe); Files.createFile(probe);
@ -423,7 +434,6 @@ private static Optional<Duration> measureFsTimestampResolution(
Duration fsResolution = Duration.between(t1.toInstant(), t2.toInstant()); Duration fsResolution = Duration.between(t1.toInstant(), t2.toInstant());
Duration clockResolution = measureClockResolution(); Duration clockResolution = measureClockResolution();
fsResolution = fsResolution.plus(clockResolution); fsResolution = fsResolution.plus(clockResolution);
saveFileTimeResolution(s, fsResolution);
LOG.debug("{}: end measure timestamp resolution {} in {}", //$NON-NLS-1$ LOG.debug("{}: end measure timestamp resolution {} in {}", //$NON-NLS-1$
Thread.currentThread(), s, dir); Thread.currentThread(), s, dir);
return Optional.of(fsResolution); return Optional.of(fsResolution);
@ -454,20 +464,20 @@ private static Duration measureClockResolution() {
} }
private static void deleteProbe(Path probe) { private static void deleteProbe(Path probe) {
if (Files.exists(probe)) { try {
try { FileUtils.delete(probe.toFile(),
Files.delete(probe); FileUtils.SKIP_MISSING | FileUtils.RETRY);
} catch (IOException e) { } catch (IOException e) {
LOG.error(e.getLocalizedMessage(), e); LOG.error(e.getMessage(), e);
}
} }
} }
private static Duration readFileTimeResolution(FileStore s) { private static Optional<FileStoreAttributes> readFromConfig(
FileStore s) {
FileBasedConfig userConfig = SystemReader.getInstance() FileBasedConfig userConfig = SystemReader.getInstance()
.openUserConfig(null, FS.DETECTED); .openUserConfig(null, FS.DETECTED);
try { try {
userConfig.load(); userConfig.load(false);
} catch (IOException e) { } catch (IOException e) {
LOG.error(MessageFormat.format(JGitText.get().readConfigFailed, LOG.error(MessageFormat.format(JGitText.get().readConfigFailed,
userConfig.getFile().getAbsolutePath()), e); userConfig.getFile().getAbsolutePath()), e);
@ -477,49 +487,65 @@ private static Duration readFileTimeResolution(FileStore s) {
userConfig.getFile().getAbsolutePath(), userConfig.getFile().getAbsolutePath(),
e.getMessage())); e.getMessage()));
} }
Duration configured = Duration String key = getConfigKey(s);
.ofNanos(userConfig.getTimeUnit( Duration resolution = Duration.ofNanos(userConfig.getTimeUnit(
ConfigConstants.CONFIG_FILESYSTEM_SECTION, ConfigConstants.CONFIG_FILESYSTEM_SECTION, key,
javaVersionPrefix + s.name(), ConfigConstants.CONFIG_KEY_TIMESTAMP_RESOLUTION,
ConfigConstants.CONFIG_KEY_TIMESTAMP_RESOLUTION, UNDEFINED_DURATION.toNanos(), TimeUnit.NANOSECONDS));
UNDEFINED_RESOLUTION.toNanos(), if (UNDEFINED_DURATION.equals(resolution)) {
TimeUnit.NANOSECONDS)); return Optional.empty();
return configured; }
Duration minRacyThreshold = Duration.ofNanos(userConfig.getTimeUnit(
ConfigConstants.CONFIG_FILESYSTEM_SECTION, key,
ConfigConstants.CONFIG_KEY_MIN_RACY_THRESHOLD,
UNDEFINED_DURATION.toNanos(), TimeUnit.NANOSECONDS));
FileStoreAttributes c = new FileStoreAttributes(resolution);
if (!UNDEFINED_DURATION.equals(minRacyThreshold)) {
c.minimalRacyInterval = minRacyThreshold;
}
return Optional.of(c);
} }
private static void saveFileTimeResolution(FileStore s, private static void saveToConfig(FileStore s,
Duration resolution) { FileStoreAttributes c) {
FileBasedConfig userConfig = SystemReader.getInstance() FileBasedConfig userConfig = SystemReader.getInstance()
.openUserConfig(null, FS.DETECTED); .openUserConfig(null, FS.DETECTED);
long nanos = resolution.toNanos(); long resolution = c.getFsTimestampResolution().toNanos();
TimeUnit unit; TimeUnit resolutionUnit = getUnit(resolution);
if (nanos < 200_000L) { long resolutionValue = resolutionUnit.convert(resolution,
unit = TimeUnit.NANOSECONDS; TimeUnit.NANOSECONDS);
} else if (nanos < 200_000_000L) {
unit = TimeUnit.MICROSECONDS; long minRacyThreshold = c.getMinimalRacyInterval().toNanos();
} else { TimeUnit minRacyThresholdUnit = getUnit(minRacyThreshold);
unit = TimeUnit.MILLISECONDS; long minRacyThresholdValue = minRacyThresholdUnit
} .convert(minRacyThreshold, TimeUnit.NANOSECONDS);
final int max_retries = 5; final int max_retries = 5;
int retries = 0; int retries = 0;
boolean succeeded = false; boolean succeeded = false;
long value = unit.convert(nanos, TimeUnit.NANOSECONDS); String key = getConfigKey(s);
while (!succeeded && retries < max_retries) { while (!succeeded && retries < max_retries) {
try { try {
userConfig.load(); userConfig.load(false);
userConfig.setString( userConfig.setString(
ConfigConstants.CONFIG_FILESYSTEM_SECTION, ConfigConstants.CONFIG_FILESYSTEM_SECTION, key,
javaVersionPrefix + s.name(),
ConfigConstants.CONFIG_KEY_TIMESTAMP_RESOLUTION, ConfigConstants.CONFIG_KEY_TIMESTAMP_RESOLUTION,
String.format("%d %s", //$NON-NLS-1$ String.format("%d %s", //$NON-NLS-1$
Long.valueOf(value), Long.valueOf(resolutionValue),
unit.name().toLowerCase())); resolutionUnit.name().toLowerCase()));
userConfig.setString(
ConfigConstants.CONFIG_FILESYSTEM_SECTION, key,
ConfigConstants.CONFIG_KEY_MIN_RACY_THRESHOLD,
String.format("%d %s", //$NON-NLS-1$
Long.valueOf(minRacyThresholdValue),
minRacyThresholdUnit.name().toLowerCase()));
userConfig.save(); userConfig.save();
succeeded = true; succeeded = true;
} catch (LockFailedException e) { } catch (LockFailedException e) {
// race with another thread, wait a bit and try again // race with another thread, wait a bit and try again
try { try {
LOG.warn(MessageFormat.format(JGitText.get().cannotLock,
userConfig.getFile().getAbsolutePath()));
retries++; retries++;
Thread.sleep(20); Thread.sleep(20);
} catch (InterruptedException e1) { } catch (InterruptedException e1) {
@ -538,6 +564,38 @@ private static void saveFileTimeResolution(FileStore s,
} }
} }
private static String getConfigKey(FileStore s) {
final String storeKey;
if (SystemReader.getInstance().isWindows()) {
Object attribute = null;
try {
attribute = s.getAttribute("volume:vsn"); //$NON-NLS-1$
} catch (IOException ignored) {
// ignore
}
if (attribute instanceof Integer) {
storeKey = attribute.toString();
} else {
storeKey = s.name();
}
} else {
storeKey = s.name();
}
return javaVersionPrefix + storeKey;
}
private static TimeUnit getUnit(long nanos) {
TimeUnit unit;
if (nanos < 200_000L) {
unit = TimeUnit.NANOSECONDS;
} else if (nanos < 200_000_000L) {
unit = TimeUnit.MICROSECONDS;
} else {
unit = TimeUnit.MILLISECONDS;
}
return unit;
}
private final @NonNull Duration fsTimestampResolution; private final @NonNull Duration fsTimestampResolution;
private Duration minimalRacyInterval; private Duration minimalRacyInterval;
@ -565,7 +623,7 @@ public Duration getFsTimestampResolution() {
* *
* @param fsTimestampResolution * @param fsTimestampResolution
*/ */
public FileStoreAttributeCache( public FileStoreAttributes(
@NonNull Duration fsTimestampResolution) { @NonNull Duration fsTimestampResolution) {
this.fsTimestampResolution = fsTimestampResolution; this.fsTimestampResolution = fsTimestampResolution;
this.minimalRacyInterval = Duration.ZERO; this.minimalRacyInterval = Duration.ZERO;
@ -575,7 +633,7 @@ public FileStoreAttributeCache(
@Override @Override
public String toString() { public String toString() {
return String.format( return String.format(
"FileStoreAttributeCache[fsTimestampResolution=%,d µs, " "FileStoreAttributes[fsTimestampResolution=%,d µs, "
+ "minimalRacyInterval=%,d µs]", + "minimalRacyInterval=%,d µs]",
fsTimestampResolution.toNanos() / 1000, fsTimestampResolution.toNanos() / 1000,
minimalRacyInterval.toNanos() / 1000); minimalRacyInterval.toNanos() / 1000);
@ -598,17 +656,16 @@ public static FS detect() {
} }
/** /**
* Whether FileStore attribute cache entries should be determined * Whether FileStore attributes should be determined asynchronously
* asynchronously
* *
* @param asynch * @param asynch
* whether FileStore attribute cache entries should be determined * whether FileStore attributes should be determined
* asynchronously. If false access to cached attributes may block * asynchronously. If false access to cached attributes may block
* for some seconds for the first call per FileStore * for some seconds for the first call per FileStore
* @since 5.1.9 * @since 5.1.9
*/ */
public static void setAsyncfileStoreAttrCache(boolean asynch) { public static void setAsyncFileStoreAttributes(boolean asynch) {
FileStoreAttributeCache.setBackground(asynch); FileStoreAttributes.setBackground(asynch);
} }
/** /**
@ -639,9 +696,8 @@ public static FS detect(Boolean cygwinUsed) {
} }
/** /**
* Get an estimate for the filesystem timestamp resolution from a cache of * Get cached FileStore attributes, if not yet available measure them using
* timestamp resolution per FileStore, if not yet available it is measured * a probe file under the given directory.
* for a probe file under the given directory.
* *
* @param dir * @param dir
* the directory under which the probe file will be created to * the directory under which the probe file will be created to
@ -649,9 +705,9 @@ public static FS detect(Boolean cygwinUsed) {
* @return measured filesystem timestamp resolution * @return measured filesystem timestamp resolution
* @since 5.1.9 * @since 5.1.9
*/ */
public static FileStoreAttributeCache getFileStoreAttributeCache( public static FileStoreAttributes getFileStoreAttributes(
@NonNull Path dir) { @NonNull Path dir) {
return FileStoreAttributeCache.get(dir); return FileStoreAttributes.get(dir);
} }
private volatile Holder<File> userHome; private volatile Holder<File> userHome;