Browse Source

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>
stable-4.5
Nasser Grainawi 7 years ago committed by Matthias Sohn
parent
commit
d13918310f
  1. 1
      org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
  2. 1
      org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
  3. 85
      org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java

1
org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties

@ -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

1
org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java

@ -182,6 +182,7 @@ public class JGitText extends TranslationBundle {
/***/ 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;

85
org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java

@ -65,13 +65,19 @@ import org.eclipse.jgit.lib.Constants;
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,41 +141,58 @@ public class FileBasedConfig extends StoredConfig {
*/ */
@Override @Override
public void load() throws IOException, ConfigInvalidException { public void load() throws IOException, ConfigInvalidException {
final FileSnapshot oldSnapshot = snapshot; final int maxStaleRetries = 5;
final FileSnapshot newSnapshot = FileSnapshot.save(getFile()); int retries = 0;
try { while (true) {
final byte[] in = IO.readFully(getFile()); final FileSnapshot oldSnapshot = snapshot;
final ObjectId newHash = hash(in); final FileSnapshot newSnapshot = FileSnapshot.save(getFile());
if (hash.equals(newHash)) { try {
if (oldSnapshot.equals(newSnapshot)) final byte[] in = IO.readFully(getFile());
oldSnapshot.setClean(newSnapshot); final ObjectId newHash = hash(in);
else if (hash.equals(newHash)) {
snapshot = newSnapshot; if (oldSnapshot.equals(newSnapshot)) {
} else { oldSnapshot.setClean(newSnapshot);
final String decoded; } else {
if (isUtf8(in)) { snapshot = newSnapshot;
decoded = RawParseUtils.decode(RawParseUtils.UTF8_CHARSET, }
in, 3, in.length);
utf8Bom = true;
} else { } else {
decoded = RawParseUtils.decode(in); final String decoded;
if (isUtf8(in)) {
decoded = RawParseUtils.decode(
RawParseUtils.UTF8_CHARSET, in, 3, in.length);
utf8Bom = true;
} else {
decoded = RawParseUtils.decode(in);
}
fromText(decoded);
snapshot = newSnapshot;
hash = newHash;
} }
fromText(decoded); return;
} catch (FileNotFoundException noFile) {
if (configFile.exists()) {
throw noFile;
}
clear();
snapshot = newSnapshot; snapshot = newSnapshot;
hash = newHash; return;
} } catch (IOException e) {
} catch (FileNotFoundException noFile) { if (FileUtils.isStaleFileHandle(e)
if (configFile.exists()) { && retries < maxStaleRetries) {
throw noFile; 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) {
throw new ConfigInvalidException(MessageFormat
.format(JGitText.get().cannotReadFile, getFile()), e);
} }
clear();
snapshot = newSnapshot;
} catch (IOException e) {
final IOException e2 = new IOException(MessageFormat.format(JGitText.get().cannotReadFile, getFile()));
e2.initCause(e);
throw e2;
} catch (ConfigInvalidException e) {
throw new ConfigInvalidException(MessageFormat.format(JGitText.get().cannotReadFile, getFile()), e);
} }
} }

Loading…
Cancel
Save