aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthias Sohn <matthias.sohn@sap.com>2021-05-11 00:19:10 +0200
committerMatthias Sohn <matthias.sohn@sap.com>2021-05-11 00:31:58 +0200
commit0616016c83614d09e69895a2ca76564ff478d4d2 (patch)
treea701bd6da2c3ee8e75e941f6ef1aa792e403cdea
parent540b29bf4266f3ec974cdf230faca32ab712843b (diff)
parent00386272264f65c41e36406f7c2e9ea6e901276e (diff)
downloadjgit-0616016c83614d09e69895a2ca76564ff478d4d2.tar.gz
jgit-0616016c83614d09e69895a2ca76564ff478d4d2.zip
Merge branch 'stable-5.6' into stable-5.7
* stable-5.6: LockFile: create OutputStream only when needed Change-Id: I7c0e37d2cee0923662a7e39df5a802a84c017e4f
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/LockFileTest.java153
-rw-r--r--org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties7
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java9
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java122
4 files changed, 256 insertions, 35 deletions
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/LockFileTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/LockFileTest.java
index 0f93749d9b..509935dfb9 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/LockFileTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/LockFileTest.java
@@ -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
* terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -9,10 +9,16 @@
*/
package org.eclipse.jgit.internal.storage.file;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
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.errors.JGitInternalException;
import org.eclipse.jgit.errors.LockFailedException;
@@ -49,4 +55,149 @@ public class LockFileTest extends RepositoryTestCase {
}
}
}
+
+ @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");
+ }
}
diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
index 054a6ef859..424bff0b19 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
@@ -410,11 +410,18 @@ listingPacks=Listing packs
localObjectsIncomplete=Local objects incomplete.
localRefIsMissingObjects=Local ref {0} is missing object(s).
localRepository=local repository
+lockAlreadyHeld=Lock on {0} already held
lockCountMustBeGreaterOrEqual1=lockCount must be >= 1
lockError=lock error: {0}
lockFailedRetry=locking {0} failed after {1} retries
lockOnNotClosed=Lock on {0} not closed.
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
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}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
index 5b08e00e39..fd17d326e5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -1,6 +1,6 @@
/*
* 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
* terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -439,10 +439,17 @@ public class JGitText extends TranslationBundle {
/***/ public String localRefIsMissingObjects;
/***/ public String localRepository;
/***/ public String lockCountMustBeGreaterOrEqual1;
+ /***/ public String lockAlreadyHeld;
/***/ public String lockError;
/***/ public String lockFailedRetry;
/***/ public String lockOnNotClosed;
/***/ 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 mergeConflictOnNonNoteEntries;
/***/ public String mergeConflictOnNotes;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java
index 2e0a6da3a4..f57581a299 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java
@@ -1,6 +1,6 @@
/*
* 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
* terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -96,11 +96,15 @@ public class LockFile {
private boolean haveLck;
- FileOutputStream os;
+ private FileOutputStream os;
private boolean needSnapshot;
- boolean fsync;
+ private boolean fsync;
+
+ private boolean isAppend;
+
+ private boolean written;
private FileSnapshot commitSnapshot;
@@ -127,6 +131,10 @@ public class LockFile {
* does not hold the lock.
*/
public boolean lock() throws IOException {
+ if (haveLck) {
+ throw new IllegalStateException(
+ MessageFormat.format(JGitText.get().lockAlreadyHeld, ref));
+ }
FileUtils.mkdirs(lck.getParentFile(), true);
try {
token = FS.DETECTED.createNewFileAtomic(lck);
@@ -134,18 +142,15 @@ public class LockFile {
LOG.error(JGitText.get().failedCreateLockFile, lck, e);
throw e;
}
- if (token.isCreated()) {
+ boolean obtainedLock = token.isCreated();
+ if (obtainedLock) {
haveLck = true;
- try {
- os = new FileOutputStream(lck);
- } catch (IOException ioe) {
- unlock();
- throw ioe;
- }
+ isAppend = false;
+ written = false;
} else {
closeToken();
}
- return haveLck;
+ return obtainedLock;
}
/**
@@ -158,12 +163,24 @@ public class LockFile {
* does not hold the lock.
*/
public boolean lockForAppend() throws IOException {
- if (!lock())
+ if (!lock()) {
return false;
+ }
copyCurrentContent();
+ isAppend = true;
+ written = false;
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.
* <p>
@@ -185,32 +202,31 @@ public class LockFile {
*/
public void copyCurrentContent() throws IOException {
requireLock();
- try {
+ try (FileOutputStream out = getStream()) {
try (FileInputStream fis = new FileInputStream(ref)) {
if (fsync) {
FileChannel in = fis.getChannel();
long pos = 0;
long cnt = in.size();
while (0 < cnt) {
- long r = os.getChannel().transferFrom(in, pos, cnt);
+ long r = out.getChannel().transferFrom(in, pos, cnt);
pos += r;
cnt -= r;
}
} else {
final byte[] buf = new byte[2048];
int r;
- while ((r = fis.read(buf)) >= 0)
- os.write(buf, 0, r);
+ while ((r = fis.read(buf)) >= 0) {
+ out.write(buf, 0, r);
}
}
} catch (FileNotFoundException fnfe) {
if (ref.exists()) {
- unlock();
throw fnfe;
}
// Don't worry about a file that doesn't exist yet, it
// conceptually has no current content to copy.
- //
+ }
} catch (IOException | RuntimeException | Error ioe) {
unlock();
throw ioe;
@@ -253,18 +269,22 @@ public class LockFile {
*/
public void write(byte[] content) throws IOException {
requireLock();
- try {
+ try (FileOutputStream out = getStream()) {
+ if (written) {
+ throw new IOException(MessageFormat
+ .format(JGitText.get().lockStreamClosed, ref));
+ }
if (fsync) {
- FileChannel fc = os.getChannel();
+ FileChannel fc = out.getChannel();
ByteBuffer buf = ByteBuffer.wrap(content);
- while (0 < buf.remaining())
+ while (0 < buf.remaining()) {
fc.write(buf);
+ }
fc.force(true);
} else {
- os.write(content);
+ out.write(content);
}
- os.close();
- os = null;
+ written = true;
} catch (IOException | RuntimeException | Error ioe) {
unlock();
throw ioe;
@@ -283,36 +303,68 @@ public class LockFile {
public OutputStream getOutputStream() {
requireLock();
- final OutputStream out;
- if (fsync)
+ if (written || os != null) {
+ throw new IllegalStateException(MessageFormat
+ .format(JGitText.get().lockStreamMultiple, ref));
+ }
+
+ 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
+ } else {
out = os;
+ }
+ }
+ return out;
+ }
- return new OutputStream() {
@Override
public void write(byte[] b, int o, int n)
throws IOException {
- out.write(b, o, n);
+ get().write(b, o, n);
}
@Override
public void write(byte[] b) throws IOException {
- out.write(b);
+ get().write(b);
}
@Override
public void write(int b) throws IOException {
- out.write(b);
+ get().write(b);
}
@Override
public void close() throws IOException {
+ if (closed) {
+ return;
+ }
+ closed = true;
try {
- if (fsync)
+ if (written) {
+ throw new IOException(MessageFormat
+ .format(JGitText.get().lockStreamClosed, ref));
+ }
+ if (out != null) {
+ if (fsync) {
os.getChannel().force(true);
+ }
out.close();
os = null;
+ }
+ written = true;
} catch (IOException | RuntimeException | Error ioe) {
unlock();
throw ioe;
@@ -322,7 +374,7 @@ public class LockFile {
}
void requireLock() {
- if (os == null) {
+ if (!haveLck) {
unlock();
throw new IllegalStateException(MessageFormat.format(JGitText.get().lockOnNotHeld, ref));
}
@@ -411,6 +463,8 @@ public class LockFile {
try {
FileUtils.rename(lck, ref, StandardCopyOption.ATOMIC_MOVE);
haveLck = false;
+ isAppend = false;
+ written = false;
closeToken();
return true;
} catch (IOException e) {
@@ -497,6 +551,8 @@ public class LockFile {
closeToken();
}
}
+ isAppend = false;
+ written = false;
}
/** {@inheritDoc} */