Browse Source

LockFile: create OutputStream only when needed

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
Thomas Wolf 3 years ago
parent
commit
0038627226

+ 173
- 38
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/LockFileTest.java View File

/* /*
* 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");
}
} }

+ 7
- 0
org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties View File

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}

+ 12
- 38
org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java View File

/* /*
* 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;

+ 93
- 70
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java View File

/* /*
* 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} */

Loading…
Cancel
Save