All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.openhft.chronicle.queue.internal.util.InternalFileUtil Maven / Gradle / Ivy

/*
 * Copyright 2016-2022 chronicle.software
 *
 *       https://chronicle.software
 *
 * Licensed 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 net.openhft.chronicle.queue.internal.util;

import net.openhft.chronicle.core.Jvm;
import net.openhft.chronicle.queue.impl.single.SingleChronicleQueue;
import net.openhft.chronicle.queue.util.FileState;
import org.jetbrains.annotations.NotNull;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import java.util.stream.Stream;

import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.toList;

/**
 * Utility methods for handling Files in connection with ChronicleQueue.
 *
 * @author Per Minborg
 * @since 5.17.34
 */
public final class InternalFileUtil {

    private static final Comparator EARLIEST_FIRST = comparing(File::getName);

    private InternalFileUtil() {
    }

    /**
     * Returns a Stream of roll Queue files that are likely removable
     * from the given {@code baseDir} without affecting any Queue
     * process that is currently active in the given {@code baseDir} reading
     * data sequentially.
     * 

* Files are returned in order of creation and can successively be removed * in that order. If the removal of a particular file fails, then subsequent * files must be untouched. *

* WARNING: This method is inherently un-deterministic as new Queue processes may * join or leave at any time asynchronously. Thus, it is not recommended to store * results produced by this method for longer periods. *

* Only sequential reading is supported because random access Tailers can read at * any location at any time. *

* Here is an example of how unused files can be removed: * *

{@code
     *     for (File file : removableFileCandidates(baseDir).collect(Collectors.toList())) {
     *         if (!file.delete()) {
     *             break;
     *         }
     *     }
     * }
* * @param baseDir containing queue file removal candidates * @return a Stream of roll Queue files that are likely removable * from the given {@code baseDir} without affecting any Queue * process that is currently active in the given {@code baseDir} * reading data sequentially * @throws UnsupportedOperationException if this operation is not * supported for the current platform (e.g. Windows). */ @NotNull public static Stream removableRollFileCandidates(@NotNull File baseDir) { assertOsSupported(); final File[] files = baseDir.listFiles(InternalFileUtil::hasQueueSuffix); if (files == null) return Stream.empty(); final List sortedInitialCandidates = Stream.of(files) .sorted(EARLIEST_FIRST) .collect(toList()); final Stream.Builder builder = Stream.builder(); try { final Map allOpenFiles = getAllOpenFiles(); for (File file : sortedInitialCandidates) { // If one file is not closed, discard it and the rest in the sequence if (state(file, allOpenFiles) != FileState.CLOSED) break; builder.accept(file); } return builder.build(); } catch (IOException e) { Jvm.warn().on(InternalFileUtil.class, "Error getting removable candidates", e); return Stream.empty(); } } /** * Returns if the provided {@code file} has the Chronicle Queue file * suffix. The current file suffix is ".cq4". * * @param file to check * @return true if the provided {@code file} has the ChronicleQueue file suffix */ public static boolean hasQueueSuffix(@NotNull File file) { return file.getName().endsWith(SingleChronicleQueue.SUFFIX); } /** * Returns if the given {@code file } is used by any process (i.e. * has the file open for reading or writing). *

* If the open state of the given {@code file} can not be determined, {@code true } * is returned. * * @param file to check * @return FileState if the given {@code file } is used by any process * @throws UnsupportedOperationException if this operation is not * supported for the current platform (e.g. Windows). */ public static FileState state(@NotNull File file) { try { return state(file, getAllOpenFiles()); } catch (IOException e) { // Do nothing } return FileState.UNDETERMINED; } /** * Returns if the given {@code file } is used by any process (i.e. * has the file open for reading or writing). *

* If the open state of the given {@code file} can not be determined, {@code true } * is returned. * * @param file to check * @param allOpenFiles The map of all open files retrieve from {@link #getAllOpenFiles()} * @return FileState if the given {@code file } is used by any process * @throws UnsupportedOperationException if this operation is not * supported for the current platform (e.g. Windows). */ public static FileState state(@NotNull File file, Map allOpenFiles) { assertOsSupported(); if (!file.exists()) return FileState.NON_EXISTENT; final String absolutePath = file.getAbsolutePath(); return allOpenFiles.keySet().contains(absolutePath) ? FileState.OPEN : FileState.CLOSED; } /** * Returns if the given {@code file } is used by any process (i.e. * has the file open for reading or writing). * * @param file to check * @return FileState if the given {@code file } is used by any process */ // Todo: Here is a candidate for Windows. Verify that it works private static FileState stateWindows(@NotNull File file) { if (!file.exists()) return FileState.NON_EXISTENT; try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw"); FileChannel fileChannel = randomAccessFile.getChannel()) { final FileLock fileLock = fileChannel.tryLock(); if (fileLock != null) { fileLock.close(); return FileState.CLOSED; } return FileState.OPEN; } catch (IOException ignored) { // Do nothing } return FileState.UNDETERMINED; } private static void assertOsSupported() { if (!getAllOpenFilesIsSupportedOnOS()) { throw new UnsupportedOperationException("This operation is not supported on your operating system"); } } /** * Will {@link #getAllOpenFiles()} work on the current OS? * * @return true if getting all open files is supported, false otherwise */ public static boolean getAllOpenFilesIsSupportedOnOS() { return Files.exists(Paths.get("/proc/self/fd")); } /** * Get the distinct files currently open by any process, including the PID of the process holding * the file open * * @return a {@link Map} of the absolute paths to all the open files on the system, mapped to the PID hold the file open * @throws IOException if we can't get the open files */ public static Map getAllOpenFiles() throws IOException { assertOsSupported(); final ProcFdWalker visitor = new ProcFdWalker(); Files.walkFileTree(Paths.get("/proc/"), Collections.emptySet(), 3, visitor); return visitor.openFiles; } private static class ProcFdWalker extends SimpleFileVisitor { private final static int PID_PATH_INDEX = 1; // where is the pid for process holding file open represented in path? private final Map openFiles = new HashMap<>(); @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { if (file.toAbsolutePath().toString().matches("/proc/\\d+/fd/\\d+")) { try { final String e = file.toRealPath().toAbsolutePath().toString(); final String pid = file.getName(PID_PATH_INDEX).toString(); // pid holding file open openFiles.put(e, pid); } catch (NoSuchFileException | AccessDeniedException e) { // Ignore, sometimes they disappear & we can't access all the files } catch (IOException e) { Jvm.warn().on(ProcFdWalker.class, "Error resolving " + file, e); } } return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFileFailed(Path file, IOException exc) { // we definitely won't be able to access all the files, and it's common for a file to go missing mid-traversal // so don't log when one of those things happens if (!((exc instanceof AccessDeniedException) || (exc instanceof NoSuchFileException))) { Jvm.warn().on(ProcFdWalker.class, "Error visiting file", exc); } return FileVisitResult.CONTINUE; } @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { if (!dir.toAbsolutePath().toString().matches("/proc(/\\d+(/fd)?)?")) { return FileVisitResult.SKIP_SUBTREE; } return FileVisitResult.CONTINUE; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy