Retry stale file handles on .git/config file
On a local non-NFS filesystem the .git/config file will be orphaned if it is replaced by a new process while the current process is reading the old file. The current process successfully continues to read the orphaned file until it closes the file handle. Since NFS servers do not keep track of open files, instead of orphaning the old .git/config file, such a replacement on an NFS filesystem will instead cause the old file to be garbage collected (deleted). A stale file handle exception will be raised on NFS clients if the file is garbage collected (deleted) on the server while it is being read. Since we no longer have access to the old file in these cases, the previous code would just fail. However, in these cases, reopening the file and rereading it will succeed (since it will open the new replacement file). Since retrying the read is a viable strategy to deal with stale file handles on the .git/config file, implement such a strategy. Since it is possible that the .git/config file could be replaced again while rereading it, loop on stale file handle exceptions, up to 5 extra times, trying to read the .git/config file again, until we either read the new file, or find that the file no longer exists. The limit of 5 is arbitrary, and provides a safe upper bounds to prevent infinite loops consuming resources in a potential unforeseen persistent error condition. Change-Id: I6901157b9dfdbd3013360ebe3eb40af147a8c626 Signed-off-by: Nasser Grainawi <nasser@codeaurora.org> Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
This commit is contained in:
parent
7608de5e5d
commit
d13918310f
|
@ -123,6 +123,7 @@ commitMessageNotSpecified=commit message not specified
|
||||||
commitOnRepoWithoutHEADCurrentlyNotSupported=Commit on repo without HEAD currently not supported
|
commitOnRepoWithoutHEADCurrentlyNotSupported=Commit on repo without HEAD currently not supported
|
||||||
commitAmendOnInitialNotPossible=Amending is not possible on initial commit.
|
commitAmendOnInitialNotPossible=Amending is not possible on initial commit.
|
||||||
compressingObjects=Compressing objects
|
compressingObjects=Compressing objects
|
||||||
|
configHandleIsStale=config file handle is stale, {0}. retry
|
||||||
connectionFailed=connection failed
|
connectionFailed=connection failed
|
||||||
connectionTimeOut=Connection time out: {0}
|
connectionTimeOut=Connection time out: {0}
|
||||||
contextMustBeNonNegative=context must be >= 0
|
contextMustBeNonNegative=context must be >= 0
|
||||||
|
|
|
@ -182,6 +182,7 @@ public static JGitText get() {
|
||||||
/***/ public String commitOnRepoWithoutHEADCurrentlyNotSupported;
|
/***/ public String commitOnRepoWithoutHEADCurrentlyNotSupported;
|
||||||
/***/ public String commitAmendOnInitialNotPossible;
|
/***/ public String commitAmendOnInitialNotPossible;
|
||||||
/***/ public String compressingObjects;
|
/***/ public String compressingObjects;
|
||||||
|
/***/ public String configHandleIsStale;
|
||||||
/***/ public String connectionFailed;
|
/***/ public String connectionFailed;
|
||||||
/***/ public String connectionTimeOut;
|
/***/ public String connectionTimeOut;
|
||||||
/***/ public String contextMustBeNonNegative;
|
/***/ public String contextMustBeNonNegative;
|
||||||
|
|
|
@ -65,13 +65,19 @@
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
import org.eclipse.jgit.lib.StoredConfig;
|
import org.eclipse.jgit.lib.StoredConfig;
|
||||||
import org.eclipse.jgit.util.FS;
|
import org.eclipse.jgit.util.FS;
|
||||||
|
import org.eclipse.jgit.util.FileUtils;
|
||||||
import org.eclipse.jgit.util.IO;
|
import org.eclipse.jgit.util.IO;
|
||||||
import org.eclipse.jgit.util.RawParseUtils;
|
import org.eclipse.jgit.util.RawParseUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The configuration file that is stored in the file of the file system.
|
* The configuration file that is stored in the file of the file system.
|
||||||
*/
|
*/
|
||||||
public class FileBasedConfig extends StoredConfig {
|
public class FileBasedConfig extends StoredConfig {
|
||||||
|
private final static Logger LOG = LoggerFactory
|
||||||
|
.getLogger(FileBasedConfig.class);
|
||||||
|
|
||||||
private final File configFile;
|
private final File configFile;
|
||||||
|
|
||||||
private boolean utf8Bom;
|
private boolean utf8Bom;
|
||||||
|
@ -135,21 +141,25 @@ public final File getFile() {
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void load() throws IOException, ConfigInvalidException {
|
public void load() throws IOException, ConfigInvalidException {
|
||||||
|
final int maxStaleRetries = 5;
|
||||||
|
int retries = 0;
|
||||||
|
while (true) {
|
||||||
final FileSnapshot oldSnapshot = snapshot;
|
final FileSnapshot oldSnapshot = snapshot;
|
||||||
final FileSnapshot newSnapshot = FileSnapshot.save(getFile());
|
final FileSnapshot newSnapshot = FileSnapshot.save(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);
|
||||||
if (hash.equals(newHash)) {
|
if (hash.equals(newHash)) {
|
||||||
if (oldSnapshot.equals(newSnapshot))
|
if (oldSnapshot.equals(newSnapshot)) {
|
||||||
oldSnapshot.setClean(newSnapshot);
|
oldSnapshot.setClean(newSnapshot);
|
||||||
else
|
} else {
|
||||||
snapshot = newSnapshot;
|
snapshot = newSnapshot;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
final String decoded;
|
final String decoded;
|
||||||
if (isUtf8(in)) {
|
if (isUtf8(in)) {
|
||||||
decoded = RawParseUtils.decode(RawParseUtils.UTF8_CHARSET,
|
decoded = RawParseUtils.decode(
|
||||||
in, 3, in.length);
|
RawParseUtils.UTF8_CHARSET, in, 3, in.length);
|
||||||
utf8Bom = true;
|
utf8Bom = true;
|
||||||
} else {
|
} else {
|
||||||
decoded = RawParseUtils.decode(in);
|
decoded = RawParseUtils.decode(in);
|
||||||
|
@ -158,18 +168,31 @@ public void load() throws IOException, ConfigInvalidException {
|
||||||
snapshot = newSnapshot;
|
snapshot = newSnapshot;
|
||||||
hash = newHash;
|
hash = newHash;
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
} catch (FileNotFoundException noFile) {
|
} catch (FileNotFoundException noFile) {
|
||||||
if (configFile.exists()) {
|
if (configFile.exists()) {
|
||||||
throw noFile;
|
throw noFile;
|
||||||
}
|
}
|
||||||
clear();
|
clear();
|
||||||
snapshot = newSnapshot;
|
snapshot = newSnapshot;
|
||||||
|
return;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
final IOException e2 = new IOException(MessageFormat.format(JGitText.get().cannotReadFile, getFile()));
|
if (FileUtils.isStaleFileHandle(e)
|
||||||
e2.initCause(e);
|
&& retries < maxStaleRetries) {
|
||||||
throw e2;
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug(MessageFormat.format(
|
||||||
|
JGitText.get().configHandleIsStale,
|
||||||
|
Integer.valueOf(retries)), e);
|
||||||
|
}
|
||||||
|
retries++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
throw new IOException(MessageFormat
|
||||||
|
.format(JGitText.get().cannotReadFile, getFile()), e);
|
||||||
} catch (ConfigInvalidException e) {
|
} catch (ConfigInvalidException e) {
|
||||||
throw new ConfigInvalidException(MessageFormat.format(JGitText.get().cannotReadFile, getFile()), e);
|
throw new ConfigInvalidException(MessageFormat
|
||||||
|
.format(JGitText.get().cannotReadFile, getFile()), e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue