| /*- |
| * Copyright (C) 2002, 2018, Oracle and/or its affiliates. All rights reserved. |
| * |
| * This file was distributed by Oracle as part of a version of Oracle Berkeley |
| * DB Java Edition made available at: |
| * |
| * http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html |
| * |
| * Please see the LICENSE file included in the top-level directory of the |
| * appropriate version of Oracle Berkeley DB Java Edition for a copy of the |
| * license and additional information. |
| */ |
| |
| package com.sleepycat.je.cleaner; |
| |
| import java.io.File; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.NavigableMap; |
| import java.util.NavigableSet; |
| import java.util.SortedSet; |
| import java.util.TreeMap; |
| import java.util.TreeSet; |
| |
| import com.sleepycat.je.EnvironmentFailureException; |
| import com.sleepycat.je.dbi.EnvironmentImpl; |
| import com.sleepycat.je.log.FileManager; |
| import com.sleepycat.je.utilint.DbLsn; |
| import com.sleepycat.je.utilint.Pair; |
| import com.sleepycat.je.utilint.VLSN; |
| import com.sleepycat.utilint.FormatUtil; |
| |
| import org.checkerframework.checker.nullness.qual.Nullable; |
| |
| /** |
| * The FileProtector is primarily responsible for protecting files from being |
| * deleted due to log cleaning, when they are needed for other purposes. As |
| * such it is a gatekeeper for reading files. In addition it maintains certain |
| * metadata: |
| * - the size of each file, which is needed for calculating disk usage; |
| * - the first and last VLSNs in each reserved file, which are needed for |
| * maintaining the VLSNIndex; |
| * - the total size of active and reserved files, for disk usage statistics. |
| * |
| * All files are in three categories: |
| * + Active: readable by all. The minimum set of files needed to function. |
| * + Reserved: should be read only by feeders and low level utilities. |
| * They have been cleaned and will not become active again. They can be |
| * "condemned" and deleted if they are not being read. |
| * + Condemned: not readable and effectively invisible. They will be deleted |
| * ASAP. They will not become active or reserved again. A file is typically |
| * in the Condemned category for a very short time, while being deleted. |
| * |
| * Reserved files can be temporarily protected, i.e., prevented from being |
| * condemned and deleted. Only reserved files can be condemned and deleted. All |
| * active files are implicitly protected, but are also protected explicitly by |
| * consumers because they may become reserved while being consumed. |
| * |
| * Consumers of the File Protection Service |
| * ---------------------------------------- |
| * |
| * + DiskOrderedScanner (DiskOrderedCursor and Database.count) |
| * - Protects all currently active files. By currently active we mean active |
| * at the time they are protected. If they become reserved during the |
| * scan, they must continue to be protected. |
| * - Also protects any new files written during the scan. |
| * |
| * + DbBackup: |
| * - Protects-and-lists all currently active files, or a subset of the |
| * currently active files in the case of an incremental backup. |
| * - Provides API to remove files from protected set as they are copied, to |
| * allow file deletion. |
| * |
| * + NetworkRestore: |
| * - Protects-and-lists all currently active files using DbBackup. |
| * - Also protects the two newest reserved files at the time that the active |
| * files are protected. |
| * - Removes files from protected set as they are copied, to allow file |
| * deletion. |
| * |
| * + Syncup: |
| * - Protects all files (active and reserved) in an open ended range starting |
| * with the file of the VLSNIndex range start. Barren files (with no |
| * replicable entries) are not protected. |
| * |
| * + Feeder: |
| * - Protects all files (active and reserved) in an open ended range starting |
| * with the file of the current VLSN. Barren files (with no replicable |
| * entries) are not protected. |
| * - Advances lower bound of protected range as VLSN advances, to allow file |
| * deletion. |
| * |
| * + Cleaner: |
| * - Transforms active files into reserved files after cleaning them. |
| * - Condemns and deletes reserved files to honor disk limits. Truncates head |
| * of VLSNIndex when necessary to stay with disk thresholds. |
| * |
| * Syncup, Feeders and the VLSNIndex |
| * --------------------------------- |
| * During syncup, a ProtectedFileRange is used to protect files in the entire |
| * range of the VLSNIndex. Syncup must also prevent head truncation of the |
| * VLSNIndex itself because the file readers (used by both master and replica) |
| * use the VLSNIndex to position in the file at various times. |
| * |
| * A feeder file reader also protects all files from the current VLSN onward |
| * using a ProtectedFileRange. We rely on syncup to initialize the feeder's |
| * ProtectedFileRange safely, while the syncup ProtectedFileRange is in effect. |
| * The feeder reader will advance the lower bound of its ProtectedFileRange as |
| * it reads forward to allow files to be deleted. It also uses the VLNSIndex to |
| * skip over gaps in the file, although it is unclear whether this is really |
| * necessary. |
| * |
| * Therefore the syncup and feeder ProtectedFileRanges are special in that |
| * they also prevent head truncation of the VLSNIndex. |
| * |
| * The cleaner truncates the head of the VLSNIndex to allow deletion of files |
| * when necessary to stay within disk usage limits. This truncation must not |
| * be allowed when a syncup is in progress, and must not be allowed to remove |
| * the portion of the VLSN range used by a feeder. This is enforced using a |
| * special ProtectedFileRange (vlsnIndexRange) that protects the entire |
| * VLSNIndex range. The vlsnIndexRange is advanced when necessary to delete |
| * files that it protects, but only if those files are not protected by syncup |
| * or feeders. See {@link #checkVLSNIndexTruncation}, {@link |
| * com.sleepycat.je.rep.vlsn.VLSNTracker#tryTruncateFromHead} and {@link |
| * com.sleepycat.je.rep.vlsn.VLSNTracker#protectRangeHead}. |
| * |
| * We takes pains to avoid synchronizing on FileProtector while truncating the |
| * VLSNIndex head, which is a relatively expensive operation. (The |
| * FileProtector is meant to be used by multiple threads without a lot of |
| * blocking and should perform only a fairly small amount of work while |
| * synchronized.) The following approach is used to truncate the VLSNIndex head |
| * safely: |
| * |
| * -- To prevent disk usage limit violations, Cleaner.manageDiskUsage first |
| * tries to delete reserved files without truncating the VLSNIndex. If this |
| * is not sufficient, it then tries to truncate the VLSNIndex head. If the |
| * VLSNIndex head can be truncated, then it tries again to delete reserved |
| * files, since more files should then be unprotected. |
| * |
| * -- VLSNTracker synchronization is used to protect the VLSNIndex range. The |
| * vlsnIndexRange ProtectedFileRange is advanced only while synchronized on |
| * the VLSNTracker. |
| * |
| * -- VLSNTracker.tryTruncateFromHead (which is synchronized) calls |
| * FileProtector.checkVLSNIndexTruncation to determine where to truncate the |
| * index. Reserved files can be removed from the VLSNIndex range only if |
| * they are not protected by syncup and feeders. |
| * |
| * -- The VLSNTracker range is then truncated, and the vlsnIndexRange is |
| * advanced to allow file deletion, all while synchronized on the tracker. |
| * |
| * -- When a syncup starts, it adds a ProtectedFileRange with the same |
| * startFile as the vlsnIndexRange. This is done while synchronized on the |
| * VLSNTracker and it prevents the vlsnIndexRange from advancing during the |
| * syncup. |
| * |
| * -- When a syncup is successful, on the master the Feeder is initialized and |
| * it adds a ProtectedFileRange to protect the range of the VLSNIndex that |
| * it is reading. This is done BEFORE removing the ProtectedFileRange that |
| * was added at the start of the syncup. This guarantees that the files and |
| * VLSNIndex range used by the feeder will not be truncated/deleted. |
| * |
| * Note that the special vlsnIndexRange ProtectedFileRange is excluded from |
| * LogSizeStats to avoid confusion and because this ProtectedFileRange does not |
| * ultimately prevent VLSNIndex head truncation or file deletion. |
| * |
| * Barren Files |
| * ------------ |
| * Files with no replicable entries do not need to be protected by feeders. |
| * See {@link #protectFileRange(String, long, boolean, boolean)}. Barren files |
| * may be created when cleaning is occurring but app writes are not, for |
| * example, when recovering from a cache size configuration error. In this |
| * situation it is important to delete the barren files to reclaim disk space. |
| * |
| * Such "barren" files are identified by having null begin/end VLSNs. The |
| * begin/end VLSNs for a file are part of the cleaner metadata that is |
| * collected when the cleaner processes a file. These VLSNs are for replicable |
| * entries only, not migrated entries that happen to contain a VLSN. |
| */ |
| public class FileProtector { |
| |
| /* Prefixes for ProtectedFileSet names. */ |
| public static final String BACKUP_NAME = "Backup"; |
| public static final String DATABASE_COUNT_NAME = "DatabaseCount"; |
| public static final String DISK_ORDERED_CURSOR_NAME = "DiskOrderedCursor"; |
| public static final String FEEDER_NAME = "Feeder"; |
| public static final String SYNCUP_NAME = "Syncup"; |
| public static final String VLSN_INDEX_NAME = "VLSNIndex"; |
| public static final String NETWORK_RESTORE_NAME = "NetworkRestore"; |
| |
| private static class ReservedFileInfo { |
| |
| final long size; |
| final VLSN endVLSN; |
| |
| ReservedFileInfo(long size, VLSN endVLSN) { |
| this.size = size; |
| this.endVLSN = endVLSN; |
| } |
| } |
| |
| private final EnvironmentImpl envImpl; |
| |
| /* Access active files only via getActiveFiles. */ |
| private final NavigableMap<Long, Long> activeFiles = new TreeMap<>(); |
| |
| private final NavigableMap<Long, ReservedFileInfo> reservedFiles = |
| new TreeMap<>(); |
| |
| private final NavigableMap<Long, Long> condemnedFiles = new TreeMap<>(); |
| |
| private final Map<String, ProtectedFileSet> protectedFileSets = |
| new HashMap<>(); |
| |
| /* Is null if the env is not replicated. */ |
| private ProtectedFileRange vlsnIndexRange; |
| |
| FileProtector(final EnvironmentImpl envImpl) { |
| this.envImpl = envImpl; |
| } |
| |
| private void addFileProtection(ProtectedFileSet pfs) { |
| |
| if (protectedFileSets.putIfAbsent(pfs.getName(), pfs) != null) { |
| |
| throw EnvironmentFailureException.unexpectedState( |
| "ProtectedFileSets already present name=" + pfs.getName()); |
| } |
| } |
| |
| /** |
| * Removes protection by the given ProtectedFileSet to allow files to be |
| * deleted. |
| */ |
| public synchronized void removeFileProtection(ProtectedFileSet pfs) { |
| |
| final ProtectedFileSet oldPfs = |
| protectedFileSets.remove(pfs.getName()); |
| |
| if (oldPfs == null) { |
| throw EnvironmentFailureException.unexpectedState( |
| "ProtectedFileSet not found name=" + pfs.getName()); |
| } |
| |
| if (oldPfs != pfs) { |
| throw EnvironmentFailureException.unexpectedState( |
| "ProtectedFileSet mismatch name=" + pfs.getName()); |
| } |
| } |
| |
| /** |
| * Calls {@link #protectFileRange(String, long, boolean, boolean)} passing |
| * false for protectVlsnIndex and true for protectBarrenFiles. |
| */ |
| public synchronized ProtectedFileRange protectFileRange( |
| final String name, |
| final long rangeStart) { |
| |
| return protectFileRange(name, rangeStart, false, true); |
| } |
| |
| /** |
| * Returns a ProtectedFileRange that protects files with numbers GTE a |
| * lower bound. The upper bound is open ended. The protectVlsnIndex param |
| * should be true for feeder/syncup file protection only. |
| * |
| * @param rangeStart is the first file to be protected in the range. |
| * |
| * @param protectVlsnIndex is whether to prevent the VLSNIndex head from |
| * advancing. |
| * |
| * @param protectBarrenFiles is whether barren files (having no replicable |
| * entries) are protected. |
| */ |
| public synchronized ProtectedFileRange protectFileRange( |
| final String name, |
| final long rangeStart, |
| final boolean protectVlsnIndex, |
| final boolean protectBarrenFiles) { |
| |
| final ProtectedFileRange fileRange = new ProtectedFileRange( |
| name, rangeStart, protectVlsnIndex, protectBarrenFiles); |
| |
| addFileProtection(fileRange); |
| return fileRange; |
| } |
| |
| /** |
| * Calls {@link #protectActiveFiles(String, int, boolean)} passing 0 for |
| * nReservedFiles and true for protectNewFiles. |
| */ |
| public synchronized ProtectedActiveFileSet protectActiveFiles( |
| final String name) { |
| |
| return protectActiveFiles(name, 0, true); |
| } |
| |
| /** |
| * Returns a ProtectedActiveFileSet that protects all files currently |
| * active at the time of construction. These files are protected even if |
| * they later become reserved. Note that this does not include the last |
| * file at the time of construction. Additional files can also be |
| * protected -- see params. |
| * |
| * @param nReservedFiles if greater than zero, this number of the newest |
| * (highest numbered) reserved files are also protected. |
| * |
| * @param protectNewFiles if true, the last file and any new files created |
| * later are also protected. |
| */ |
| public synchronized ProtectedActiveFileSet protectActiveFiles( |
| final String name, |
| final int nReservedFiles, |
| final boolean protectNewFiles) { |
| |
| final NavigableMap<Long, Long> activeFiles = getActiveFiles(); |
| |
| final NavigableSet<Long> protectedFiles = |
| new TreeSet<>(activeFiles.keySet()); |
| |
| if (nReservedFiles > 0) { |
| int n = nReservedFiles; |
| for (Long file : reservedFiles.descendingKeySet()) { |
| protectedFiles.add(file); |
| n -= 1; |
| if (n <= 0) { |
| break; |
| } |
| } |
| } |
| |
| final Long rangeStart = protectNewFiles ? |
| (protectedFiles.isEmpty() ? 0 : (protectedFiles.last() + 1)) : |
| null; |
| |
| final ProtectedActiveFileSet pfs = |
| new ProtectedActiveFileSet(name, protectedFiles, rangeStart); |
| |
| addFileProtection(pfs); |
| return pfs; |
| } |
| |
| /** |
| * Freshens and returns the active files. The last file is not included |
| * because its length is still changing. |
| * |
| * Gets new file info lazily to prevent synchronization and work in the |
| * CRUD code path when a new file is added. |
| */ |
| private synchronized NavigableMap<Long, Long> getActiveFiles() { |
| |
| final FileManager fileManager = envImpl.getFileManager(); |
| |
| /* |
| * Add all existing files when the env is first opened (except for the |
| * last file -- see below). This is a relatively expensive but one-time |
| * initialization. |
| */ |
| if (activeFiles.isEmpty()) { |
| |
| final Long[] files = fileManager.getAllFileNumbers(); |
| |
| for (int i = 0; i < files.length - 1; i++) { |
| final long file = files[i]; |
| |
| final File fileObj = |
| new File(fileManager.getFullFileName(file)); |
| |
| activeFiles.put(file, fileObj.length()); |
| } |
| |
| if (activeFiles.isEmpty()) { |
| return activeFiles; |
| } |
| } |
| |
| /* |
| * Add new files that have appeared. This is very quick, because no |
| * synchronization is required to get the last file number. Do not |
| * add the last file, since its length may still be changing. |
| */ |
| final long lastFile = DbLsn.getFileNumber(fileManager.getNextLsn()); |
| final long firstNewFile = activeFiles.lastKey() + 1; |
| |
| for (long file = firstNewFile; file < lastFile; file += 1) { |
| |
| final File fileObj = |
| new File(fileManager.getFullFileName(file)); |
| |
| /* New files should be active before being reserved and deleted. */ |
| if (!fileObj.exists() && !envImpl.isMemOnly()) { |
| throw EnvironmentFailureException.unexpectedState( |
| "File 0x" + Long.toHexString(file) + |
| " lastFile=" + Long.toHexString(lastFile)); |
| } |
| |
| activeFiles.put(file, fileObj.length()); |
| } |
| |
| return activeFiles; |
| } |
| |
| /** |
| * Moves a file from active status to reserved status. |
| */ |
| synchronized void reserveFile(Long file, VLSN endVLSN) { |
| |
| final NavigableMap<Long, Long> activeFiles = getActiveFiles(); |
| |
| final Long size = activeFiles.remove(file); |
| |
| if (size == null) { |
| throw EnvironmentFailureException.unexpectedState( |
| "Only active files (not the last file) may be" + |
| " cleaned/reserved file=0x" + Long.toHexString(file) + |
| " exists=" + envImpl.getFileManager().isFileValid(file) + |
| " reserved=" + reservedFiles.containsKey(file) + |
| " nextLsn=" + DbLsn.getNoFormatString( |
| envImpl.getFileManager().getNextLsn())); |
| } |
| |
| final ReservedFileInfo info = new ReservedFileInfo(size, endVLSN); |
| final ReservedFileInfo prevInfo = reservedFiles.put(file, info); |
| assert prevInfo == null; |
| } |
| |
| /** |
| * Changes a file's state from reserved to active. |
| */ |
| synchronized void reactivateReservedFile(Long file, long size) { |
| reservedFiles.remove(file); |
| getActiveFiles().put(file, size); |
| } |
| |
| /** |
| * Returns a set of all files except for the last file. |
| */ |
| synchronized NavigableSet<Long> getAllCompletedFiles() { |
| |
| final NavigableSet<Long> set = |
| new TreeSet<>(getActiveFiles().keySet()); |
| |
| set.addAll(reservedFiles.keySet()); |
| set.addAll(condemnedFiles.keySet()); |
| |
| return set; |
| } |
| |
| /** |
| * Returns the number of active files, including the last file. |
| */ |
| synchronized int getNActiveFiles() { |
| |
| final NavigableMap<Long, Long> activeFiles = getActiveFiles(); |
| int count = activeFiles.size(); |
| |
| if (activeFiles.isEmpty() || |
| activeFiles.lastKey() < |
| envImpl.getFileManager().getCurrentFileNum()) { |
| count += 1; |
| } |
| |
| return count; |
| } |
| |
| /** |
| * Returns the number of reserved files. |
| */ |
| synchronized int getNReservedFiles() { |
| return reservedFiles.size(); |
| } |
| |
| /** |
| * Returns a copy of the reserved files along with the total size. |
| */ |
| public synchronized Pair<Long, NavigableSet<Long>> getReservedFileInfo() { |
| long size = 0; |
| for (final ReservedFileInfo info : reservedFiles.values()) { |
| size += info.size; |
| } |
| return new Pair<>(size, new TreeSet<>(reservedFiles.keySet())); |
| } |
| |
| /** |
| * Returns whether the given file is active, including the last file |
| * whether or not it has been created on disk yet. If false is returned, |
| * the file is reserved, condemned or deleted. |
| */ |
| public synchronized boolean isActiveOrNewFile(Long file) { |
| |
| final NavigableMap<Long, Long> activeFiles = getActiveFiles(); |
| |
| return activeFiles.isEmpty() || |
| file > activeFiles.lastKey() || |
| activeFiles.containsKey(file); |
| } |
| |
| /** |
| * Returns whether the given file is in the reserved file set. |
| */ |
| public synchronized boolean isReservedFile(Long file) { |
| return reservedFiles.containsKey(file); |
| } |
| |
| /* |
| * If the given file is a reserved file, returns its last VLSN |
| */ |
| synchronized VLSN getReservedFileLastVLSN(Long file) { |
| final ReservedFileInfo info = reservedFiles.get(file); |
| return (info == null) ? null : info.endVLSN; |
| } |
| |
| /** |
| * Returns a previously condemned file or the oldest unprotected reserved |
| * file. If non-null is returned the file is removed from the |
| * FileProtector's data structures and is effectively condemned, so if it |
| * cannot be deleted by the caller then {@link #putBackCondemnedFile} |
| * should be called so the file deletion can be retried later. |
| * |
| * @param fromFile the lowest file number to return. Used to iterate over |
| * reserved files that are protected. |
| * |
| * @return {file, size} pair or null if a condemned file is not available. |
| */ |
| synchronized Pair<Long, Long> takeNextCondemnedFile(long fromFile) { |
| |
| if (!condemnedFiles.isEmpty()) { |
| final Long file = condemnedFiles.firstKey(); |
| final Long size = condemnedFiles.remove(file); |
| return new Pair<>(file, size); |
| } |
| |
| if (reservedFiles.isEmpty()) { |
| return null; |
| } |
| |
| for (final Map.Entry<Long, ReservedFileInfo> entry : |
| reservedFiles.tailMap(fromFile).entrySet()) { |
| |
| final Long file = entry.getKey(); |
| final ReservedFileInfo info = entry.getValue(); |
| |
| if (isProtected(file, info)) { |
| continue; |
| } |
| |
| reservedFiles.remove(file); |
| return new Pair<>(file, info.size); |
| } |
| |
| return null; |
| } |
| |
| /** |
| * If the given file was previously condemned, or is reserved and |
| * unprotected, this method removes it from the FileProtector's data |
| * structures and returns its size. If non-null is returned the file is |
| * effectively condemned, so if it cannot be deleted by the caller then |
| * {@link #putBackCondemnedFile} should be called so the file deletion |
| * can be retried later. |
| * |
| * @return the size, or null if the file is not condemned and is not |
| * reserved and unprotected. |
| */ |
| synchronized Long takeCondemnedFile(Long file) { |
| |
| final Long condemnedSize = condemnedFiles.remove(file); |
| |
| if (condemnedSize != null) { |
| return condemnedSize; |
| } |
| |
| final ReservedFileInfo info = reservedFiles.get(file); |
| |
| if (info != null && !isProtected(file, info)) { |
| reservedFiles.remove(file); |
| return info.size; |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Returns whether the given file is protected. |
| */ |
| private synchronized boolean isProtected(final Long file, |
| final ReservedFileInfo info) { |
| |
| for (final ProtectedFileSet pfs : protectedFileSets.values()) { |
| if (pfs.isProtected(file, info)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Puts back a condemned file after a file returned by {@link |
| * #takeNextCondemnedFile}, or passed to {@link #takeCondemnedFile}, could |
| * not be deleted. |
| */ |
| synchronized void putBackCondemnedFile(Long file, Long size) { |
| final Long oldSize = condemnedFiles.put(file, size); |
| assert oldSize == null; |
| } |
| |
| /** |
| * Returns the lowest-valued file number in the given set that is not |
| * protected. |
| */ |
| synchronized Long getFirstUnprotectedFile(NavigableSet<Long> files) { |
| |
| for (final Long file : files) { |
| if (!isProtected(file, reservedFiles.get(file))) { |
| return file; |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * For the given files, returns map of file protector name to files |
| * protected. |
| */ |
| synchronized NavigableMap<String, String> getProtectedFileMap( |
| final SortedSet<Long> files) { |
| |
| final Map<String, SortedSet<Long>> map = new HashMap<>(); |
| |
| for (final Long file : files) { |
| final ReservedFileInfo info = reservedFiles.get(file); |
| |
| for (final ProtectedFileSet pfs : protectedFileSets.values()) { |
| |
| if (!pfs.isProtected(file, info)) { |
| continue; |
| } |
| |
| SortedSet<Long> set = map.get(pfs.getName()); |
| if (set == null) { |
| set = new TreeSet<>(); |
| } |
| set.add(file); |
| |
| map.put(pfs.getName(), set); |
| } |
| } |
| |
| final NavigableMap<String, String> mapResult = new TreeMap<>(); |
| |
| for (final Map.Entry<String, SortedSet<Long>> entry : map.entrySet()) { |
| mapResult.put( |
| entry.getKey(), FormatUtil.asHexString(entry.getValue())); |
| } |
| |
| return mapResult; |
| } |
| |
| static class LogSizeStats { |
| final long activeSize; |
| final long reservedSize; |
| final long protectedSize; |
| final Map<String, Long> protectedSizeMap; |
| |
| LogSizeStats(final long activeSize, |
| final long reservedSize, |
| final long protectedSize, |
| final Map<String, Long> protectedSizeMap) { |
| this.activeSize = activeSize; |
| this.reservedSize = reservedSize; |
| this.protectedSize = protectedSize; |
| this.protectedSizeMap = protectedSizeMap; |
| } |
| } |
| |
| /** |
| * Returns sizes occupied by active, reserved and protected files. |
| */ |
| synchronized LogSizeStats getLogSizeStats() { |
| |
| /* Calculate active size. */ |
| final NavigableMap<Long, Long> activeFiles = getActiveFiles(); |
| long activeSize = 0; |
| |
| for (final long size : activeFiles.values()) { |
| activeSize += size; |
| } |
| |
| /* Add size of last file, which is not included in activeFiles. */ |
| final long lastFileNum = activeFiles.isEmpty() ? |
| 0 : activeFiles.lastKey() + 1; |
| |
| final File lastFile = new File( |
| envImpl.getFileManager().getFullFileName(lastFileNum)); |
| |
| if (lastFile.exists()) { |
| activeSize += lastFile.length(); |
| } |
| |
| /* Calculate reserved and protected sizes. */ |
| long reservedSize = 0; |
| long protectedSize = 0; |
| final Map<String, Long> protectedSizeMap = new HashMap<>(); |
| |
| for (final Map.Entry<Long, ReservedFileInfo> entry : |
| reservedFiles.entrySet()) { |
| |
| final Long file = entry.getKey(); |
| final ReservedFileInfo info = entry.getValue(); |
| reservedSize += info.size; |
| boolean isProtected = false; |
| |
| for (final ProtectedFileSet pfs : protectedFileSets.values()) { |
| |
| if (pfs == vlsnIndexRange || !pfs.isProtected(file, info)) { |
| continue; |
| } |
| |
| isProtected = true; |
| |
| protectedSizeMap.compute( |
| pfs.getName(), |
| (k, v) -> ((v != null) ? v : 0) + info.size); |
| } |
| |
| if (isProtected) { |
| protectedSize += info.size; |
| } |
| } |
| |
| return new LogSizeStats( |
| activeSize, reservedSize, protectedSize, protectedSizeMap); |
| } |
| |
| /** |
| * Sets the ProtectedFileRange that protects files in VLSNIndex range |
| * from being deleted. The range start is changed during VLSNIndex |
| * initialization and when the head of the index is truncated. It is |
| * changed while synchronized on VLSNTracker so that changes to the |
| * range and changes to the files it protects are made atomically. This |
| * is important for |
| * {@link com.sleepycat.je.rep.vlsn.VLSNTracker#protectRangeHead}. |
| */ |
| public void setVLSNIndexProtectedFileRange(ProtectedFileRange pfs) { |
| vlsnIndexRange = pfs; |
| } |
| |
| /** |
| * Determines whether the VLSNIndex ProtectedFileRange should be advanced |
| * to reclaim bytesNeeded. This is possible if one or more reserved files |
| * are not protected by syncup and feeders. The range of files to be |
| * truncated must be at the head of the ordered set of reserved files, and |
| * the highest numbered file must contain a VLSN so we know where to |
| * truncate the VLSNIndex. |
| * |
| * @param bytesNeeded the number of bytes we need to free. |
| * |
| * @param preserveVLSN is the boundary above which the VLSN range may not |
| * advance. The deleteEnd returned will be less than preserveVLSN. |
| * |
| * @return {deleteEnd, deleteFileNum} pair if the protected file range |
| * should be advanced, or null if advancing is not currently possible. |
| * -- deleteEnd is the last VLSN to be truncated. |
| * -- deleteFileNum the file having deleteEnd as its last VLSN. |
| */ |
| public synchronized Pair<VLSN, Long> checkVLSNIndexTruncation( |
| final long bytesNeeded, |
| final VLSN preserveVLSN) { |
| |
| /* |
| * Determine how many reserved files we need to delete, and find the |
| * last file/VLSN in that set of files, which is the truncation point. |
| */ |
| VLSN truncateVLSN = VLSN.NULL_VLSN; |
| long truncateFile = -1; |
| long deleteBytes = 0; |
| |
| for (final Map.Entry<Long, ReservedFileInfo> entry : |
| reservedFiles.entrySet()) { |
| |
| final Long file = entry.getKey(); |
| final ReservedFileInfo info = entry.getValue(); |
| |
| if (isVLSNIndexProtected(file, info)) { |
| break; |
| } |
| |
| final VLSN lastVlsn = info.endVLSN; |
| |
| if (!lastVlsn.isNull()) { |
| if (lastVlsn.compareTo(preserveVLSN) > 0) { |
| break; |
| } |
| truncateVLSN = lastVlsn; |
| truncateFile = file; |
| } |
| |
| deleteBytes += info.size; |
| |
| if (deleteBytes >= bytesNeeded) { |
| break; |
| } |
| } |
| |
| return truncateVLSN.isNull() ? null : |
| new Pair<>(truncateVLSN, truncateFile); |
| } |
| |
| /** |
| * Determines whether the VLSNIndex ProtectedFileRange should be advanced |
| * to unprotect the given file. This is possible if the file is not |
| * protected by syncup and feeders. The given file is assumed to contain |
| * deleteEnd VLSN, i.e., it is not a barren file. |
| * |
| * @param file that should be unprotected. |
| * |
| * @return whether the protected file range should be advanced. |
| */ |
| public boolean checkVLSNIndexTruncation(final Long file) { |
| return !isVLSNIndexProtected(file, null); |
| } |
| |
| /** |
| * Returns whether the VLSNIndex is protected for the given file. Because |
| * the VLSNIndex is always protected for all files in a range at the tail |
| * of the log, returning false implies that the VLSNIndex is unprotected |
| * for all files less than or equal to the given file. |
| */ |
| private synchronized boolean isVLSNIndexProtected( |
| final Long file, |
| final ReservedFileInfo info) { |
| |
| for (final ProtectedFileSet pfs : protectedFileSets.values()) { |
| |
| if (pfs == vlsnIndexRange || !pfs.protectVlsnIndex) { |
| continue; |
| } |
| |
| if (pfs.isProtected(file, info)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Returns the first file covered by the VLSNIndex, or DbLsn.MAX_FILE_NUM |
| * in a non-replicated environment. |
| */ |
| long getVLSNIndexStartFile() { |
| return (vlsnIndexRange != null) ? |
| vlsnIndexRange.getRangeStart() : DbLsn.MAX_FILE_NUM; |
| } |
| |
| /** |
| * A ProtectedFileSet is used to prevent a set of files from being deleted. |
| * Implementations must meet two special requirements: |
| * |
| * 1. After a ProtectedFileSet is added using {@link #addFileProtection}, |
| * its set of protected files (the set for which {@link #isProtected} |
| * returns true) may only be changed by shrinking the set. Files may not be |
| * added to the set of protected files. (One exception is that newly |
| * created files are effectively to a file set defined as an opened ended |
| * range.) |
| * |
| * 2. Shrinking the protected set can be done without synchronization on |
| * FileProtector. However, implementations should ensure that changes made |
| * in one thread are visible to all threads. |
| * |
| * The intention is to allow protecting a set of files that are to be |
| * processed in some way, and allow easily shrinking this set as the files |
| * are processed, so that the processed files may be deleted. Changes to |
| * the protected set should be visible to all threads so that periodic disk |
| * space reclamation tasks can delete unprotected files ASAP. {@link |
| * ProtectedFileRange} is a simple class that meets these requirements. |
| */ |
| public static abstract class ProtectedFileSet { |
| |
| private final String name; |
| private final boolean protectVlsnIndex; |
| |
| private ProtectedFileSet(final String name, |
| final boolean protectVlsnIndex) { |
| this.name = name; |
| this.protectVlsnIndex = protectVlsnIndex; |
| } |
| |
| /** |
| * Identifies protecting entity, used in LogSizeStats. Must be unique |
| * across all file sets added to the FileProtector. |
| */ |
| private String getName() { |
| return name; |
| } |
| |
| /** |
| * Whether the given file is protected. |
| * |
| * @param info is null for non-reserved files. |
| */ |
| abstract boolean isProtected(Long file, |
| @Nullable ReservedFileInfo info); |
| |
| @Override |
| public String toString() { |
| return "ProtectedFileSet:" + name; |
| } |
| } |
| |
| /** |
| * A ProtectedFileSet created using {@link #protectFileRange}. |
| * |
| * Protection may be removed dynamically to allow file deletion using |
| * {@link #advanceRange}. The current lower bound can be obtained using |
| * {@link #getRangeStart()}. |
| */ |
| public static class ProtectedFileRange extends ProtectedFileSet { |
| |
| private volatile long rangeStart; |
| private final boolean protectBarrenFiles; |
| |
| ProtectedFileRange( |
| final String name, |
| final long rangeStart, |
| final boolean protectVlsnIndex, |
| final boolean protectBarrenFiles) { |
| |
| super(name, protectVlsnIndex); |
| this.rangeStart = rangeStart; |
| this.protectBarrenFiles = protectBarrenFiles; |
| } |
| |
| @Override |
| boolean isProtected(final Long file, |
| final ReservedFileInfo info) { |
| |
| return file >= rangeStart && |
| (protectBarrenFiles || |
| info == null || |
| !info.endVLSN.isNull()); |
| } |
| |
| /** |
| * Returns the current rangeStart. This method is not synchronized and |
| * rangeStart is volatile to allow checking this value without |
| * blocking. |
| */ |
| public long getRangeStart() { |
| return rangeStart; |
| } |
| |
| /** |
| * Moves the lower bound of the protected file range forward. Used to |
| * allow file deletion as protected files are processed. |
| */ |
| public synchronized void advanceRange(final long rangeStart) { |
| |
| if (rangeStart < this.rangeStart) { |
| throw EnvironmentFailureException.unexpectedState( |
| "Attempted to advance to a new rangeStart=0x" + |
| Long.toHexString(rangeStart) + |
| " that precedes the old rangeStart=0x" + |
| Long.toHexString(this.rangeStart)); |
| } |
| |
| this.rangeStart = rangeStart; |
| } |
| } |
| |
| /** |
| * A ProtectedFileSet created using {@link #protectActiveFiles}. |
| * |
| * Protection may be removed dynamically to allow file deletion using |
| * {@link #truncateHead(long)}, {@link #truncateTail(long)} and |
| * {@link #removeFile(Long)}. A copy of the currently protected files can |
| * be obtained using {@link #getProtectedFiles()}. |
| */ |
| public static class ProtectedActiveFileSet extends ProtectedFileSet { |
| |
| private NavigableSet<Long> protectedFiles; |
| private Long rangeStart; |
| |
| ProtectedActiveFileSet( |
| final String name, |
| final NavigableSet<Long> protectedFiles, |
| final Long rangeStart) { |
| |
| super(name, false /*protectVlsnIndex*/); |
| this.protectedFiles = protectedFiles; |
| this.rangeStart = rangeStart; |
| } |
| |
| @Override |
| synchronized boolean isProtected(final Long file, |
| final ReservedFileInfo info) { |
| |
| return (rangeStart != null && file >= rangeStart) || |
| protectedFiles.contains(file); |
| } |
| |
| /** |
| * Returns a copy of the currently protected files, not including any |
| * new files. |
| */ |
| public synchronized NavigableSet<Long> getProtectedFiles() { |
| return new TreeSet<>(protectedFiles); |
| } |
| |
| /** |
| * Removes protection for files GT lastProtectedFile. Protection of |
| * new files is not impacted. |
| */ |
| public synchronized void truncateTail(long lastProtectedFile) { |
| protectedFiles = protectedFiles.headSet(lastProtectedFile, true); |
| } |
| |
| /** |
| * Removes protection for files LT firstProtectedFile. Protection of |
| * new files is not impacted. |
| */ |
| public synchronized void truncateHead(long firstProtectedFile) { |
| protectedFiles = protectedFiles.tailSet(firstProtectedFile, true); |
| } |
| |
| /** |
| * Removes protection for a given file. |
| */ |
| public synchronized void removeFile(final Long file) { |
| |
| protectedFiles.remove(file); |
| |
| /* |
| * This only works if protected files are removed in sequence, but |
| * that's good enough -- new files will rarely need to be deleted. |
| */ |
| if (file.equals(rangeStart)) { |
| rangeStart += 1; |
| } |
| } |
| } |
| |
| /** |
| * For debugging. |
| */ |
| @SuppressWarnings("unused") |
| synchronized void verifyFileSizes() { |
| final FileManager fm = envImpl.getFileManager(); |
| final Long[] numArray = fm.getAllFileNumbers(); |
| final NavigableMap<Long, Long> activeFiles = getActiveFiles(); |
| for (int i = 0; i < numArray.length - 1; i++) { |
| final Long n = numArray[i]; |
| final long trueSize = new File(fm.getFullFileName(n)).length(); |
| if (activeFiles.containsKey(n)) { |
| final long activeSize = activeFiles.get(n); |
| if (activeSize != trueSize) { |
| System.out.format( |
| "active file %,d size %,d but true size %,d %n", |
| n, activeSize, trueSize); |
| } |
| } else if (reservedFiles.containsKey(n)) { |
| final long reservedSize = reservedFiles.get(n).size; |
| if (reservedSize != trueSize) { |
| System.out.format( |
| "reserved file %,d size %,d but true size %,d %n", |
| n, reservedSize, trueSize); |
| } |
| } else { |
| System.out.format( |
| "true file %x size %,d missing in FileProtector%n", |
| n, trueSize); |
| } |
| } |
| } |
| } |