* stable-5.11: Retry loose object read upon "Stale file handle" exception Ignore missing javadoc in test bundles Change-Id: Ia4dc886c920cec3c9da86e1a90a0af68bd016b4fchanges/89/182489/1
org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled | org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled | ||||
org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=protected | org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=protected | ||||
org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=return_tag | org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=return_tag | ||||
org.eclipse.jdt.core.compiler.problem.missingJavadocTags=error | |||||
org.eclipse.jdt.core.compiler.problem.missingJavadocTags=ignore | |||||
org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled | org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled | ||||
org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled | org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled | ||||
org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=private | org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=private |
import static java.nio.charset.StandardCharsets.UTF_8; | import static java.nio.charset.StandardCharsets.UTF_8; | ||||
import static org.junit.Assert.assertFalse; | import static org.junit.Assert.assertFalse; | ||||
import static org.junit.Assert.assertNull; | |||||
import static org.junit.Assert.assertThrows; | import static org.junit.Assert.assertThrows; | ||||
import static org.junit.Assert.assertTrue; | import static org.junit.Assert.assertTrue; | ||||
import static org.mockito.ArgumentMatchers.any; | |||||
import static org.mockito.Mockito.mock; | |||||
import static org.mockito.Mockito.verify; | |||||
import java.io.File; | import java.io.File; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import org.eclipse.jgit.util.FS; | import org.eclipse.jgit.util.FS; | ||||
import org.junit.Assume; | import org.junit.Assume; | ||||
import org.junit.Test; | import org.junit.Test; | ||||
import org.mockito.Mockito; | |||||
public class ObjectDirectoryTest extends RepositoryTestCase { | public class ObjectDirectoryTest extends RepositoryTestCase { | ||||
assertTrue(shallowCommits.isEmpty()); | assertTrue(shallowCommits.isEmpty()); | ||||
} | } | ||||
@Test | |||||
public void testOpenLooseObjectSuppressStaleFileHandleException() | |||||
throws Exception { | |||||
ObjectId id = ObjectId | |||||
.fromString("873fb8d667d05436d728c52b1d7a09528e6eb59b"); | |||||
WindowCursor curs = new WindowCursor(db.getObjectDatabase()); | |||||
LooseObjects mock = mock(LooseObjects.class); | |||||
UnpackedObjectCache unpackedObjectCacheMock = mock( | |||||
UnpackedObjectCache.class); | |||||
Mockito.when(mock.getObjectLoader(any(), any(), any())) | |||||
.thenThrow(new IOException("Stale File Handle")); | |||||
Mockito.when(mock.open(curs, id)).thenCallRealMethod(); | |||||
Mockito.when(mock.unpackedObjectCache()) | |||||
.thenReturn(unpackedObjectCacheMock); | |||||
assertNull(mock.open(curs, id)); | |||||
verify(unpackedObjectCacheMock).remove(id); | |||||
} | |||||
@Test | |||||
public void testOpenLooseObjectPropagatesIOExceptions() throws Exception { | |||||
ObjectId id = ObjectId | |||||
.fromString("873fb8d667d05436d728c52b1d7a09528e6eb59b"); | |||||
WindowCursor curs = new WindowCursor(db.getObjectDatabase()); | |||||
LooseObjects mock = mock(LooseObjects.class); | |||||
Mockito.when(mock.getObjectLoader(any(), any(), any())) | |||||
.thenThrow(new IOException("some IO failure")); | |||||
Mockito.when(mock.open(curs, id)).thenCallRealMethod(); | |||||
assertThrows(IOException.class, () -> mock.open(curs, id)); | |||||
} | |||||
@Test | @Test | ||||
public void testShallowFileCorrupt() throws Exception { | public void testShallowFileCorrupt() throws Exception { | ||||
FileRepository repository = createBareRepository(); | FileRepository repository = createBareRepository(); |
logLargerFiletimeDiff={}: inconsistent duration from file timestamps on {}, {}: diff = {} > {} (last good value). Aborting measurement. | logLargerFiletimeDiff={}: inconsistent duration from file timestamps on {}, {}: diff = {} > {} (last good value). Aborting measurement. | ||||
logSmallerFiletime={}: got smaller file timestamp on {}, {}: {} < {}. Aborting measurement at resolution {}. | logSmallerFiletime={}: got smaller file timestamp on {}, {}: {} < {}. Aborting measurement at resolution {}. | ||||
logXDGConfigHomeInvalid=Environment variable XDG_CONFIG_HOME contains an invalid path {} | logXDGConfigHomeInvalid=Environment variable XDG_CONFIG_HOME contains an invalid path {} | ||||
looseObjectHandleIsStale=loose-object {0} file handle is stale. retry {1} of {2} | |||||
maxCountMustBeNonNegative=max count must be >= 0 | maxCountMustBeNonNegative=max count must be >= 0 | ||||
mergeConflictOnNonNoteEntries=Merge conflict on non-note entries: base = {0}, ours = {1}, theirs = {2} | mergeConflictOnNonNoteEntries=Merge conflict on non-note entries: base = {0}, ours = {1}, theirs = {2} | ||||
mergeConflictOnNotes=Merge conflict on note {0}. base = {1}, ours = {2}, theirs = {2} | mergeConflictOnNotes=Merge conflict on note {0}. base = {1}, ours = {2}, theirs = {2} |
/***/ public String logLargerFiletimeDiff; | /***/ public String logLargerFiletimeDiff; | ||||
/***/ public String logSmallerFiletime; | /***/ public String logSmallerFiletime; | ||||
/***/ public String logXDGConfigHomeInvalid; | /***/ public String logXDGConfigHomeInvalid; | ||||
/***/ public String looseObjectHandleIsStale; | |||||
/***/ public String maxCountMustBeNonNegative; | /***/ public String maxCountMustBeNonNegative; | ||||
/***/ public String mergeConflictOnNonNoteEntries; | /***/ public String mergeConflictOnNonNoteEntries; | ||||
/***/ public String mergeConflictOnNotes; | /***/ public String mergeConflictOnNotes; |
import java.nio.file.Files; | import java.nio.file.Files; | ||||
import java.nio.file.NoSuchFileException; | import java.nio.file.NoSuchFileException; | ||||
import java.nio.file.StandardCopyOption; | import java.nio.file.StandardCopyOption; | ||||
import java.text.MessageFormat; | |||||
import java.util.Set; | import java.util.Set; | ||||
import org.eclipse.jgit.internal.JGitText; | |||||
import org.eclipse.jgit.internal.storage.file.FileObjectDatabase.InsertLooseObjectResult; | import org.eclipse.jgit.internal.storage.file.FileObjectDatabase.InsertLooseObjectResult; | ||||
import org.eclipse.jgit.lib.AbbreviatedObjectId; | import org.eclipse.jgit.lib.AbbreviatedObjectId; | ||||
import org.eclipse.jgit.lib.AnyObjectId; | import org.eclipse.jgit.lib.AnyObjectId; | ||||
private static final Logger LOG = LoggerFactory | private static final Logger LOG = LoggerFactory | ||||
.getLogger(LooseObjects.class); | .getLogger(LooseObjects.class); | ||||
/** | |||||
* Maximum number of attempts to read a loose object for which a stale file | |||||
* handle exception is thrown | |||||
*/ | |||||
private final static int MAX_LOOSE_OBJECT_STALE_READ_ATTEMPTS = 5; | |||||
private final File directory; | private final File directory; | ||||
private final UnpackedObjectCache unpackedObjectCache; | private final UnpackedObjectCache unpackedObjectCache; | ||||
} | } | ||||
void close() { | void close() { | ||||
unpackedObjectCache.clear(); | |||||
unpackedObjectCache().clear(); | |||||
} | } | ||||
/** {@inheritDoc} */ | /** {@inheritDoc} */ | ||||
} | } | ||||
boolean hasCached(AnyObjectId id) { | boolean hasCached(AnyObjectId id) { | ||||
return unpackedObjectCache.isUnpacked(id); | |||||
return unpackedObjectCache().isUnpacked(id); | |||||
} | } | ||||
/** | /** | ||||
} | } | ||||
ObjectLoader open(WindowCursor curs, AnyObjectId id) throws IOException { | ObjectLoader open(WindowCursor curs, AnyObjectId id) throws IOException { | ||||
File path = fileFor(id); | |||||
int readAttempts = 0; | |||||
while (readAttempts < MAX_LOOSE_OBJECT_STALE_READ_ATTEMPTS) { | |||||
readAttempts++; | |||||
File path = fileFor(id); | |||||
try { | |||||
return getObjectLoader(curs, path, id); | |||||
} catch (FileNotFoundException noFile) { | |||||
if (path.exists()) { | |||||
throw noFile; | |||||
} | |||||
break; | |||||
} catch (IOException e) { | |||||
if (!FileUtils.isStaleFileHandleInCausalChain(e)) { | |||||
throw e; | |||||
} | |||||
if (LOG.isDebugEnabled()) { | |||||
LOG.debug(MessageFormat.format( | |||||
JGitText.get().looseObjectHandleIsStale, id.name(), | |||||
Integer.valueOf(readAttempts), Integer.valueOf( | |||||
MAX_LOOSE_OBJECT_STALE_READ_ATTEMPTS))); | |||||
} | |||||
} | |||||
} | |||||
unpackedObjectCache().remove(id); | |||||
return null; | |||||
} | |||||
/** | |||||
* Provides a loader for an objectId | |||||
* | |||||
* @param curs | |||||
* cursor on the database | |||||
* @param path | |||||
* the path of the loose object | |||||
* @param id | |||||
* the object id | |||||
* @return a loader for the loose file object | |||||
* @throws IOException | |||||
* when file does not exist or it could not be opened | |||||
*/ | |||||
ObjectLoader getObjectLoader(WindowCursor curs, File path, AnyObjectId id) | |||||
throws IOException { | |||||
try (FileInputStream in = new FileInputStream(path)) { | try (FileInputStream in = new FileInputStream(path)) { | ||||
unpackedObjectCache.add(id); | |||||
unpackedObjectCache().add(id); | |||||
return UnpackedObject.open(in, path, id, curs); | return UnpackedObject.open(in, path, id, curs); | ||||
} catch (FileNotFoundException noFile) { | |||||
if (path.exists()) { | |||||
throw noFile; | |||||
} | |||||
unpackedObjectCache.remove(id); | |||||
return null; | |||||
} | } | ||||
} | } | ||||
/** | |||||
* <p> | |||||
* Getter for the field <code>unpackedObjectCache</code>. | |||||
* </p> | |||||
* This accessor is particularly useful to allow mocking of this class for | |||||
* testing purposes. | |||||
* | |||||
* @return the cache of the objects currently unpacked. | |||||
*/ | |||||
UnpackedObjectCache unpackedObjectCache() { | |||||
return unpackedObjectCache; | |||||
} | |||||
long getSize(WindowCursor curs, AnyObjectId id) throws IOException { | long getSize(WindowCursor curs, AnyObjectId id) throws IOException { | ||||
File f = fileFor(id); | File f = fileFor(id); | ||||
try (FileInputStream in = new FileInputStream(f)) { | try (FileInputStream in = new FileInputStream(f)) { | ||||
unpackedObjectCache.add(id); | |||||
unpackedObjectCache().add(id); | |||||
return UnpackedObject.getSize(in, id, curs); | return UnpackedObject.getSize(in, id, curs); | ||||
} catch (FileNotFoundException noFile) { | } catch (FileNotFoundException noFile) { | ||||
if (f.exists()) { | if (f.exists()) { | ||||
throw noFile; | throw noFile; | ||||
} | } | ||||
unpackedObjectCache.remove(id); | |||||
unpackedObjectCache().remove(id); | |||||
return -1; | return -1; | ||||
} | } | ||||
} | } | ||||
Files.move(FileUtils.toPath(tmp), FileUtils.toPath(dst), | Files.move(FileUtils.toPath(tmp), FileUtils.toPath(dst), | ||||
StandardCopyOption.ATOMIC_MOVE); | StandardCopyOption.ATOMIC_MOVE); | ||||
dst.setReadOnly(); | dst.setReadOnly(); | ||||
unpackedObjectCache.add(id); | |||||
unpackedObjectCache().add(id); | |||||
return InsertLooseObjectResult.INSERTED; | return InsertLooseObjectResult.INSERTED; | ||||
} | } | ||||