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