Merge branch 'stable-5.7' into stable-5.8

* stable-5.7:
  LockFile: create OutputStream only when needed
  Remove ReftableNumbersNotIncreasingException

Change-Id: Ib3f280e0741f87a0ff615d857a5ea39b35527e74
This commit is contained in:
Matthias Sohn 2021-05-11 00:51:21 +02:00
commit f2e5bace48
5 changed files with 257 additions and 73 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2012, GitHub Inc. and others * Copyright (C) 2012, 2021 GitHub Inc. and others
* *
* This program and the accompanying materials are made available under the * This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at * terms of the Eclipse Distribution License v. 1.0 which is available at
@ -9,10 +9,16 @@
*/ */
package org.eclipse.jgit.internal.storage.file; package org.eclipse.jgit.internal.storage.file;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import java.io.File;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.errors.LockFailedException; import org.eclipse.jgit.errors.LockFailedException;
@ -49,4 +55,149 @@ public void lockFailedExceptionRecovery() throws Exception {
} }
} }
} }
@Test
public void testLockTwice() throws Exception {
File f = writeTrashFile("somefile", "content");
LockFile lock = new LockFile(f);
assertTrue(lock.lock());
lock.write("other".getBytes(StandardCharsets.US_ASCII));
lock.commit();
assertFalse(lock.isLocked());
checkFile(f, "other");
assertTrue(lock.lock());
assertTrue(lock.isLocked());
try (OutputStream out = lock.getOutputStream()) {
out.write("second".getBytes(StandardCharsets.US_ASCII));
}
lock.commit();
assertFalse(lock.isLocked());
checkFile(f, "second");
}
@Test
public void testLockTwiceUnlock() throws Exception {
File f = writeTrashFile("somefile", "content");
LockFile lock = new LockFile(f);
assertTrue(lock.lock());
assertTrue(lock.isLocked());
lock.write("other".getBytes(StandardCharsets.US_ASCII));
lock.unlock();
assertFalse(lock.isLocked());
checkFile(f, "content");
assertTrue(lock.lock());
assertTrue(lock.isLocked());
try (OutputStream out = lock.getOutputStream()) {
out.write("second".getBytes(StandardCharsets.US_ASCII));
}
lock.commit();
assertFalse(lock.isLocked());
checkFile(f, "second");
}
@Test
public void testLockWriteTwiceThrows1() throws Exception {
File f = writeTrashFile("somefile", "content");
LockFile lock = new LockFile(f);
assertTrue(lock.lock());
assertTrue(lock.isLocked());
lock.write("other".getBytes(StandardCharsets.US_ASCII));
assertThrows(Exception.class,
() -> lock.write("second".getBytes(StandardCharsets.US_ASCII)));
lock.unlock();
}
@Test
public void testLockWriteTwiceThrows2() throws Exception {
File f = writeTrashFile("somefile", "content");
LockFile lock = new LockFile(f);
assertTrue(lock.lock());
assertTrue(lock.isLocked());
try (OutputStream out = lock.getOutputStream()) {
out.write("other".getBytes(StandardCharsets.US_ASCII));
}
assertThrows(Exception.class,
() -> lock.write("second".getBytes(StandardCharsets.US_ASCII)));
lock.unlock();
}
@Test
public void testLockWriteTwiceThrows3() throws Exception {
File f = writeTrashFile("somefile", "content");
LockFile lock = new LockFile(f);
assertTrue(lock.lock());
assertTrue(lock.isLocked());
lock.write("other".getBytes(StandardCharsets.US_ASCII));
assertThrows(Exception.class, () -> {
try (OutputStream out = lock.getOutputStream()) {
out.write("second".getBytes(StandardCharsets.US_ASCII));
}
});
lock.unlock();
}
@Test
public void testLockWriteTwiceThrows4() throws Exception {
File f = writeTrashFile("somefile", "content");
LockFile lock = new LockFile(f);
assertTrue(lock.lock());
assertTrue(lock.isLocked());
try (OutputStream out = lock.getOutputStream()) {
out.write("other".getBytes(StandardCharsets.US_ASCII));
}
assertThrows(Exception.class, () -> {
try (OutputStream out = lock.getOutputStream()) {
out.write("second".getBytes(StandardCharsets.US_ASCII));
}
});
lock.unlock();
}
@Test
public void testLockUnclosedCommitThrows() throws Exception {
File f = writeTrashFile("somefile", "content");
LockFile lock = new LockFile(f);
assertTrue(lock.lock());
assertTrue(lock.isLocked());
try (OutputStream out = lock.getOutputStream()) {
out.write("other".getBytes(StandardCharsets.US_ASCII));
assertThrows(Exception.class, () -> lock.commit());
}
}
@Test
public void testLockNested() throws Exception {
File f = writeTrashFile("somefile", "content");
LockFile lock = new LockFile(f);
assertTrue(lock.lock());
assertTrue(lock.isLocked());
assertThrows(IllegalStateException.class, () -> lock.lock());
assertTrue(lock.isLocked());
lock.unlock();
}
@Test
public void testLockHeld() throws Exception {
File f = writeTrashFile("somefile", "content");
LockFile lock = new LockFile(f);
assertTrue(lock.lock());
assertTrue(lock.isLocked());
LockFile lock2 = new LockFile(f);
assertFalse(lock2.lock());
assertFalse(lock2.isLocked());
assertTrue(lock.isLocked());
lock.unlock();
}
@Test
public void testLockForAppend() throws Exception {
File f = writeTrashFile("somefile", "content");
LockFile lock = new LockFile(f);
assertTrue(lock.lockForAppend());
assertTrue(lock.isLocked());
lock.write("other".getBytes(StandardCharsets.US_ASCII));
lock.commit();
assertFalse(lock.isLocked());
checkFile(f, "contentother");
}
} }

View File

@ -404,11 +404,18 @@ listingPacks=Listing packs
localObjectsIncomplete=Local objects incomplete. localObjectsIncomplete=Local objects incomplete.
localRefIsMissingObjects=Local ref {0} is missing object(s). localRefIsMissingObjects=Local ref {0} is missing object(s).
localRepository=local repository localRepository=local repository
lockAlreadyHeld=Lock on {0} already held
lockCountMustBeGreaterOrEqual1=lockCount must be >= 1 lockCountMustBeGreaterOrEqual1=lockCount must be >= 1
lockError=lock error: {0} lockError=lock error: {0}
lockFailedRetry=locking {0} failed after {1} retries lockFailedRetry=locking {0} failed after {1} retries
lockOnNotClosed=Lock on {0} not closed. lockOnNotClosed=Lock on {0} not closed.
lockOnNotHeld=Lock on {0} not held. lockOnNotHeld=Lock on {0} not held.
lockStreamClosed=Output to lock on {0} already closed
lockStreamMultiple=Output to lock on {0} already opened
logInconsistentFiletimeDiff={}: inconsistent duration from file timestamps on {}, {}: {} > {}, but diff = {}. Aborting measurement at resolution {}.
logLargerFiletimeDiff={}: inconsistent duration from file timestamps on {}, {}: diff = {} > {} (last good value). Aborting measurement.
logSmallerFiletime={}: got smaller file timestamp on {}, {}: {} < {}. Aborting measurement at resolution {}.
logXDGConfigHomeInvalid=Environment variable XDG_CONFIG_HOME contains an invalid path {}
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}

View File

@ -1,6 +1,6 @@
/* /*
* Copyright (C) 2010, 2013 Sasa Zivkov <sasa.zivkov@sap.com> * Copyright (C) 2010, 2013 Sasa Zivkov <sasa.zivkov@sap.com>
* Copyright (C) 2012, Research In Motion Limited and others * Copyright (C) 2012, 2021 Research In Motion Limited and others
* *
* This program and the accompanying materials are made available under the * This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at * terms of the Eclipse Distribution License v. 1.0 which is available at
@ -433,10 +433,17 @@ public static JGitText get() {
/***/ public String localRefIsMissingObjects; /***/ public String localRefIsMissingObjects;
/***/ public String localRepository; /***/ public String localRepository;
/***/ public String lockCountMustBeGreaterOrEqual1; /***/ public String lockCountMustBeGreaterOrEqual1;
/***/ public String lockAlreadyHeld;
/***/ public String lockError; /***/ public String lockError;
/***/ public String lockFailedRetry; /***/ public String lockFailedRetry;
/***/ public String lockOnNotClosed; /***/ public String lockOnNotClosed;
/***/ public String lockOnNotHeld; /***/ public String lockOnNotHeld;
/***/ public String lockStreamClosed;
/***/ public String lockStreamMultiple;
/***/ public String logInconsistentFiletimeDiff;
/***/ public String logLargerFiletimeDiff;
/***/ public String logSmallerFiletime;
/***/ public String logXDGConfigHomeInvalid;
/***/ public String maxCountMustBeNonNegative; /***/ public String maxCountMustBeNonNegative;
/***/ public String mergeConflictOnNonNoteEntries; /***/ public String mergeConflictOnNonNoteEntries;
/***/ public String mergeConflictOnNotes; /***/ public String mergeConflictOnNotes;

View File

@ -128,33 +128,6 @@ CompactionStats getStats() {
return stats; return stats;
} }
/** Thrown if the update indices in the stack are not monotonic */
public static class ReftableNumbersNotIncreasingException
extends RuntimeException {
private static final long serialVersionUID = 1L;
String name;
long lastMax;
long min;
ReftableNumbersNotIncreasingException(String name, long lastMax,
long min) {
this.name = name;
this.lastMax = lastMax;
this.min = min;
}
@SuppressWarnings({ "nls", "boxing" })
@Override
public String toString() {
return String.format(
"ReftableNumbersNotIncreasingException %s: min %d, lastMax %d",
name, min, lastMax);
}
}
/** /**
* Reloads the stack, potentially reusing opened reftableReaders. * Reloads the stack, potentially reusing opened reftableReaders.
* *
@ -173,7 +146,6 @@ private void reloadOnce(List<String> names)
List<ReftableReader> newTables = new ArrayList<>(); List<ReftableReader> newTables = new ArrayList<>();
List<StackEntry> newStack = new ArrayList<>(stack.size() + 1); List<StackEntry> newStack = new ArrayList<>(stack.size() + 1);
try { try {
ReftableReader last = null;
for (String name : names) { for (String name : names) {
StackEntry entry = new StackEntry(); StackEntry entry = new StackEntry();
entry.name = name; entry.name = name;
@ -191,15 +163,6 @@ private void reloadOnce(List<String> names)
newTables.add(t); newTables.add(t);
} }
if (last != null) {
// TODO: move this to MergedReftable
if (last.maxUpdateIndex() >= t.minUpdateIndex()) {
throw new ReftableNumbersNotIncreasingException(name,
last.maxUpdateIndex(), t.minUpdateIndex());
}
}
last = t;
entry.reftableReader = t; entry.reftableReader = t;
newStack.add(entry); newStack.add(entry);
} }

View File

@ -1,6 +1,6 @@
/* /*
* Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com> * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
* Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org> and others * Copyright (C) 2006-2021, Shawn O. Pearce <spearce@spearce.org> and others
* *
* This program and the accompanying materials are made available under the * This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at * terms of the Eclipse Distribution License v. 1.0 which is available at
@ -96,11 +96,15 @@ static File getLockFile(File file) {
private boolean haveLck; private boolean haveLck;
FileOutputStream os; private FileOutputStream os;
private boolean needSnapshot; private boolean needSnapshot;
boolean fsync; private boolean fsync;
private boolean isAppend;
private boolean written;
private FileSnapshot commitSnapshot; private FileSnapshot commitSnapshot;
@ -127,6 +131,10 @@ public LockFile(File f) {
* does not hold the lock. * does not hold the lock.
*/ */
public boolean lock() throws IOException { public boolean lock() throws IOException {
if (haveLck) {
throw new IllegalStateException(
MessageFormat.format(JGitText.get().lockAlreadyHeld, ref));
}
FileUtils.mkdirs(lck.getParentFile(), true); FileUtils.mkdirs(lck.getParentFile(), true);
try { try {
token = FS.DETECTED.createNewFileAtomic(lck); token = FS.DETECTED.createNewFileAtomic(lck);
@ -134,18 +142,15 @@ public boolean lock() throws IOException {
LOG.error(JGitText.get().failedCreateLockFile, lck, e); LOG.error(JGitText.get().failedCreateLockFile, lck, e);
throw e; throw e;
} }
if (token.isCreated()) { boolean obtainedLock = token.isCreated();
if (obtainedLock) {
haveLck = true; haveLck = true;
try { isAppend = false;
os = new FileOutputStream(lck); written = false;
} catch (IOException ioe) {
unlock();
throw ioe;
}
} else { } else {
closeToken(); closeToken();
} }
return haveLck; return obtainedLock;
} }
/** /**
@ -158,12 +163,24 @@ public boolean lock() throws IOException {
* does not hold the lock. * does not hold the lock.
*/ */
public boolean lockForAppend() throws IOException { public boolean lockForAppend() throws IOException {
if (!lock()) if (!lock()) {
return false; return false;
}
copyCurrentContent(); copyCurrentContent();
isAppend = true;
written = false;
return true; return true;
} }
// For tests only
boolean isLocked() {
return haveLck;
}
private FileOutputStream getStream() throws IOException {
return new FileOutputStream(lck, isAppend);
}
/** /**
* Copy the current file content into the temporary file. * Copy the current file content into the temporary file.
* <p> * <p>
@ -185,32 +202,31 @@ public boolean lockForAppend() throws IOException {
*/ */
public void copyCurrentContent() throws IOException { public void copyCurrentContent() throws IOException {
requireLock(); requireLock();
try { try (FileOutputStream out = getStream()) {
try (FileInputStream fis = new FileInputStream(ref)) { try (FileInputStream fis = new FileInputStream(ref)) {
if (fsync) { if (fsync) {
FileChannel in = fis.getChannel(); FileChannel in = fis.getChannel();
long pos = 0; long pos = 0;
long cnt = in.size(); long cnt = in.size();
while (0 < cnt) { while (0 < cnt) {
long r = os.getChannel().transferFrom(in, pos, cnt); long r = out.getChannel().transferFrom(in, pos, cnt);
pos += r; pos += r;
cnt -= r; cnt -= r;
} }
} else { } else {
final byte[] buf = new byte[2048]; final byte[] buf = new byte[2048];
int r; int r;
while ((r = fis.read(buf)) >= 0) while ((r = fis.read(buf)) >= 0) {
os.write(buf, 0, r); out.write(buf, 0, r);
} }
} }
} catch (FileNotFoundException fnfe) { } catch (FileNotFoundException fnfe) {
if (ref.exists()) { if (ref.exists()) {
unlock();
throw fnfe; throw fnfe;
} }
// Don't worry about a file that doesn't exist yet, it // Don't worry about a file that doesn't exist yet, it
// conceptually has no current content to copy. // conceptually has no current content to copy.
// }
} catch (IOException | RuntimeException | Error ioe) { } catch (IOException | RuntimeException | Error ioe) {
unlock(); unlock();
throw ioe; throw ioe;
@ -253,18 +269,22 @@ public void write(ObjectId id) throws IOException {
*/ */
public void write(byte[] content) throws IOException { public void write(byte[] content) throws IOException {
requireLock(); requireLock();
try { try (FileOutputStream out = getStream()) {
if (written) {
throw new IOException(MessageFormat
.format(JGitText.get().lockStreamClosed, ref));
}
if (fsync) { if (fsync) {
FileChannel fc = os.getChannel(); FileChannel fc = out.getChannel();
ByteBuffer buf = ByteBuffer.wrap(content); ByteBuffer buf = ByteBuffer.wrap(content);
while (0 < buf.remaining()) while (0 < buf.remaining()) {
fc.write(buf); fc.write(buf);
}
fc.force(true); fc.force(true);
} else { } else {
os.write(content); out.write(content);
} }
os.close(); written = true;
os = null;
} catch (IOException | RuntimeException | Error ioe) { } catch (IOException | RuntimeException | Error ioe) {
unlock(); unlock();
throw ioe; throw ioe;
@ -283,36 +303,68 @@ public void write(byte[] content) throws IOException {
public OutputStream getOutputStream() { public OutputStream getOutputStream() {
requireLock(); requireLock();
final OutputStream out; if (written || os != null) {
if (fsync) throw new IllegalStateException(MessageFormat
out = Channels.newOutputStream(os.getChannel()); .format(JGitText.get().lockStreamMultiple, ref));
else }
out = os;
return new OutputStream() { return new OutputStream() {
private OutputStream out;
private boolean closed;
private OutputStream get() throws IOException {
if (written) {
throw new IOException(MessageFormat
.format(JGitText.get().lockStreamMultiple, ref));
}
if (out == null) {
os = getStream();
if (fsync) {
out = Channels.newOutputStream(os.getChannel());
} else {
out = os;
}
}
return out;
}
@Override @Override
public void write(byte[] b, int o, int n) public void write(byte[] b, int o, int n)
throws IOException { throws IOException {
out.write(b, o, n); get().write(b, o, n);
} }
@Override @Override
public void write(byte[] b) throws IOException { public void write(byte[] b) throws IOException {
out.write(b); get().write(b);
} }
@Override @Override
public void write(int b) throws IOException { public void write(int b) throws IOException {
out.write(b); get().write(b);
} }
@Override @Override
public void close() throws IOException { public void close() throws IOException {
if (closed) {
return;
}
closed = true;
try { try {
if (fsync) if (written) {
throw new IOException(MessageFormat
.format(JGitText.get().lockStreamClosed, ref));
}
if (out != null) {
if (fsync) {
os.getChannel().force(true); os.getChannel().force(true);
}
out.close(); out.close();
os = null; os = null;
}
written = true;
} catch (IOException | RuntimeException | Error ioe) { } catch (IOException | RuntimeException | Error ioe) {
unlock(); unlock();
throw ioe; throw ioe;
@ -322,7 +374,7 @@ public void close() throws IOException {
} }
void requireLock() { void requireLock() {
if (os == null) { if (!haveLck) {
unlock(); unlock();
throw new IllegalStateException(MessageFormat.format(JGitText.get().lockOnNotHeld, ref)); throw new IllegalStateException(MessageFormat.format(JGitText.get().lockOnNotHeld, ref));
} }
@ -411,6 +463,8 @@ public boolean commit() {
try { try {
FileUtils.rename(lck, ref, StandardCopyOption.ATOMIC_MOVE); FileUtils.rename(lck, ref, StandardCopyOption.ATOMIC_MOVE);
haveLck = false; haveLck = false;
isAppend = false;
written = false;
closeToken(); closeToken();
return true; return true;
} catch (IOException e) { } catch (IOException e) {
@ -497,6 +551,8 @@ public void unlock() {
closeToken(); closeToken();
} }
} }
isAppend = false;
written = false;
} }
/** {@inheritDoc} */ /** {@inheritDoc} */