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

com.caucho.db.io.StoreReadWriteMmapNio Maven / Gradle / Ivy

/*
 * Copyright (c) 1998-2015 Caucho Technology -- all rights reserved
 *
 * This file is part of Baratine(TM)
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Baratine is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Baratine is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Baratine; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.db.io;

import java.io.IOException;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;

import com.caucho.util.ConcurrentArrayList;
import com.caucho.util.FreeList;
import com.caucho.util.L10N;
import com.caucho.vfs.Path;

/**
 * Filesystem access for a random-access store.
 * 
 * The store is designed around a single writer thread and multiple
 * reader threads. When possible, it uses mmap.
 */
public class StoreReadWriteMmapNio implements StoreReadWrite
{
  private final static L10N L = new L10N(StoreReadWriteMmapNio.class);
  
  // private final static long FILE_SIZE_INCREMENT = 32L * 1024 * 1024; 
  // private final static long FILE_SIZE_INCREMENT = 64 * 1024; 

  private final Path _path;

  private long _fileSize;
  
  private FileChannel _channel;
  
  private long _mmapChunkSize;
  
  private final ConcurrentArrayList _mmapFiles
    = new ConcurrentArrayList(MmapFile.class);
  
  private MmapFile []_mmapFileChunks = new MmapFile[0];
  
  private long _mmapCloseTimeout = 1000L;
  
  private final AtomicBoolean _isClosed = new AtomicBoolean();
  // private ServiceManagerAmp _rampManager;

  // private StoreFsyncService _fsyncService;
  
  private FreeList _freeInStore
    = new FreeList(16);
  private FreeList _freeOutStore
    = new FreeList(16);
  private AtomicLong _storeSequence = new AtomicLong();

  /**
   * Creates a new store.
   *
   * @param database the owning database.
   * @param name the store name
   * @param lock the table lock
   * @param path the path to the files
   */
  StoreReadWriteMmapNio(StoreBuilder builder)
  {
    _path = builder.getPath();
    
    //_rampManager = builder.getRampManager();

    if (_path == null) {
      throw new NullPointerException();
    }
    //Objects.requireNonNull(_rampManager);
    
    /*
    FsyncImpl fsyncImpl = new FsyncImpl();
    StoreFsyncServiceImpl fsyncServiceImpl
      = new StoreFsyncServiceImpl(fsyncImpl);
    
    _fsyncService = getRampManager().service(fsyncServiceImpl)
                                    .as(StoreFsyncService.class);
                                    */
  }
  
  /*
  ServiceManagerAmp getRampManager()
  {
    return _rampManager;
  }
  */

  /**
   * Returns the file size.
   */
  @Override
  public long getFileSize()
  {
    return _fileSize;
  }
  
  private void setFileSize(long size)
  {
    _fileSize = Math.max(_fileSize, size);
  }
  
  @Override
  public long getChunkSize()
  {
    return FILE_SIZE_INCREMENT;
  }
  
  public long getMmapCloseTimeout()
  {
    return _mmapCloseTimeout;
  }

  boolean isFileExist()
  {
    return _path.exists();
  }

  /**
   * Creates the store.
   */
  @Override
  public void create()
    throws IOException
  {
    _path.getParent().mkdirs();

    if (_path.exists()) {
      throw new IOException(L.l("CREATE for path '{0}' failed, because the file already exists.  CREATE can not override an existing table.",
                                _path.getNativePath()));
    }
    
    _channel = _path.fileChannelFactory()
                    .openFileChannel(StandardOpenOption.CREATE, 
                                     StandardOpenOption.WRITE,
                                     StandardOpenOption.READ);
    
    /*
    try (WriteStream os = _path.openWrite()) {
    }
    
    */
    setFileSize(_path.getLength());
    
    initImpl();
  }

  @Override
  public void init()
    throws IOException
  {
    if (_channel == null) {
      _channel = _path.fileChannelFactory()
                      .openFileChannel(StandardOpenOption.CREATE,
                                       StandardOpenOption.WRITE,
                                       StandardOpenOption.READ);
    }

    setFileSize(_path.getLength());

    /*
    try (StoreWrite os = openWrite(0, FILE_SIZE_INCREMENT)) {
      setFileSize(os.getLength());
    }
    */
    
    initImpl();
  }
  
  private void initImpl()
    throws IOException
  {
    long fileSize = getFileSize();
    
    long highBit = Long.highestOneBit(fileSize);
    
    int chunkSize = (int) Math.min(highBit >> 3, 0x10000000);
    chunkSize = Math.max(chunkSize, FILE_SIZE_INCREMENT);
    
    _mmapChunkSize = chunkSize;
    
    long offset = 0;
    
    while (offset < fileSize) {
      // long size = Math.min(_mmapChunkSize, fileSize - offset);

      streamOpen(offset, (int) chunkSize);

      offset += chunkSize;
    }
  }

  /**
   * Opens the underlying file to the database.
   */
  @Override
  public InStore openRead(long address, int size)
  {
    if (getFileSize() < address + size) {
      throw new IllegalStateException(L.l("{0} read open for length {1}:{2} but file length {3}",
                                this, address, size, getFileSize()));
    }

    if (_isClosed.get()) {
      throw new IllegalStateException(L.l("{0} is closed.", this));
    }
    
    if (_fileSize < address + size) {
      throw new IllegalStateException(L.l("Open read of large file {0}:{1}",
                                          Long.toHexString(address), size));
    }
    
    try {
      streamOpen(address, size);
      
      return openReadImpl(address, size);
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * Opens the underlying file to the database.
   */
  @Override
  public OutStore openWrite(long address, int size)
  {
    if (_isClosed.get()) {
      throw new IllegalStateException(L.l("{0} is closed.", this));
    }
    
    if (size <= 0) {
      throw new IllegalArgumentException(L.l("Invalid size: {0}", size));
    }
    
    if (address < 0) {
      throw new IllegalArgumentException(L.l("Invalid address: {0}", 
                                             Long.toHexString(address)));
    }

    try {
      streamOpen(address, size);
      
      return openWriteImpl(address, size);
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  private InStore openReadImpl(long address, int size)
  {
    return new InStoreFacade(address, size);
  }
  
  private InStoreImpl allocateRead()
  {
    InStoreImpl inStore = _freeInStore.allocate();
    
    if (inStore != null && inStore.getSequence() == _storeSequence.get()) {
      return inStore;
    }
    
    return new InStoreImpl();
  }

  private OutStore openWriteImpl(long address, int size)
  {
    return new OutStoreFacade(address, size);
  }
  
  private OutStoreMmapNio allocateWrite()
  {
    OutStoreMmapNio outStore = _freeOutStore.allocate();
    
    if (outStore != null && outStore.getSequence() == _storeSequence.get()) {
      return outStore;
    }
    
    return new OutStoreMmapNio();
  }
  
  private MmapFile streamOpen(long address, int size)
    throws IOException
  {
    if (_isClosed.get()) {
      throw new IllegalStateException();
    }

    int chunkIndex = (int) ((address + size - 1) / _mmapChunkSize);
    
    if (chunkIndex < 0) {
      throw new IllegalStateException(L.l("Invalid stream address: 0x{0} size: 0x{1} chunk: {2}",
                                          Long.toHexString(address),
                                          Long.toHexString(size),
                                          chunkIndex));
    }
    
    synchronized (_mmapFiles) {
      if (chunkIndex < _mmapFileChunks.length) {
        MmapFile mmapFile = _mmapFileChunks[chunkIndex];
        
        /*
        if (address < mmapFile.getAddress()
            || mmapFile.getAddress() + mmapFile.getSize() < address + size) {
          throw new IllegalStateException(L.l("Invalid mmap chunk. Requested <0x{0},0x{1}>. Received <0x{2},0x{3}>",
                                              Long.toHexString(address), Long.toHexString(size),
                                              Long.toHexString(mmapFile.getAddress()), 
                                              Long.toHexString(mmapFile.getSize())));
        }
        */
        
        return mmapFile;
      }
      
      long reqSize = Math.max(address + size, _path.getLength());
      long fileSize = extendFileSize(_fileSize, reqSize);
      
      setFileSize(fileSize);
      
      if (fileSize % _mmapChunkSize != 0) {
        throw new IllegalStateException(L.l("file size 0x{0} must be an increment of the chunk size 0x{1}",
                                            Long.toHexString(fileSize),
                                            Long.toHexString(_mmapChunkSize)));
      }
      
      long tailAddress = _mmapFileChunks.length * _mmapChunkSize;
      
      MmapFile mmapFile = null;

      while (tailAddress < fileSize) {
        long delta = Math.min(fileSize - tailAddress, Integer.MAX_VALUE / 2);
        
        delta -= delta % _mmapChunkSize;
      
        mmapFile = new MmapFile(_channel, tailAddress, (int) delta);
      
        appendMmapFile(mmapFile);
        
        tailAddress += delta;
      }
      
      _storeSequence.incrementAndGet();
      
      if (mmapFile.getAddress() + mmapFile.getSize() < address + size) {
        throw new IllegalStateException(L.l("Invalid mmap chunk. Requested <0x{0},0x{1}>. Received <0x{2},0x{3}>",
                                            Long.toHexString(address),
                                            Long.toHexString(size),
                                            Long.toHexString(mmapFile.getAddress()), 
                                            Long.toHexString(mmapFile.getSize())));
      }

      return mmapFile;
    }
  }
  
  private void appendMmapFile(MmapFile mmapFile)
  {
    _mmapFiles.add(mmapFile);
    
    long tail = mmapFile.getAddress() + mmapFile.getSize();
    int index = (int) (mmapFile.getAddress() / _mmapChunkSize);
    
    if (index != _mmapFileChunks.length) {
      throw new IllegalStateException();
    }
    
    MmapFile []newMmapChunks;
    newMmapChunks = new MmapFile[(int) (tail / _mmapChunkSize)];
    
    System.arraycopy(_mmapFileChunks, 0, newMmapChunks, 0, index);
    
    for (int i = index; i < newMmapChunks.length; i++) {
      newMmapChunks[i] = mmapFile;
    }
   
    _mmapFileChunks = newMmapChunks;
    
    for (int i = 1; i < newMmapChunks.length; i++) {
      MmapFile ptr = newMmapChunks[i];
      MmapFile prev = newMmapChunks[i - 1];

      if (ptr == prev) {
        continue;
      }
      else if (prev.getAddress() + prev.getSize() != ptr.getAddress()) {
        throw new IllegalStateException();
      }
    }
  }
  
  private long extendFileSize(long oldFileSize, long reqFileSize)
  {
    long newFileSize;
    
    if (reqFileSize <= oldFileSize) {
      newFileSize = oldFileSize;
    }
    else {
      newFileSize = 5 * oldFileSize / 4 + FILE_SIZE_INCREMENT;
    }
    
    long index = Long.highestOneBit(newFileSize);
    long mask = ~(index - 1) >> 3;

    newFileSize = Math.max(newFileSize & mask, reqFileSize);
    
    long chunkSize = _mmapChunkSize;
    
    long mod = newFileSize % chunkSize;
    newFileSize -= mod;
    
    if (newFileSize < reqFileSize) {
      newFileSize += chunkSize;
    }
    
    return newFileSize;
  }
  
  @Override
  public void fsync()
  {
    /*
    long sequence = _fsyncService.allocateFsyncSequence();
          
    _fsyncService.fsync(sequence, null, Result.ignore());
    */
    
    for (MmapFile mmapFile : _mmapFiles) {
      mmapFile.fsyncImpl();
    }
  }

  @Override
  public void close()
  {
    if (_isClosed.getAndSet(true)) {
      return;
    }
    
    fsync();
    
    FileChannel channel = _channel;
    _channel = null;
    
    if (channel != null) {
      try {
        channel.close();
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }

  @Override
  public String toString()
  {
    return getClass().getSimpleName() + "[" + _path + "]";
  }
  
  /*
  private class FsyncImpl implements StoreFsync {
    @Override
    public void addResult(MmapFile mmapFile, Result result)
    {
      if (mmapFile != null) {
        mmapFile.addResult(result);
      }
      else if (_mmapFiles.size() > 0) {
        _mmapFiles.get(_mmapFiles.size() - 1).addResult(result);
      }
      else {
        result.complete(Boolean.TRUE);
      }
    }
    
    
    @Override
    public void fsync()
    {
      for (MmapFile mmapFile : _mmapFiles) {
        mmapFile.fsyncImpl();
      }
    }
  }
  */
  
  private class MmapFile
  {
    private MappedByteBuffer _mmap;
    
    private final long _address;
    private final int _size;
    
    //private final ArrayList> _resultList = new ArrayList<>();
    private final AtomicBoolean _isDirty = new AtomicBoolean();

    // private final StoreFsyncService _fsyncService;

    public MmapFile(FileChannel channel, long address, int size)
      throws IOException
    {
      _mmap = channel.map(MapMode.READ_WRITE, address, size);
      
      _address = address;
      _size = size;
    }

    /*
    public void addResult(Result result)
    {
      _resultList.add(result);
    }
    */

    public long getAddress()
    {
      return _address;
    }
    
    public long getSize()
    {
      return _size;
    }
    
    MappedByteBuffer getByteBuffer()
    {
      return _mmap;
    }

    public void setDirty()
    {
      _isDirty.set(true);
    }

    /*
    public void fsyncImpl()
    {
      MappedByteBuffer mmap = _mmap;

      try {
        if (mmap != null && _isDirty.compareAndSet(true, false)) {
          ArrayList> resultList = new ArrayList<>(_resultList);
          _resultList.clear();
          
          mmap.force();
          
          for (Result result : resultList) {
            try {
              result.complete(Boolean.TRUE);
            } catch (Throwable e) {
              e.printStackTrace();
            }
          }
        }
        else {
          ArrayList> resultList = new ArrayList<>(_resultList);
          _resultList.clear();
          
          for (Result result : resultList) {
            try {
              result.complete(Boolean.TRUE);
            } catch (Throwable e) {
              e.printStackTrace();
            }
          }
        }
      } catch (Throwable e) {
        e.printStackTrace();
      }
    }
    */
    
    public void fsyncImpl()
    {
      MappedByteBuffer mmap = _mmap;

      try {
        if (mmap != null) {
          mmap.force();
        }
      } catch (Throwable e) {
        e.printStackTrace();
      }
    }

    public void close()
    {
      // fsyncImpl();
      
      MappedByteBuffer mmap = _mmap;
      _mmap = null;

      try {
        if (mmap != null) {
          mmap.force();
        }
      } catch (Throwable e) {
        e.printStackTrace();
      }
      
      try {
        _channel.force(true);
      } catch (Throwable e) {
        e.printStackTrace();
      }
    }
    
    @Override
    public String toString()
    {
      return getClass().getSimpleName() + "[" + Long.toHexString(_fileSize) + "]";
    }
  }
  
  class InStoreFacade implements InStore
  {
    private InStoreImpl _delegate;
    private long _address;
    private int _size;
    
    InStoreFacade(long address, int size)
    {
      _delegate = allocateRead();
      
      _address = address;
      _size = size;
    }

    @Override
    public boolean read(long address, byte[] buffer, int offset, int length)
    {
      if (address < _address) {
        throw new IllegalStateException();
      }
      
      if (_address + _size < offset + length) {
        throw new IllegalStateException();
      }
      
      return _delegate.read(address, buffer, offset, length);
    }

    @Override
    public InStore clone()
    {
      return new InStoreFacade(_address, _size);
    }

    @Override
    public void close()
    {
      InStoreImpl delegate = _delegate;
      _delegate = null;
      
      if (delegate != null && delegate.getSequence() == _storeSequence.get()) {
        _freeInStore.free(delegate);
      }
    }
    
  }

  class InStoreImpl implements InStore
  {
    private final MmapFile []_mmapFile;
    
    private final ByteBuffer []_mmap;
    
    private final long _sequence;

    InStoreImpl()
    {
      _sequence = _storeSequence.get();
      _mmapFile = _mmapFileChunks;
      
      int indexEnd = (int) ((getFileSize() - 1) / _mmapChunkSize);
      
      _mmap = new ByteBuffer[indexEnd + 1];
      
      ByteBuffer lastBuffer = null;
      for (int i = 0; i < _mmap.length; i++) {
        MmapFile mmapFile = _mmapFile[i];
        
        if (lastBuffer != mmapFile.getByteBuffer()) {
          _mmap[i] = mmapFile.getByteBuffer().duplicate();
          
          lastBuffer = mmapFile.getByteBuffer();
        }
        else {
          _mmap[i] = _mmap[i - 1];
        }
      }
    }
    
    public long getSequence()
    {
      return _sequence;
    }

    @Override
    public boolean read(long address, byte[] buffer, int offset, int length)
    {
      while (length > 0) {
        int mmapIndex = (int) (address / _mmapChunkSize);
        
        ByteBuffer mmap = _mmap[mmapIndex];
     
        synchronized (mmap) {
          MmapFile mmapFile = _mmapFile[mmapIndex];
          int mmapOffset = (int) (address - mmapFile.getAddress());
        
          int sublen = (int) Math.min(length, mmapFile.getSize() - mmapOffset);

          mmap.limit(mmapOffset + sublen);
          mmap.position(mmapOffset);
      
          mmap.get(buffer, offset, sublen);
        
          offset += sublen;
          length -= sublen;
          address += sublen;
        }
      }
      
      return true;
    }
    
    @Override
    public InStore clone()
    {
      throw new IllegalStateException();
    }

    @Override
    public void close()
    {
      throw new IllegalStateException();
    }
    
    public String toString()
    {
      return getClass().getSimpleName() + "[" + _path + "]";
    }
  }
  
  class OutStoreFacade implements OutStore
  {
    private OutStoreMmapNio _outStore;
    private long _address;
    private int _size;
    
    OutStoreFacade(long address, int size)
    {
      _outStore = allocateWrite();
      _address = address;
      _size = size;
    }
    
    @Override
    public long getLength()
    {
      return _fileSize;
    }

    @Override
    public boolean write(long address, byte[] buffer, int offset, int length)
    {
      if (address < _address) {
        throw new IllegalStateException(L.l("Address 0x{0} less than {1} offset 0x{2}",
                                            Long.toHexString(address),
                                            OutStoreMmapNio.class.getSimpleName(),
                                            Long.toHexString(_address)));
      }
      
      if (_address + _size < address + length) {
        throw new IllegalStateException(L.l("Tail 0x{0} greater than mmap tail 0x{1}",
                                            Long.toHexString(address + length),
                                            Long.toHexString(_address + _size)));
      }
      

      return _outStore.write(address, buffer, offset, length);
    }

    @Override
    public OutStore clone()
    {
      return new OutStoreFacade(_address, _size); 
    }

    @Override
    public void close()
    {
      OutStoreMmapNio outStore = _outStore;
      _outStore = null;
      
      if (outStore != null && outStore.getSequence() == _storeSequence.get()) {
        _freeOutStore.free(outStore);
      }
    }
    
  }

  class OutStoreMmapNio implements OutStore
  {
    private final MmapFile []_mmapFile;
    private final ByteBuffer []_mmap;
    
    private final ArrayList _mmapFileList
      = new ArrayList();
    
    private final long _sequence;
    //private final AtomicReferenceArray _mmapDirty;

    OutStoreMmapNio()
    {
      _sequence = _storeSequence.get();

      synchronized (_mmapFiles) {
        _mmapFile = _mmapFileChunks;
      
        int indexEnd = (int) ((getFileSize() - 1) / _mmapChunkSize);
      
        _mmap = new ByteBuffer[indexEnd + 1];
      
        ByteBuffer lastBuffer = null;
      
        for (int i = 0; i < _mmap.length; i++) {
          long address = i * _mmapChunkSize;

          for (int j = 0; j < _mmapFile.length; j++) {
            MmapFile mmapFile = _mmapFile[j];

            if (mmapFile.getAddress() <= address 
                && address < mmapFile.getAddress() + mmapFile.getSize()) {
              if (lastBuffer == mmapFile.getByteBuffer()) {
                _mmap[i] = _mmap[i - 1];
              }
              else {
                _mmapFileList.add(mmapFile);
                _mmap[i] = mmapFile.getByteBuffer().duplicate();
              }

              lastBuffer = mmapFile.getByteBuffer();

              break;
            }
          }

          if (_mmap[i] == null) {
            throw new IllegalStateException(L.l("Invalid initialization address=0x{0}", Long.toHexString(address)));
          }
        }
      }
    }
    
    public long getSequence()
    {
      return _sequence;
    }

    @Override
    public long getLength()
    {
      return _fileSize;
    }

    @Override
    public boolean write(long address, byte[] buffer, int offset, int length)
    {
      while (length > 0) {
        int mmapIndex = (int) (address / _mmapChunkSize);
        
        ByteBuffer mmap = _mmap[mmapIndex];
        Buffer mmapBuf = mmap;
        
        synchronized (mmap) {
          MmapFile mmapFile = _mmapFile[mmapIndex];
          int mmapOffset = (int) (address - mmapFile.getAddress());
          
          int sublen = (int) Math.min(length, mmapFile.getSize() - mmapOffset);

          mmapBuf.limit(mmapOffset + sublen);
          mmapBuf.position(mmapOffset);

          mmap.put(buffer, offset, sublen);
        
          mmapFile.setDirty();
        
          length -= sublen;
          offset += sublen;
          address += sublen;
        }
      }

      return true;
    }

    /*
    @Override
    public void fsync()
    {
      long sequence = _fsyncService.allocateFsyncSequence();
      
      _fsyncService.fsync(sequence, getLastMmap());
    }

    @Override
    public void fsync(Result result)
    {
      long sequence = _fsyncService.allocateFsyncSequence();
      
      _fsyncService.fsync(sequence, getLastMmap(), result);
    }

    @Override
    public void fsyncSchedule(Result result)
    {
      long sequence = _fsyncService.allocateFsyncSequence();
      
      _fsyncService.scheduleFsync(sequence, getLastMmap(), result);
    }
    */
    
    private MmapFile getLastMmap()
    {
      return _mmapFile[_mmap.length - 1];
    }
    
    @Override
    public OutStore clone()
    {
      throw new IllegalStateException();
    }

    @Override
    public void close()
    {
      throw new IllegalStateException();
    }
    
    public String toString()
    {
      return getClass().getSimpleName() + "[" + _path + "]";
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy