Skip to content

Commit b38e981

Browse files
committed
speed up extracting archives with embedded postgres binaries
1 parent adb4ff8 commit b38e981

File tree

1 file changed

+50
-25
lines changed

1 file changed

+50
-25
lines changed

src/main/java/io/zonky/test/db/postgres/embedded/EmbeddedPostgres.java

Lines changed: 50 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,27 @@
1414
package io.zonky.test.db.postgres.embedded;
1515

1616

17+
import java.io.ByteArrayInputStream;
1718
import java.io.Closeable;
1819
import java.io.File;
1920
import java.io.FileOutputStream;
2021
import java.io.IOException;
2122
import java.io.InputStream;
22-
import java.io.OutputStream;
2323
import java.net.InetAddress;
2424
import java.net.InetSocketAddress;
2525
import java.net.ServerSocket;
2626
import java.net.Socket;
2727
import java.net.SocketException;
2828
import java.net.UnknownHostException;
29+
import java.nio.ByteBuffer;
30+
import java.nio.channels.AsynchronousFileChannel;
31+
import java.nio.channels.Channel;
32+
import java.nio.channels.CompletionHandler;
2933
import java.nio.channels.FileLock;
3034
import java.nio.channels.OverlappingFileLockException;
3135
import java.nio.file.FileSystems;
3236
import java.nio.file.Files;
3337
import java.nio.file.Path;
34-
import java.nio.file.Paths;
3538
import java.security.DigestInputStream;
3639
import java.security.MessageDigest;
3740
import java.security.NoSuchAlgorithmException;
@@ -49,6 +52,7 @@
4952
import java.util.Map.Entry;
5053
import java.util.Objects;
5154
import java.util.UUID;
55+
import java.util.concurrent.Phaser;
5256
import java.util.concurrent.TimeUnit;
5357
import java.util.concurrent.atomic.AtomicBoolean;
5458
import java.util.concurrent.atomic.AtomicReference;
@@ -63,13 +67,17 @@
6367
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
6468
import org.apache.commons.io.FileUtils;
6569
import org.apache.commons.io.IOUtils;
70+
import org.apache.commons.io.output.ByteArrayOutputStream;
6671
import org.apache.commons.lang3.SystemUtils;
6772
import org.apache.commons.lang3.time.StopWatch;
6873
import org.postgresql.ds.PGSimpleDataSource;
6974
import org.slf4j.Logger;
7075
import org.slf4j.LoggerFactory;
7176
import org.tukaani.xz.XZInputStream;
7277

78+
import static java.nio.file.StandardOpenOption.CREATE_NEW;
79+
import static java.nio.file.StandardOpenOption.WRITE;
80+
7381
@SuppressWarnings("PMD.AvoidDuplicateLiterals") // "postgres"
7482
public class EmbeddedPostgres implements Closeable
7583
{
@@ -637,16 +645,15 @@ private static String getArchitecture()
637645
* Unpack archive compressed by tar with xz compression. By default system tar is used (faster). If not found, then the
638646
* java implementation takes place.
639647
*
640-
* @param tbzPath The archive path.
648+
* @param stream A stream with the postgres binaries.
641649
* @param targetDir The directory to extract the content to.
642650
*/
643-
private static void extractTxz(String tbzPath, String targetDir) throws IOException
644-
{
651+
private static void extractTxz(InputStream stream, String targetDir) throws IOException {
645652
try (
646-
InputStream in = Files.newInputStream(Paths.get(tbzPath));
647-
XZInputStream xzIn = new XZInputStream(in);
653+
XZInputStream xzIn = new XZInputStream(stream);
648654
TarArchiveInputStream tarIn = new TarArchiveInputStream(xzIn)
649655
) {
656+
final Phaser phaser = new Phaser(1);
650657
TarArchiveEntry entry;
651658

652659
while ((entry = tarIn.getNextTarEntry()) != null) {
@@ -663,9 +670,33 @@ private static void extractTxz(String tbzPath, String targetDir) throws IOExcept
663670
throw new IllegalStateException("could not read " + individualFile);
664671
}
665672
mkdirs(fsObject.getParentFile());
666-
try (OutputStream outputFile = new FileOutputStream(fsObject)) {
667-
IOUtils.write(content, outputFile);
668-
}
673+
674+
final AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(fsObject.toPath(), CREATE_NEW, WRITE);
675+
final ByteBuffer buffer = ByteBuffer.wrap(content);
676+
677+
phaser.register();
678+
fileChannel.write(buffer, 0, fileChannel, new CompletionHandler<Integer, Channel>() {
679+
@Override
680+
public void completed(Integer written, Channel channel) {
681+
closeChannel(channel);
682+
}
683+
684+
@Override
685+
public void failed(Throwable error, Channel channel) {
686+
LOG.error("Could not write file {}", fsObject.getAbsolutePath(), error);
687+
closeChannel(channel);
688+
}
689+
690+
private void closeChannel(Channel channel) {
691+
try {
692+
channel.close();
693+
} catch (IOException e) {
694+
LOG.error("Unexpected error while closing the channel", e);
695+
} finally {
696+
phaser.arriveAndDeregister();
697+
}
698+
}
699+
});
669700
} else if (entry.isDirectory()) {
670701
mkdirs(fsObject);
671702
} else {
@@ -678,6 +709,8 @@ private static void extractTxz(String tbzPath, String targetDir) throws IOExcept
678709
fsObject.setExecutable(true);
679710
}
680711
}
712+
713+
phaser.arriveAndAwaitAdvance();
681714
}
682715
}
683716

@@ -694,10 +727,8 @@ private static File prepareBinaries(PgBinaryResolver pgBinaryResolver)
694727

695728
LOG.info("Detected a {} {} system", system, machineHardware);
696729
File pgDir;
697-
File pgTbz;
698730
final InputStream pgBinary;
699731
try {
700-
pgTbz = File.createTempFile("pgpg", "pgpg");
701732
pgBinary = pgBinaryResolver.getPgBinary(system, machineHardware);
702733
} catch (final IOException e) {
703734
throw new ExceptionInInitializerError(e);
@@ -707,16 +738,12 @@ private static File prepareBinaries(PgBinaryResolver pgBinaryResolver)
707738
throw new IllegalStateException("No Postgres binary found for " + system + " / " + machineHardware);
708739
}
709740

710-
try (DigestInputStream pgArchiveData = new DigestInputStream(
711-
pgBinary, MessageDigest.getInstance("MD5"));
712-
FileOutputStream os = new FileOutputStream(pgTbz))
713-
{
714-
IOUtils.copy(pgArchiveData, os);
741+
try (DigestInputStream pgArchiveData = new DigestInputStream(pgBinary, MessageDigest.getInstance("MD5"));
742+
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
743+
IOUtils.copy(pgArchiveData, baos);
715744
pgArchiveData.close();
716-
os.close();
717745

718746
String pgDigest = Hex.encodeHexString(pgArchiveData.getMessageDigest().digest());
719-
720747
pgDir = new File(getWorkingDirectory(), String.format("PG-%s", pgDigest));
721748

722749
mkdirs(pgDir);
@@ -725,14 +752,16 @@ private static File prepareBinaries(PgBinaryResolver pgBinaryResolver)
725752

726753
if (!pgDirExists.exists()) {
727754
try (FileOutputStream lockStream = new FileOutputStream(unpackLockFile);
728-
FileLock unpackLock = lockStream.getChannel().tryLock()) {
755+
FileLock unpackLock = lockStream.getChannel().tryLock()) {
729756
if (unpackLock != null) {
730757
try {
731758
if (pgDirExists.exists()) {
732759
throw new IllegalStateException("unpack lock acquired but .exists file is present " + pgDirExists);
733760
}
734761
LOG.info("Extracting Postgres...");
735-
extractTxz(pgTbz.getPath(), pgDir.getPath());
762+
try (ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray())) {
763+
extractTxz(bais, pgDir.getPath());
764+
}
736765
if (!pgDirExists.createNewFile()) {
737766
throw new IllegalStateException("couldn't make .exists file " + pgDirExists);
738767
}
@@ -760,10 +789,6 @@ private static File prepareBinaries(PgBinaryResolver pgBinaryResolver)
760789
} catch (final InterruptedException ie) {
761790
Thread.currentThread().interrupt();
762791
throw new ExceptionInInitializerError(ie);
763-
} finally {
764-
if (!pgTbz.delete()) {
765-
LOG.warn("could not delete {}", pgTbz);
766-
}
767792
}
768793
BINARY_DIR.set(pgDir);
769794
LOG.info("Postgres binaries at {}", pgDir);

0 commit comments

Comments
 (0)