Don't create the stream eagerly in lock(); that may cause JGit to exceed OS or JVM limits on open file descriptors if many locks need to be created, for instance when creating many refs. Instead create the output stream only when one really needs to write something. Bug: 573328 Change-Id: If9441ed40494d46f594a896d34a5c4f56f91ebf4 Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>tags/v5.11.1.202105131744-r
/* | /* | ||||
* Copyright (C) 2012, GitHub Inc. | |||||
* and other copyright owners as documented in the project's IP log. | |||||
* 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 v1.0 which | |||||
* accompanies this distribution, is reproduced below, and is | |||||
* available at http://www.eclipse.org/org/documents/edl-v10.php | |||||
* This program and the accompanying materials are made available under the | |||||
* terms of the Eclipse Distribution License v. 1.0 which is available at | |||||
* https://www.eclipse.org/org/documents/edl-v10.php. | |||||
* | * | ||||
* All rights reserved. | |||||
* | |||||
* Redistribution and use in source and binary forms, with or | |||||
* without modification, are permitted provided that the following | |||||
* conditions are met: | |||||
* | |||||
* - Redistributions of source code must retain the above copyright | |||||
* notice, this list of conditions and the following disclaimer. | |||||
* | |||||
* - Redistributions in binary form must reproduce the above | |||||
* copyright notice, this list of conditions and the following | |||||
* disclaimer in the documentation and/or other materials provided | |||||
* with the distribution. | |||||
* | |||||
* - Neither the name of the Eclipse Foundation, Inc. nor the | |||||
* names of its contributors may be used to endorse or promote | |||||
* products derived from this software without specific prior | |||||
* written permission. | |||||
* | |||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND | |||||
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, | |||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR | |||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | |||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, | |||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF | |||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||||
* SPDX-License-Identifier: BSD-3-Clause | |||||
*/ | */ | ||||
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.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; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
@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)); | |||||
try { | |||||
lock.write("second".getBytes(StandardCharsets.US_ASCII)); | |||||
fail(); | |||||
} catch (Exception e) { | |||||
// expected | |||||
} | |||||
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)); | |||||
} | |||||
try { | |||||
lock.write("second".getBytes(StandardCharsets.US_ASCII)); | |||||
fail(); | |||||
} catch (Exception e) { | |||||
// expected | |||||
} | |||||
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)); | |||||
try (OutputStream out = lock.getOutputStream()) { | |||||
out.write("second".getBytes(StandardCharsets.US_ASCII)); | |||||
fail(); | |||||
} catch (Exception e) { | |||||
// expected | |||||
} | |||||
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)); | |||||
} | |||||
try (OutputStream out = lock.getOutputStream()) { | |||||
out.write("second".getBytes(StandardCharsets.US_ASCII)); | |||||
fail(); | |||||
} catch (Exception e) { | |||||
// expected | |||||
} | |||||
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)); | |||||
lock.commit(); | |||||
fail(); | |||||
} catch (Exception e) { | |||||
// expected | |||||
} | |||||
} | |||||
@Test | |||||
public void testLockNested() throws Exception { | |||||
File f = writeTrashFile("somefile", "content"); | |||||
LockFile lock = new LockFile(f); | |||||
assertTrue(lock.lock()); | |||||
assertTrue(lock.isLocked()); | |||||
try { | |||||
lock.lock(); | |||||
fail(); | |||||
} catch (IllegalStateException e) { | |||||
// expected | |||||
} | |||||
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"); | |||||
} | |||||
} | } |
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} |
/* | /* | ||||
* 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 other copyright owners as documented in the project's IP log. | |||||
* 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 v1.0 which | |||||
* accompanies this distribution, is reproduced below, and is | |||||
* available at http://www.eclipse.org/org/documents/edl-v10.php | |||||
* This program and the accompanying materials are made available under the | |||||
* terms of the Eclipse Distribution License v. 1.0 which is available at | |||||
* https://www.eclipse.org/org/documents/edl-v10.php. | |||||
* | * | ||||
* All rights reserved. | |||||
* | |||||
* Redistribution and use in source and binary forms, with or | |||||
* without modification, are permitted provided that the following | |||||
* conditions are met: | |||||
* | |||||
* - Redistributions of source code must retain the above copyright | |||||
* notice, this list of conditions and the following disclaimer. | |||||
* | |||||
* - Redistributions in binary form must reproduce the above | |||||
* copyright notice, this list of conditions and the following | |||||
* disclaimer in the documentation and/or other materials provided | |||||
* with the distribution. | |||||
* | |||||
* - Neither the name of the Eclipse Foundation, Inc. nor the | |||||
* names of its contributors may be used to endorse or promote | |||||
* products derived from this software without specific prior | |||||
* written permission. | |||||
* | |||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND | |||||
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, | |||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR | |||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | |||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, | |||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF | |||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||||
* SPDX-License-Identifier: BSD-3-Clause | |||||
*/ | */ | ||||
package org.eclipse.jgit.internal; | package org.eclipse.jgit.internal; | ||||
/***/ 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; |
/* | /* | ||||
* 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 other copyright owners as documented in the project's IP log. | |||||
* 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 v1.0 which | |||||
* accompanies this distribution, is reproduced below, and is | |||||
* available at http://www.eclipse.org/org/documents/edl-v10.php | |||||
* This program and the accompanying materials are made available under the | |||||
* terms of the Eclipse Distribution License v. 1.0 which is available at | |||||
* https://www.eclipse.org/org/documents/edl-v10.php. | |||||
* | * | ||||
* All rights reserved. | |||||
* | |||||
* Redistribution and use in source and binary forms, with or | |||||
* without modification, are permitted provided that the following | |||||
* conditions are met: | |||||
* | |||||
* - Redistributions of source code must retain the above copyright | |||||
* notice, this list of conditions and the following disclaimer. | |||||
* | |||||
* - Redistributions in binary form must reproduce the above | |||||
* copyright notice, this list of conditions and the following | |||||
* disclaimer in the documentation and/or other materials provided | |||||
* with the distribution. | |||||
* | |||||
* - Neither the name of the Eclipse Foundation, Inc. nor the | |||||
* names of its contributors may be used to endorse or promote | |||||
* products derived from this software without specific prior | |||||
* written permission. | |||||
* | |||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND | |||||
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, | |||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR | |||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | |||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, | |||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF | |||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||||
* SPDX-License-Identifier: BSD-3-Clause | |||||
*/ | */ | ||||
package org.eclipse.jgit.internal.storage.file; | package org.eclipse.jgit.internal.storage.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; | ||||
* 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); | ||||
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 { | |||||
os = new FileOutputStream(lck); | |||||
} catch (IOException ioe) { | |||||
unlock(); | |||||
throw ioe; | |||||
} | |||||
isAppend = false; | |||||
written = false; | |||||
} else { | } else { | ||||
closeToken(); | closeToken(); | ||||
} | } | ||||
return haveLck; | |||||
return obtainedLock; | |||||
} | } | ||||
/** | /** | ||||
* 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> | ||||
*/ | */ | ||||
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) | |||||
os.write(buf, 0, r); | |||||
while ((r = fis.read(buf)) >= 0) { | |||||
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; | ||||
*/ | */ | ||||
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(); | |||||
os = null; | |||||
written = true; | |||||
} catch (IOException | RuntimeException | Error ioe) { | } catch (IOException | RuntimeException | Error ioe) { | ||||
unlock(); | unlock(); | ||||
throw ioe; | throw ioe; | ||||
public OutputStream getOutputStream() { | public OutputStream getOutputStream() { | ||||
requireLock(); | 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()); | out = Channels.newOutputStream(os.getChannel()); | ||||
else | |||||
} else { | |||||
out = os; | out = os; | ||||
} | |||||
} | |||||
return out; | |||||
} | |||||
return new OutputStream() { | |||||
@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; | ||||
} | } | ||||
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)); | ||||
} | } | ||||
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) { | ||||
closeToken(); | closeToken(); | ||||
} | } | ||||
} | } | ||||
isAppend = false; | |||||
written = false; | |||||
} | } | ||||
/** {@inheritDoc} */ | /** {@inheritDoc} */ |