This is an automated email from the ASF dual-hosted git repository. lhotari pushed a commit to branch branch-4.15 in repository https://gitbox.apache.org/repos/asf/bookkeeper.git
commit 1b61f0c2266003b7b892f9b23edf88d3624baac3 Author: lifepuzzlefun <[email protected]> AuthorDate: Mon Jan 8 18:26:05 2024 +0800 Try to use jdk api to create hardlink when rename file when compaction. (#3876) ### Motivation Current HardLink will create a process to execute mv like command to rename file in compaction logic. maybe we can just use jdk api to do this with lower overhead. see javadoc: https://docs.oracle.com/javase/7/docs/api/java/nio/file/Files.html#createLink(java.nio.file.Path,%20java.nio.file.Path) ### Changes test if `Files.createLink` is available in hardlink static code block. if test fails means `Files.createLink` is not available. else will use `Files.createLink` when call createHardlink (cherry picked from commit af419cccba9a945b407018e2e2962a5680d38219) --- .../org/apache/bookkeeper/bookie/EntryLogger.java | 2 +- .../java/org/apache/bookkeeper/util/HardLink.java | 32 ++++++++- .../org/apache/bookkeeper/util/TestHardLink.java | 82 ++++++++++++++++++++++ 3 files changed, 114 insertions(+), 2 deletions(-) diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/EntryLogger.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/EntryLogger.java index 28e3297ef2..b37bca1656 100644 --- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/EntryLogger.java +++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/EntryLogger.java @@ -686,7 +686,7 @@ public class EntryLogger { void removeCurCompactionLog() { synchronized (compactionLogLock) { if (compactionLogChannel != null) { - if (!compactionLogChannel.getLogFile().delete()) { + if (compactionLogChannel.getLogFile().exists() && !compactionLogChannel.getLogFile().delete()) { LOG.warn("Could not delete compaction log file {}", compactionLogChannel.getLogFile()); } diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/util/HardLink.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/util/HardLink.java index ea92867b3e..930c9598ac 100644 --- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/util/HardLink.java +++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/util/HardLink.java @@ -22,12 +22,18 @@ package org.apache.bookkeeper.util; import static java.nio.charset.StandardCharsets.UTF_8; +import com.google.common.annotations.VisibleForTesting; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Arrays; +import java.util.concurrent.atomic.AtomicBoolean; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Class for creating hardlinks. @@ -42,7 +48,7 @@ import java.util.Arrays; * efficient - and minimizes the impact of the extra buffer creations. */ public class HardLink { - + private static final Logger LOG = LoggerFactory.getLogger(HardLink.class); /** * OS Types. */ @@ -395,12 +401,19 @@ public class HardLink { return getHardLinkCommand.getMaxAllowedCmdArgLength(); } + private static final AtomicBoolean CREATE_LINK_SUPPORTED = new AtomicBoolean(true); + /* * **************************************************** * Complexity is above. User-visible functionality is below * **************************************************** */ + @VisibleForTesting + static void enableJdkLinkApi(boolean enable) { + CREATE_LINK_SUPPORTED.set(enable); + } + /** * Creates a hardlink. * @param file - existing source file @@ -416,6 +429,23 @@ public class HardLink { throw new IOException( "invalid arguments to createHardLink: link name is null"); } + + // if createLink available try first, else fall back to shell command. + if (CREATE_LINK_SUPPORTED.get()) { + try { + Path newFile = Files.createLink(linkName.toPath(), file.toPath()); + if (newFile.toFile().exists()) { + return; + } + } catch (UnsupportedOperationException e) { + LOG.error("createLink not supported", e); + CREATE_LINK_SUPPORTED.set(false); + } catch (IOException e) { + LOG.error("error when create hard link use createLink", e); + CREATE_LINK_SUPPORTED.set(false); + } + } + // construct and execute shell command String[] hardLinkCommand = getHardLinkCommand.linkOne(file, linkName); Process process = Runtime.getRuntime().exec(hardLinkCommand); diff --git a/bookkeeper-server/src/test/java/org/apache/bookkeeper/util/TestHardLink.java b/bookkeeper-server/src/test/java/org/apache/bookkeeper/util/TestHardLink.java new file mode 100644 index 0000000000..75f6cf502d --- /dev/null +++ b/bookkeeper-server/src/test/java/org/apache/bookkeeper/util/TestHardLink.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.bookkeeper.util; + +import java.io.File; +import java.io.IOException; +import java.util.UUID; +import org.apache.commons.io.FileUtils; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + + +public class TestHardLink { + + private File tempDir; + + @Before + public void setup() throws IOException { + // Create at least one file so that target disk will never be empty + tempDir = IOUtils.createTempDir("TestHardLink", "test-hardlink"); + } + + @After + public void tearDown() throws IOException { + FileUtils.deleteDirectory(tempDir); + } + + private void verifyHardLink(File origin, File linkedOrigin) throws IOException { + Assert.assertTrue(origin.exists()); + Assert.assertFalse(linkedOrigin.exists()); + + HardLink.createHardLink(origin, linkedOrigin); + + Assert.assertTrue(origin.exists()); + Assert.assertTrue(linkedOrigin.exists()); + + // when delete origin file it should be success and not exist. + origin.delete(); + Assert.assertFalse(origin.exists()); + Assert.assertTrue(linkedOrigin.exists()); + } + + @Test + public void testHardLink() throws IOException { + String uuidSuffix = UUID.randomUUID().toString(); + + // prepare file + File origin = new File(tempDir, "originFile." + uuidSuffix); + File linkedOrigin = new File(tempDir, "linkedOrigin." + uuidSuffix); + origin.createNewFile(); + + // disable jdk api link first + HardLink.enableJdkLinkApi(false); + verifyHardLink(origin, linkedOrigin); + + // prepare file + File jdkorigin = new File(tempDir, "jdkoriginFile." + uuidSuffix); + File jdklinkedOrigin = new File(tempDir, "jdklinkedOrigin." + uuidSuffix); + jdkorigin.createNewFile(); + + // enable jdk api link + HardLink.enableJdkLinkApi(true); + verifyHardLink(jdkorigin, jdklinkedOrigin); + } +}
