Cleanup stream usage WRT filters

As it is right now some streams leak out of the filter construct. This
change clarifies responsibilities and fixes stream leaks

Change-Id: Ib9717d43a701a06a502434d64214d13a392de5ab
Signed-off-by: Markus Duft <markus.duft@ssi-schaefer.com>
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
This commit is contained in:
Markus Duft 2018-03-02 10:13:05 +01:00 committed by Matthias Sohn
parent d3ed64bcd4
commit a3f8edbf6a
6 changed files with 88 additions and 33 deletions

View File

@ -167,6 +167,7 @@ public int run() throws IOException {
} }
LfsPointer lfsPointer = new LfsPointer(loid, size); LfsPointer lfsPointer = new LfsPointer(loid, size);
lfsPointer.encode(out); lfsPointer.encode(out);
in.close();
out.close(); out.close();
return -1; return -1;
} }
@ -174,6 +175,7 @@ public int run() throws IOException {
if (aOut != null) { if (aOut != null) {
aOut.abort(); aOut.abort();
} }
in.close();
out.close(); out.close();
throw e; throw e;
} }

View File

@ -116,23 +116,29 @@ static void register() {
* @param db * @param db
* a {@link org.eclipse.jgit.lib.Repository} object. * a {@link org.eclipse.jgit.lib.Repository} object.
* @param in * @param in
* a {@link java.io.InputStream} object. * a {@link java.io.InputStream} object. The stream is closed in
* any case.
* @param out * @param out
* a {@link java.io.OutputStream} object. * a {@link java.io.OutputStream} object.
* @throws java.io.IOException * @throws java.io.IOException
* in case of an error
*/ */
public SmudgeFilter(Repository db, InputStream in, OutputStream out) public SmudgeFilter(Repository db, InputStream in, OutputStream out)
throws IOException { throws IOException {
super(in, out); super(in, out);
Lfs lfs = new Lfs(db); try {
LfsPointer res = LfsPointer.parseLfsPointer(in); Lfs lfs = new Lfs(db);
if (res != null) { LfsPointer res = LfsPointer.parseLfsPointer(in);
AnyLongObjectId oid = res.getOid(); if (res != null) {
Path mediaFile = lfs.getMediaFile(oid); AnyLongObjectId oid = res.getOid();
if (!Files.exists(mediaFile)) { Path mediaFile = lfs.getMediaFile(oid);
downloadLfsResource(lfs, db, res); if (!Files.exists(mediaFile)) {
downloadLfsResource(lfs, db, res);
}
this.in = Files.newInputStream(mediaFile);
} }
this.in = Files.newInputStream(mediaFile); } finally {
in.close(); // make sure the swapped stream is closed properly.
} }
} }
@ -147,6 +153,7 @@ public SmudgeFilter(Repository db, InputStream in, OutputStream out)
* the objects to download * the objects to download
* @return the paths of all mediafiles which have been downloaded * @return the paths of all mediafiles which have been downloaded
* @throws IOException * @throws IOException
* @since 4.11
*/ */
public static Collection<Path> downloadLfsResource(Lfs lfs, Repository db, public static Collection<Path> downloadLfsResource(Lfs lfs, Repository db,
LfsPointer... res) throws IOException { LfsPointer... res) throws IOException {
@ -228,33 +235,39 @@ public static Collection<Path> downloadLfsResource(Lfs lfs, Repository db,
/** {@inheritDoc} */ /** {@inheritDoc} */
@Override @Override
public int run() throws IOException { public int run() throws IOException {
int totalRead = 0; try {
int length = 0; int totalRead = 0;
if (in != null) { int length = 0;
byte[] buf = new byte[8192]; if (in != null) {
while ((length = in.read(buf)) != -1) { byte[] buf = new byte[8192];
out.write(buf, 0, length); while ((length = in.read(buf)) != -1) {
totalRead += length; out.write(buf, 0, length);
totalRead += length;
// when threshold reached, loop back to the caller. otherwise we // when threshold reached, loop back to the caller.
// could only support files up to 2GB (int return type) // otherwise we could only support files up to 2GB (int
// properly. we will be called again as long as we don't return // return type) properly. we will be called again as long as
// -1 here. // we don't return -1 here.
if (totalRead >= MAX_COPY_BYTES) { if (totalRead >= MAX_COPY_BYTES) {
// leave streams open - we need them in the next call. // leave streams open - we need them in the next call.
return totalRead; return totalRead;
}
} }
} }
}
if (totalRead == 0 && length == -1) { if (totalRead == 0 && length == -1) {
// we're totally done :) // we're totally done :) cleanup all streams
in.close(); in.close();
out.close();
return length;
}
return totalRead;
} catch (IOException e) {
in.close(); // clean up - we swapped this stream.
out.close(); out.close();
return length; throw e;
} }
return totalRead;
} }
} }

View File

@ -67,6 +67,9 @@ public abstract class FilterCommand {
/** /**
* Constructor for FilterCommand * Constructor for FilterCommand
* <p>
* FilterCommand implementors are required to manage the in and out streams
* (close on success and/or exception).
* *
* @param in * @param in
* The {@link java.io.InputStream} this command should read from * The {@link java.io.InputStream} this command should read from
@ -84,6 +87,9 @@ public FilterCommand(InputStream in, OutputStream out) {
* number of bytes it read from {@link #in}. It should be called in a loop * number of bytes it read from {@link #in}. It should be called in a loop
* until it returns -1 signaling that the {@link java.io.InputStream} is * until it returns -1 signaling that the {@link java.io.InputStream} is
* completely processed. * completely processed.
* <p>
* On successful completion (return -1) or on Exception, the streams
* {@link #in} and {@link #out} are closed by the implementation.
* *
* @return the number of bytes read from the {@link java.io.InputStream} or * @return the number of bytes read from the {@link java.io.InputStream} or
* -1. -1 means that the {@link java.io.InputStream} is completely * -1. -1 means that the {@link java.io.InputStream} is completely

View File

@ -476,7 +476,7 @@ private InputStream filterClean(InputStream in, OperationType opType)
while (command.run() != -1) { while (command.run() != -1) {
// loop as long as command.run() tells there is work to do // loop as long as command.run() tells there is work to do
} }
return buffer.openInputStream(); return buffer.openInputStreamWithAutoDestroy();
} }
FS fs = repository.getFS(); FS fs = repository.getFS();
ProcessBuilder filterProcessBuilder = fs.runInShell(filterCommand, ProcessBuilder filterProcessBuilder = fs.runInShell(filterCommand,
@ -499,7 +499,7 @@ filterCommand, getEntryPathString(),
RawParseUtils.decode(result.getStderr() RawParseUtils.decode(result.getStderr()
.toByteArray(MAX_EXCEPTION_TEXT_SIZE)))); .toByteArray(MAX_EXCEPTION_TEXT_SIZE))));
} }
return result.getStdout().openInputStream(); return result.getStdout().openInputStreamWithAutoDestroy();
} }
return in; return in;
} }

View File

@ -259,7 +259,7 @@ public LfsInputStream(InputStream stream, long length) {
* in case of an error opening the stream to the buffer. * in case of an error opening the stream to the buffer.
*/ */
public LfsInputStream(TemporaryBuffer buffer) throws IOException { public LfsInputStream(TemporaryBuffer buffer) throws IOException {
this.stream = buffer.openInputStream(); this.stream = buffer.openInputStreamWithAutoDestroy();
this.length = buffer.length(); this.length = buffer.length();
} }

View File

@ -313,6 +313,26 @@ public InputStream openInputStream() throws IOException {
return new BlockInputStream(); return new BlockInputStream();
} }
/**
* Same as {@link #openInputStream()} but handling destruction of any
* associated resources automatically when closing the returned stream.
*
* @return an InputStream which will automatically destroy any associated
* temporary file on {@link #close()}
* @throws IOException
* in case of an error.
* @since 4.11
*/
public InputStream openInputStreamWithAutoDestroy() throws IOException {
return new BlockInputStream() {
@Override
public void close() throws IOException {
super.close();
destroy();
}
};
}
/** /**
* Reset this buffer for reuse, purging all buffered content. * Reset this buffer for reuse, purging all buffered content.
*/ */
@ -505,6 +525,20 @@ public InputStream openInputStream() throws IOException {
return new FileInputStream(onDiskFile); return new FileInputStream(onDiskFile);
} }
@Override
public InputStream openInputStreamWithAutoDestroy() throws IOException {
if (onDiskFile == null) {
return super.openInputStreamWithAutoDestroy();
}
return new FileInputStream(onDiskFile) {
@Override
public void close() throws IOException {
super.close();
destroy();
}
};
}
@Override @Override
public void destroy() { public void destroy() {
super.destroy(); super.destroy();