aboutsummaryrefslogtreecommitdiffstats
path: root/org.eclipse.jgit
diff options
context:
space:
mode:
authorMatthias Sohn <matthias.sohn@sap.com>2019-06-21 13:37:32 +0200
committerMatthias Sohn <matthias.sohn@sap.com>2019-07-11 10:12:04 +0200
commita950eac23b94cb5cce5f75b1a7eb9a436950d3ee (patch)
tree94d1465e3d2b3dd951ad464b05597d04b125e38f /org.eclipse.jgit
parenta024759cf5bf1cd6b9beb4f790d484943761a7e1 (diff)
downloadjgit-a950eac23b94cb5cce5f75b1a7eb9a436950d3ee.tar.gz
jgit-a950eac23b94cb5cce5f75b1a7eb9a436950d3ee.zip
Optionally measure filesystem timestamp resolution asynchronously
In order to avoid blocking on the main thread during measurement interactive applications like EGit may want to measure the filesystem timestamp resolution asynchronously. In order to enable measurement in the background call FileStoreAttributeCache.setAsyncfileStoreAttrCache(true) before the first access to cached FileStore attributes. Bug: 548188 Change-Id: I8c9a2dbfc3f1d33441edea18b90e36b1dc0156c7 Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
Diffstat (limited to 'org.eclipse.jgit')
-rw-r--r--org.eclipse.jgit/.settings/.api_filters6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java189
2 files changed, 144 insertions, 51 deletions
diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters
index a8404dd599..d4c40788d6 100644
--- a/org.eclipse.jgit/.settings/.api_filters
+++ b/org.eclipse.jgit/.settings/.api_filters
@@ -117,6 +117,12 @@
</filter>
<filter id="1142947843">
<message_arguments>
+ <message_argument value="5.1.9"/>
+ <message_argument value="setAsyncfileStoreAttrCache(boolean)"/>
+ </message_arguments>
+ </filter>
+ <filter id="1142947843">
+ <message_arguments>
<message_argument value="5.2.3"/>
<message_argument value="getFsTimerResolution(Path)"/>
</message_arguments>
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 e7db6cee7a..2b4e5c78d8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
@@ -55,7 +55,6 @@ 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;
@@ -71,12 +70,17 @@ import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
import org.eclipse.jgit.annotations.NonNull;
@@ -197,74 +201,143 @@ public abstract class FS {
private static final Map<FileStore, FileStoreAttributeCache> attributeCache = new ConcurrentHashMap<>();
- static Duration getFsTimestampResolution(Path file) {
+ private static AtomicBoolean background = new AtomicBoolean();
+
+ private static Map<FileStore, Lock> locks = new ConcurrentHashMap<>();
+
+ private static void setBackground(boolean async) {
+ background.set(async);
+ }
+
+ private static Duration getFsTimestampResolution(Path file) {
+ Path dir = Files.isDirectory(file) ? file : file.getParent();
+ FileStore s;
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
+ if (Files.exists(dir)) {
+ s = Files.getFileStore(dir);
+ FileStoreAttributeCache c = attributeCache.get(s);
+ if (c != null) {
+ return c.getFsTimestampResolution();
+ }
+ if (!Files.isWritable(dir)) {
+ // cannot measure resolution in a read-only directory
+ return FALLBACK_TIMESTAMP_RESOLUTION;
+ }
+ } else {
+ // cannot determine FileStore of an unborn directory
return FALLBACK_TIMESTAMP_RESOLUTION;
}
- FileStore s = Files.getFileStore(dir);
- FileStoreAttributeCache c = attributeCache.get(s);
- if (c == null) {
- c = new FileStoreAttributeCache(s, dir);
- attributeCache.put(s, c);
- if (LOG.isDebugEnabled()) {
- LOG.debug(c.toString());
- }
+ CompletableFuture<Optional<Duration>> f = CompletableFuture
+ .supplyAsync(() -> {
+ Lock lock = locks.computeIfAbsent(s,
+ l -> new ReentrantLock());
+ if (!lock.tryLock()) {
+ return Optional.empty();
+ }
+ Optional<Duration> resolution;
+ try {
+ // Some earlier future might have set the value
+ // and removed itself since we checked for the
+ // value above. Hence check cache again.
+ FileStoreAttributeCache c = attributeCache
+ .get(s);
+ if (c != null) {
+ return Optional
+ .of(c.getFsTimestampResolution());
+ }
+ resolution = measureFsTimestampResolution(s,
+ dir);
+ if (resolution.isPresent()) {
+ FileStoreAttributeCache cache = new FileStoreAttributeCache(
+ resolution.get());
+ attributeCache.put(s, cache);
+ if (LOG.isDebugEnabled()) {
+ LOG.debug(cache.toString());
+ }
+ }
+ } finally {
+ lock.unlock();
+ locks.remove(s);
+ }
+ return resolution;
+ });
+ // even if measuring in background wait a little - if the result
+ // arrives, it's better than returning the large fallback
+ Optional<Duration> d = f.get(background.get() ? 50 : 2000,
+ TimeUnit.MILLISECONDS);
+ if (d.isPresent()) {
+ return d.get();
}
- return c.getFsTimestampResolution();
-
- } catch (IOException | InterruptedException e) {
- LOG.warn(e.getMessage(), e);
- return FALLBACK_TIMESTAMP_RESOLUTION;
+ // return fallback until measurement is finished
+ } catch (IOException | InterruptedException
+ | ExecutionException e) {
+ LOG.error(e.getMessage(), e);
+ } catch (TimeoutException e) {
+ // use fallback
}
+ return FALLBACK_TIMESTAMP_RESOLUTION;
}
- private Duration fsTimestampResolution;
-
- Duration getFsTimestampResolution() {
- return fsTimestampResolution;
- }
-
- private FileStoreAttributeCache(FileStore s, Path dir)
- throws IOException, InterruptedException {
+ private static Optional<Duration> measureFsTimestampResolution(
+ FileStore s, Path dir) {
Path probe = dir.resolve(".probe-" + UUID.randomUUID()); //$NON-NLS-1$
- Files.createFile(probe);
try {
+ Files.createFile(probe);
+ long wait = 512;
long start = System.nanoTime();
- FileTime startTime = Files.getLastModifiedTime(probe);
- FileTime actTime = startTime;
- long sleepTime = 512;
- while (actTime.compareTo(startTime) <= 0) {
- TimeUnit.NANOSECONDS.sleep(sleepTime);
- if (timeout(start)) {
- LOG.warn(MessageFormat.format(JGitText
- .get().timeoutMeasureFsTimestampResolution,
- s.toString()));
- fsTimestampResolution = FALLBACK_TIMESTAMP_RESOLUTION;
- return;
- }
+ FileTime t1 = Files.getLastModifiedTime(probe);
+ FileTime t2 = t1;
+ while (t2.compareTo(t1) <= 0) {
+ TimeUnit.NANOSECONDS.sleep(wait);
+ checkTimeout(s, start);
FileUtils.touch(probe);
- actTime = Files.getLastModifiedTime(probe);
- // limit sleep time to max. 100ms
- if (sleepTime < 100_000_000L) {
- sleepTime = sleepTime * 2;
+ t2 = Files.getLastModifiedTime(probe);
+ if (wait < 100_000_000L) {
+ wait = wait * 2;
}
}
- fsTimestampResolution = Duration.between(startTime.toInstant(),
- actTime.toInstant());
- } catch (AccessDeniedException e) {
+ return Optional
+ .of(Duration.between(t1.toInstant(), t2.toInstant()));
+ } catch (IOException | TimeoutException e) {
+ LOG.error(e.getLocalizedMessage(), e);
+ } catch (InterruptedException e) {
LOG.error(e.getLocalizedMessage(), e);
+ Thread.currentThread().interrupt();
} finally {
- Files.delete(probe);
+ deleteProbe(probe);
}
+ return Optional.empty();
}
- private static boolean timeout(long start) {
- return System.nanoTime() - start >= FALLBACK_TIMESTAMP_RESOLUTION
- .toNanos();
+ private static void checkTimeout(FileStore s, long start)
+ throws TimeoutException {
+ if (System.nanoTime() - start >= FALLBACK_TIMESTAMP_RESOLUTION
+ .toNanos()) {
+ throw new TimeoutException(MessageFormat.format(JGitText
+ .get().timeoutMeasureFsTimestampResolution,
+ s.toString()));
+ }
+ }
+ private static void deleteProbe(Path probe) {
+ if (Files.exists(probe)) {
+ try {
+ Files.delete(probe);
+ } catch (IOException e) {
+ LOG.error(e.getLocalizedMessage(), e);
+ }
+ }
+ }
+
+ private final @NonNull Duration fsTimestampResolution;
+
+ @NonNull
+ Duration getFsTimestampResolution() {
+ return fsTimestampResolution;
+ }
+
+ private FileStoreAttributeCache(
+ @NonNull Duration fsTimestampResolution) {
+ this.fsTimestampResolution = fsTimestampResolution;
}
@SuppressWarnings("nls")
@@ -293,6 +366,20 @@ public abstract class FS {
}
/**
+ * Whether FileStore attribute cache entries should be determined
+ * asynchronously
+ *
+ * @param asynch
+ * whether FileStore attribute cache entries should be determined
+ * asynchronously. If false access to cached attributes may block
+ * for some seconds for the first call per FileStore
+ * @since 5.1.9
+ */
+ public static void setAsyncfileStoreAttrCache(boolean asynch) {
+ FileStoreAttributeCache.setBackground(asynch);
+ }
+
+ /**
* Auto-detect the appropriate file system abstraction, taking into account
* the presence of a Cygwin installation on the system. Using jgit in
* combination with Cygwin requires a more elaborate (and possibly slower)