From 29e946c48309b83ee766ce88e6233265b1524419 Mon Sep 17 00:00:00 2001 From: Michael Barker Date: Thu, 13 Jul 2023 12:01:08 +1200 Subject: [PATCH] [Java] Add method to ensuring that a link file exists. --- agrona/src/main/java/org/agrona/MarkFile.java | 75 ++++++++++++++++++- .../test/java/org/agrona/MarkFileTest.java | 40 +++++++++- 2 files changed, 111 insertions(+), 4 deletions(-) diff --git a/agrona/src/main/java/org/agrona/MarkFile.java b/agrona/src/main/java/org/agrona/MarkFile.java index 39b9ac028..c9e15b618 100644 --- a/agrona/src/main/java/org/agrona/MarkFile.java +++ b/agrona/src/main/java/org/agrona/MarkFile.java @@ -22,12 +22,19 @@ import java.io.IOException; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import java.util.function.IntConsumer; import static java.nio.channels.FileChannel.MapMode.READ_WRITE; -import static java.nio.file.StandardOpenOption.*; +import static java.nio.charset.StandardCharsets.US_ASCII; +import static java.nio.file.StandardOpenOption.CREATE; +import static java.nio.file.StandardOpenOption.READ; +import static java.nio.file.StandardOpenOption.SPARSE; +import static java.nio.file.StandardOpenOption.WRITE; import static org.agrona.BitUtil.SIZE_OF_INT; import static org.agrona.BitUtil.SIZE_OF_LONG; @@ -703,6 +710,72 @@ public static boolean isActive( return timestampAgeMs <= timeoutMs; } + /** + * Ensure a link file exists if required for the actual mark file. A link file will contain the pathname of the + * actual mark file. This is useful if the mark file should be stored on a different storage medium to the directory + * of the service. This will create a file with name of {@code linkFilename} in the {@code serviceDir} that will + * contain the parent directory of {@code actualFile}. If {@code actualFile} is an immediate child of {@code + * serviceDir} then any file with the name of {@code linkFilename} will be deleted from the {@code serviceDir} (so + * that links won't be present if not required). + * + * @param serviceDir directory where the mark file would normally be stored (e.g. archiveDir, clusterDir). + * @param actualFile location of actual mark file, e.g. /dev/shm/service/node0/archive-mark.dat + * @param linkFilename short name that should be used for the link file, e.g. archive-mark.lnk + */ + public static void ensureMarkFileLink(final File serviceDir, final File actualFile, final String linkFilename) + { + final String archiveDirPath; + final String markFileParentPath; + + try + { + archiveDirPath = serviceDir.getCanonicalPath(); + } + catch (final IOException ex) + { + throw new IllegalArgumentException("failed to resolve canonical path for archiveDir=" + serviceDir); + } + + try + { + markFileParentPath = actualFile.getParentFile().getCanonicalPath(); + } + catch (final IOException ex) + { + throw new IllegalArgumentException( + "failed to resolve canonical path for markFile parent dir of " + actualFile); + } + + final Path linkFile = new File(archiveDirPath, linkFilename).toPath(); + if (archiveDirPath.equals(markFileParentPath)) + { + try + { + Files.deleteIfExists(linkFile); + } + catch (final IOException ex) + { + throw new RuntimeException("failed to remove old link file", ex); + } + } + else + { + try + { + Files.write( + linkFile, + markFileParentPath.getBytes(US_ASCII), + StandardOpenOption.CREATE, + StandardOpenOption.WRITE, + StandardOpenOption.TRUNCATE_EXISTING); + } + catch (final IOException ex) + { + throw new RuntimeException("failed to create link for mark file directory", ex); + } + } + } + /** * Put thread to sleep for the given duration and restore interrupted status if thread is interrupted while * sleeping. diff --git a/agrona/src/test/java/org/agrona/MarkFileTest.java b/agrona/src/test/java/org/agrona/MarkFileTest.java index f154aea9a..aba0ec3fc 100644 --- a/agrona/src/test/java/org/agrona/MarkFileTest.java +++ b/agrona/src/test/java/org/agrona/MarkFileTest.java @@ -26,19 +26,25 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; +import java.util.List; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; class MarkFileTest { @TempDir - File directory; + File serviceDirectory; + @TempDir + File alternativeDirectory; @Test void shouldWaitForMarkFileToContainEnoughDataForVersionCheck() throws IOException { final String filename = "markfile.dat"; - final Path markFilePath = directory.toPath().resolve(filename); + final Path markFilePath = serviceDirectory.toPath().resolve(filename); Files.createFile(markFilePath); try (FileChannel channel = FileChannel.open(markFilePath, StandardOpenOption.WRITE)) @@ -47,6 +53,34 @@ void shouldWaitForMarkFileToContainEnoughDataForVersionCheck() throws IOExceptio } assertThrows(IllegalStateException.class, - () -> new MarkFile(directory, filename, 0, 16, 10, new SystemEpochClock(), (v) -> {}, (msg) -> {})); + () -> new MarkFile(serviceDirectory, filename, 0, 16, 10, new SystemEpochClock(), (v) -> {}, (msg) -> {})); + } + + @Test + void shouldCreateLinkFileIfFileInDifferentLocation() throws IOException + { + final String linkFilename = "markfile.lnk"; + final File markFileLocation = new File(alternativeDirectory, "markfile.dat"); + + MarkFile.ensureMarkFileLink(serviceDirectory, markFileLocation, linkFilename); + final File linkFileLocation = new File(serviceDirectory, linkFilename); + assertTrue(linkFileLocation.exists()); + final List strings = Files.readAllLines(linkFileLocation.toPath()); + assertEquals(1, strings.size()); + assertEquals(markFileLocation.getCanonicalFile().getParent(), strings.get(0)); + } + + @Test + void shouldRemoveLinkFileIfMarkFileIsInServiceDirectory() throws IOException + { + final String linkFilename = "markfile.lnk"; + final File markFileLocation = new File(serviceDirectory, "markfile.dat"); + final File linkFileLocation = new File(serviceDirectory, linkFilename); + + assertTrue(linkFileLocation.createNewFile()); + assertTrue(linkFileLocation.exists()); + + MarkFile.ensureMarkFileLink(serviceDirectory, markFileLocation, linkFilename); + assertFalse(linkFileLocation.exists()); } }