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

org.jgroups.raft.filelog.FileStorage Maven / Gradle / Ivy

There is a newer version: 1.0.13.Final
Show newest version
package org.jgroups.raft.filelog;

import org.jgroups.logging.Log;
import org.jgroups.logging.LogFactory;
import org.jgroups.raft.util.pmem.FileProvider;
import org.jgroups.util.Util;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.util.Objects;

/**
 * Base class to store data in a file.
 *
 * @author Pedro Ruivo
 * @since 0.5.4
 */
public final class FileStorage implements Closeable {

   private enum Flush {
      Metadata, Data, None
   }

   private static final Log LOG = LogFactory.getLog(FileStorage.class);

   private final boolean isWindowsOS;
   private final File storageFile;
   private FileChannel channel;
   private RandomAccessFile raf;
   private long fileSize;
   private final int writeAheadBytes;
   private Flush requiredFlush;
   private ByteBuffer ioBuffer;
   private boolean ioBufferReady;

   public FileStorage(final File storageFile) {
      this(storageFile, 0);
      ioBufferReady = false;
   }

   public FileStorage(final File storageFile, final int writeAheadBytes) {
      if (writeAheadBytes < 0) {
         throw new IllegalArgumentException("writeAheadBytes must be greater than or equals to 0");
      }
      this.storageFile = Objects.requireNonNull(storageFile);
      this.writeAheadBytes = writeAheadBytes;
      this.fileSize = -1;
      this.requiredFlush = Flush.None;
      this.isWindowsOS = Util.checkForWindows();
   }

   public ByteBuffer ioBufferWith(final int requiredCapacity) {
      final ByteBuffer availableWriteBuffer = this.ioBuffer;
      if (availableWriteBuffer != null && availableWriteBuffer.capacity() >= requiredCapacity) {
         availableWriteBuffer.position(0).limit(requiredCapacity);
         ioBufferReady = true;
         return availableWriteBuffer;
      }
      final ByteBuffer newBuffer = ByteBuffer.allocateDirect(requiredCapacity);
      this.ioBuffer = newBuffer;
      ioBufferReady = true;
      return newBuffer;
   }

   public long getCachedFileSize() {
      if (channel == null) {
         throw new IllegalStateException("fileSize cannot be retrieved if closed");
      }
      return fileSize;
   }

   public File getStorageFile() {
      return storageFile;
   }

   public void open() throws IOException {
      if (channel == null) {
         final FileChannel pmemChannel = FileProvider.openPMEMChannel(storageFile, 1024, true, true);
         if (channel == null) {
            final RandomAccessFile raf = new RandomAccessFile(storageFile, "rw");
            this.channel = raf.getChannel();
            this.raf = raf;
         } else {
            this.channel = pmemChannel;
            this.raf = null;
         }
         fileSize = channel.size();
      }
   }

   public int write(final long position) throws IOException {
      if (!ioBufferReady) {
         throw new IllegalStateException("must prepare the IO buffer first!");
      }
      final FileChannel channel = checkOpen();
      final int dataLength = ioBuffer.remaining();
      final long nextPosition = position + dataLength;
      final boolean growFile = nextPosition > fileSize;
      if (growFile) {
         if (writeAheadBytes > 0 && dataLength < writeAheadBytes) {
            final long writeAheadSize = nextPosition + writeAheadBytes;
            raf.setLength(writeAheadSize);
            fileSize = writeAheadSize;
         } else {
            fileSize = nextPosition;
         }
         requiredFlush = Flush.Metadata;
      }
      try {
         final int written = channel.write(ioBuffer, position);
         if (written < dataLength) {
            if (!growFile) {
               fileSize = position + written;
            }
         }
         if (written > 0 && requiredFlush == Flush.None) {
            requiredFlush = Flush.Data;
         }
         return written;
      } finally {
         ioBufferReady = false;
      }
   }

   public ByteBuffer read(final long position, final int expectedLength) throws IOException {
      final FileChannel channel = checkOpen();
         final ByteBuffer readBuffer = ioBufferWith(expectedLength);
         assert ioBufferReady;
      try {
         channel.read(readBuffer, position);
         return readBuffer.flip();
      } finally {
         ioBufferReady = false;
      }
   }

   public boolean isOpened() {
      return channel != null && channel.isOpen();
   }

   private FileChannel checkOpen() {
      final FileChannel channel = this.channel;
      if (channel == null || !channel.isOpen()) {
         throw new IllegalStateException("File " + storageFile.getAbsolutePath() + " not open!");
      }
      return channel;
   }

   public void flush() throws IOException {
      checkOpen();
      if (requiredFlush == Flush.None) {
         return;
      }
      try {
         switch (requiredFlush) {

            case Metadata:
               channel.force(true);
               break;
            case Data:
               channel.force(false);
               break;
         }
      } finally {
         requiredFlush = Flush.None;
      }
   }

   public void truncateTo(final long size) throws IOException {
      final FileChannel existing = checkOpen();
      existing.truncate(size);
      fileSize = size;
      requiredFlush = Flush.Metadata;
   }

   public void truncateFrom(final long position) throws IOException {
      final FileChannel existing = checkOpen();
      if (position > fileSize) {
         throw new IllegalArgumentException("position must be greater then fileSize");
      }
      final File tmpFile = new File(storageFile.getParentFile(), storageFile.getName() + ".tmp");
      try (FileChannel newChannel = tryCreatePMEMFileChannel(tmpFile, fileSize)) {
         existing.transferTo(position, fileSize, newChannel);
      }
      // TODO: it requires fsync on the parent folder and on the destination file?
      try {
         // If running in Windows, first release other file holder before overwriting.
         if (isWindowsOS)
            close();
         Files.move(tmpFile.toPath(), storageFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
      } catch (IOException failedMove) {
         try {
            Files.deleteIfExists(tmpFile.toPath());
         } catch (IOException deleteTmpEx) {
            failedMove.addSuppressed(deleteTmpEx);
            throw failedMove;
         }
         throw failedMove;
      }
      channel = null;
      raf = null;
      fileSize = -1;
      existing.close();
      open();
      requiredFlush = Flush.Metadata;
   }

   @Override
   public void close() throws IOException {
      if (channel == null) {
         return;
      }
      IOException suppressed = null;
      try {
         if (raf != null) {
            raf.close();
         }
      } catch (IOException rafEx) {
         suppressed = rafEx;
      } finally {
         try {
            channel.close();
         } catch (IOException chEx) {
            if (suppressed != null) {
               chEx.addSuppressed(suppressed);
               throw chEx;
            }
         } finally {
            requiredFlush = Flush.None;
            raf = null;
            channel = null;
            fileSize = -1;
         }
      }
   }

   public void delete() throws IOException {
      if (storageFile.exists()) {
         if (!storageFile.delete()) {
            LOG.warn("Failed to delete file " + storageFile.getAbsolutePath());
         }
         close();
      }
   }

   private static FileChannel tryCreatePMEMFileChannel(File tmpFile, long fileSize) throws IOException {
      if (FileProvider.isPMEMAvailable()) {
         final FileChannel pmemChannel = FileProvider.openPMEMChannel(tmpFile, (int) fileSize, true, true);
         if (pmemChannel != null) {
            return pmemChannel;
         }
      }
      return FileChannel.open(tmpFile.toPath(), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
   }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy