Skip to content

Commit b4b1c20

Browse files
joschieddumelendez
andauthored
Fix copy large files (#8409)
Fix `OutOfMemoryError` (out of heap memory) when copying large files into a container via `GenericContainer#withCopyFileToContainer()`. Fixes #4203 --------- Co-authored-by: Eddú Meléndez <[email protected]>
1 parent a687e8a commit b4b1c20

File tree

2 files changed

+61
-10
lines changed

2 files changed

+61
-10
lines changed

core/src/main/java/org/testcontainers/containers/ContainerState.java

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@
2222
import org.testcontainers.utility.MountableFile;
2323
import org.testcontainers.utility.ThrowingFunction;
2424

25-
import java.io.ByteArrayInputStream;
26-
import java.io.ByteArrayOutputStream;
2725
import java.io.File;
2826
import java.io.FileOutputStream;
2927
import java.io.IOException;
3028
import java.io.InputStream;
29+
import java.io.PipedInputStream;
30+
import java.io.PipedOutputStream;
3131
import java.nio.charset.Charset;
3232
import java.nio.charset.StandardCharsets;
3333
import java.util.ArrayList;
@@ -339,27 +339,37 @@ default void copyFileToContainer(MountableFile mountableFile, String containerPa
339339
* @param transferable file which is copied into the container
340340
* @param containerPath destination path inside the container
341341
*/
342-
@SneakyThrows(IOException.class)
342+
@SneakyThrows({ IOException.class, InterruptedException.class })
343343
default void copyFileToContainer(Transferable transferable, String containerPath) {
344344
if (getContainerId() == null) {
345345
throw new IllegalStateException("copyFileToContainer can only be used with created / running container");
346346
}
347347

348348
try (
349-
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
350-
TarArchiveOutputStream tarArchive = new TarArchiveOutputStream(byteArrayOutputStream)
349+
PipedOutputStream pipedOutputStream = new PipedOutputStream();
350+
PipedInputStream pipedInputStream = new PipedInputStream(pipedOutputStream);
351+
TarArchiveOutputStream tarArchive = new TarArchiveOutputStream(pipedOutputStream)
351352
) {
352-
tarArchive.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);
353-
tarArchive.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_POSIX);
353+
Thread thread = new Thread(() -> {
354+
try {
355+
tarArchive.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);
356+
tarArchive.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_POSIX);
357+
358+
transferable.transferTo(tarArchive, containerPath);
359+
} finally {
360+
IOUtils.closeQuietly(tarArchive);
361+
}
362+
});
354363

355-
transferable.transferTo(tarArchive, containerPath);
356-
tarArchive.finish();
364+
thread.start();
357365

358366
getDockerClient()
359367
.copyArchiveToContainerCmd(getContainerId())
360-
.withTarInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()))
368+
.withTarInputStream(pipedInputStream)
361369
.withRemotePath("/")
362370
.exec();
371+
372+
thread.join();
363373
}
364374
}
365375

core/src/test/java/org/testcontainers/junit/FileOperationsTest.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,19 @@
33
import com.github.dockerjava.api.exception.NotFoundException;
44
import org.apache.commons.io.FileUtils;
55
import org.apache.commons.io.IOUtils;
6+
import org.apache.commons.io.output.CountingOutputStream;
67
import org.junit.Rule;
78
import org.junit.Test;
89
import org.junit.rules.TemporaryFolder;
910
import org.testcontainers.TestImages;
11+
import org.testcontainers.containers.Container;
1012
import org.testcontainers.containers.GenericContainer;
1113
import org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy;
1214
import org.testcontainers.utility.MountableFile;
1315

16+
import java.io.BufferedOutputStream;
1417
import java.io.File;
18+
import java.io.FileOutputStream;
1519
import java.io.IOException;
1620

1721
import static org.assertj.core.api.Assertions.assertThat;
@@ -40,6 +44,43 @@ public void copyFileToContainerFileTest() throws Exception {
4044
}
4145
}
4246

47+
@Test
48+
public void copyLargeFilesToContainer() throws Exception {
49+
File tempFile = temporaryFolder.newFile();
50+
try (
51+
GenericContainer<?> alpineCopyToContainer = new GenericContainer<>(TestImages.ALPINE_IMAGE) //
52+
.withCommand("sleep", "infinity")
53+
) {
54+
alpineCopyToContainer.start();
55+
final long byteCount;
56+
try (
57+
FileOutputStream fos = new FileOutputStream(tempFile);
58+
CountingOutputStream cos = new CountingOutputStream(fos);
59+
BufferedOutputStream bos = new BufferedOutputStream(cos)
60+
) {
61+
for (int i = 0; i < 0x4000; i++) {
62+
byte[] bytes = new byte[0xFFFF];
63+
bos.write(bytes);
64+
}
65+
bos.flush();
66+
byteCount = cos.getByteCount();
67+
}
68+
final MountableFile mountableFile = MountableFile.forHostPath(tempFile.getPath());
69+
final String containerPath = "/test.bin";
70+
alpineCopyToContainer.copyFileToContainer(mountableFile, containerPath);
71+
72+
final Container.ExecResult execResult = alpineCopyToContainer.execInContainer( //
73+
"stat",
74+
"-c",
75+
"%s",
76+
containerPath
77+
);
78+
assertThat(execResult.getStdout()).isEqualToIgnoringNewLines(Long.toString(byteCount));
79+
} finally {
80+
tempFile.delete();
81+
}
82+
}
83+
4384
@Test
4485
public void copyFileToContainerFolderTest() throws Exception {
4586
try (

0 commit comments

Comments
 (0)