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

com.caucho.v5.db.journal.JournalSegment Maven / Gradle / Ivy

There is a newer version: 1.0.1
Show newest version
/*
 * 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.v5.db.journal;

import java.io.IOException;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.CRC32;

import com.caucho.v5.db.journal.JournalStream.ReplayCallback;
import com.caucho.v5.io.StreamImpl;
import com.caucho.v5.io.TempBuffer;
import com.caucho.v5.store.io.InStore;
import com.caucho.v5.store.io.OutStore;
import com.caucho.v5.util.BitsUtil;
import com.caucho.v5.util.Hex;
import com.caucho.v5.util.L10N;
import com.caucho.v5.vfs.ReadStream;

/**
 * The store manages the block-based journal store file.
 * 
 * Journals are shared with the same file. Each journal has a unique
 * key.
 */
public class JournalSegment
{
  private static final L10N L = new L10N(JournalSegment.class); 
  private static final Logger log
    = Logger.getLogger(JournalSegment.class.getName());
  
  private final static int BLOCK_SIZE = 8 * 1024;
  private final static int OFFSET_MASK = BLOCK_SIZE - 1;
  private final static int ADDRESS_MASK = ~OFFSET_MASK;
  private final static int PAD = 8;
  
  private final static int CHECKPOINT_START = 0x8000;
  private final static int CHECKPOINT_END   = 0x8001;
  
  private final JournalStore _blockStore;
  
  private final long _startAddress;
  
  private final long _headAddress;
  private final long _tailAddress;
  
  private final byte []_key;
  
  private long _initialSequence = 1;
  private long _sequence = _initialSequence;
  private int _sequenceHash;
  
  private byte []_sequenceHashBytes = new byte[4];
  
  private long _lastCheckpointStart;
  private long _lastCheckpointEnd;
  
  private boolean _isWhileCheckpoint;
  
  private long _index;
  private long _flushIndex;
  
  private TempBuffer _tBuf;
  private byte []_buffer;
  private byte []_headerBuffer = new byte[8];

  private final CRC32 _crc = new CRC32();
  
  private OutStore _os;

  private JournalGroup _group;
  
  JournalSegment(JournalStore blockStore,
                 byte []key,
                 long startAddress,
                 long initSequence,
                 long sequence)
  {
    _blockStore = blockStore;
    
    _tBuf = TempBuffer.allocateLarge();
    _buffer = _tBuf.buffer();
    
    _startAddress = startAddress;
    _key = new byte [key.length];
    System.arraycopy(key, 0, _key, 0, _key.length);
    
    _headAddress = _startAddress + 256;
    _tailAddress = _startAddress + blockStore.getTailAddress();

    _initialSequence = initSequence;
    
    setSequence(sequence);
  }

  public long getAddress()
  {
    return _startAddress;
  }
  
  private void setSequence(long sequence)
  {
    _sequence = sequence;

    _sequenceHash = calculateSequenceHash(_sequence);
    
    _sequenceHashBytes[0] = (byte) (_sequenceHash >> 24);
    _sequenceHashBytes[1] = (byte) (_sequenceHash >> 16);
    _sequenceHashBytes[2] = (byte) (_sequenceHash >> 8);
    _sequenceHashBytes[3] = (byte) (_sequenceHash);
  }
  
  private int calculateSequenceHash(long sequence)
  {
    _crc.reset();
    _crc.update(_key);
    _crc.update((int) (sequence >> 56));
    _crc.update((int) (sequence >> 48));
    _crc.update((int) (sequence >> 40));
    _crc.update((int) (sequence >> 32));
    _crc.update((int) (sequence >> 24));
    _crc.update((int) (sequence >> 16));
    _crc.update((int) (sequence >> 8));
    _crc.update((int) (sequence));

    return (int) _crc.getValue();
  }

  long getSequence()
  {
    return _sequence;
  }

  public byte[] getKey()
  {
    return _key;
  }

  public long getTailAddress()
  {
    return _tailAddress;
  }
  
  private int getOffset(long address)
  {
    return (int) (address & OFFSET_MASK);
  }
  
  private long getBlockAddress(long address)
  {
    return (address & ADDRESS_MASK);
  }
  
  private int getSegmentSize()
  {
    return _blockStore.getSegmentSize();
  }
  
  void startGroup(JournalGroup group)
  {
    _group = group;
  }
  
  public void openWrite()
  {
    if (_os != null) {
      throw new IllegalStateException();
    }
    
    _os = _blockStore.openWrite(_startAddress, getSegmentSize());
    _index = _headAddress;
    _flushIndex = _startAddress;

    Arrays.fill(_buffer, (byte) 0);
    
    BitsUtil.writeLong(_buffer, 0, _sequence);
    
    System.arraycopy(_key, 0, _buffer, 8, _key.length); 
  }
  
  public void startWrite()
  {
    initCrc();
  }
  
  public void startRead()
  {
    initCrc();
  }
  
  private void initCrc()
  {
    _crc.reset();
    _crc.update(_sequenceHashBytes, 0, 4);
  }
  
  /**
   * Writes a partial journal entry. The entry will only be completed
   * after completeWrite() has been called.
   * 
   * The write will return false if the journal segment is full. The caller
   * needs to retry the write with a new segment.
   */
  
  public boolean write(byte []buffer, int offset, int length)
  {
    if (length == 0) {
      return true; // throw new IllegalStateException();
    }
    
    int count = length / BLOCK_SIZE + 1;
    
    if (_tailAddress <= _index + length + PAD + 2 * count) {
      return false;
    }

    _crc.update(buffer, offset, length);
    
    byte []headerBuffer = _headerBuffer;
    
    while (length > 0) {
      int sublen = Math.min(length, BLOCK_SIZE);
      
      BitsUtil.writeInt16(headerBuffer, 0, sublen);
      
      writeImpl(headerBuffer, 0, 2);
      writeImpl(buffer, offset, sublen);
      
      length -= sublen;
      offset += sublen;
    }
    
    return true;
  }

  /**
   * Completes a journal entry started by a write() call.
   */
  public boolean completeWrite()
  {
    int digest = (int) _crc.getValue();

    byte []headerBuffer = _headerBuffer;
    BitsUtil.writeInt16(headerBuffer, 0, 0);
    BitsUtil.writeInt(headerBuffer, 2, digest);
    
    writeImpl(headerBuffer, 0, 6);
    
    return true;
  }
  
  private void writeImpl(byte []buffer, int offset, int length)
  {
    int blockOffset = getOffset(_index);

    while (length > 0) {
      int sublen = Math.min(length, BLOCK_SIZE - blockOffset);
      
      byte []blockBuffer = _buffer;
      
      System.arraycopy(buffer, offset, blockBuffer, blockOffset, sublen);
      
      length -= sublen;
      offset += sublen;
      blockOffset += sublen;
      _index += sublen;
      
      if (blockOffset == BLOCK_SIZE) {
        if (! nextBlock()) {
          throw new IllegalStateException();
        }
        
        blockOffset = 0;
        continue;
      }
    }
  }
  
  /**
   * Starts checkpoint processing. A checkpoint start changes the epoch
   * ID for new journal entries, but does not yet close the previous epoch.
   * 
   * Journal entries written between checkpointStart() and checkpointEnd()
   * belong to the successor epoch.
   */
  public void checkpointStart()
  {
    if (_isWhileCheckpoint) {
      throw new IllegalStateException();
    }
    
    _isWhileCheckpoint = true;
    
    long sequence = ++_sequence;
    
    setSequence(sequence);
    
    byte []headerBuffer = _headerBuffer;
    BitsUtil.writeInt16(headerBuffer, 0, CHECKPOINT_START);
    writeImpl(headerBuffer, 0, 2);
    
    _lastCheckpointStart = _index - _startAddress;
  }
  
  /**
   * Closes the previous epoch. Journal entries in a closed epoch will not
   * be replayed on a journal restore.
   */
  public void checkpointEnd()
  {
    if (! _isWhileCheckpoint) {
      throw new IllegalStateException();
    }
    
    _isWhileCheckpoint = false;
    
    byte []headerBuffer = _headerBuffer;
    BitsUtil.writeInt16(headerBuffer, 0, CHECKPOINT_END);
    writeImpl(headerBuffer, 0, 2);
    
    flush();
    
    _lastCheckpointEnd = _index - _startAddress;
    
    writeTail(_os);
  }
  
  void checkpointClose()
  {
    try (OutStore out = _blockStore.openWrite(_startAddress, getSegmentSize())) {
      setSequence(0);
      _initialSequence = 0;
      _lastCheckpointStart = 0;
      _lastCheckpointEnd = 0;
      
      writeTail(out);
    }
    
    // _store.free(this);
  }
  

  /**
   * Replays all open journal entries. The journal entry will call into
   * the callback listener with an open InputStream to read the entry.
   */
  public void replay(ReplayCallback replayCallback)
  {
    TempBuffer tReadBuffer = TempBuffer.allocateLarge();
    byte []readBuffer = tReadBuffer.buffer();
    int bufferLength = readBuffer.length;

    try (InStore jIn = _blockStore.openRead(_startAddress, getSegmentSize())) {
      Replay replay = readReplay(jIn);

      if (replay == null) {
        return;
      }
      
      long address = replay.getCheckpointStart();
      long next;
      
      setSequence(replay.getSequence());

      TempBuffer tBuffer = TempBuffer.allocate();
      byte []tempBuffer = tBuffer.buffer();
      
      jIn.read(getBlockAddress(address), readBuffer, 0, bufferLength);
      ReadStream is = new ReadStream();
      
      while (address < _tailAddress
             && (next = scanItem(jIn, address, readBuffer, tempBuffer)) > 0) {
        boolean isOverflow = getBlockAddress(address) != getBlockAddress(next);
        
        // if scanning has passed the buffer boundary, need to re-read
        // the initial buffer
        if (isOverflow) {
          jIn.read(getBlockAddress(address), readBuffer, 0, bufferLength);
        }
      
        ReplayInputStream rIn = new ReplayInputStream(jIn, readBuffer, address);
        
        is.init(rIn);

        try {
          replayCallback.onItem(is);
        } catch (Exception e) {
          e.printStackTrace();
          log.log(Level.FINER, e.toString(), e);
        }
      
        address = next;
        
        if (isOverflow) {
          jIn.read(getBlockAddress(address), readBuffer, 0, bufferLength);
        }
        
        _index = address;
        _flushIndex = address;
      }
    }
  }
  
  /**
   * Validates that the item is complete and correct.
   */
  private long scanItem(InStore is, long address, byte []readBuffer,
                        byte []tempBuffer)
  {
    startRead();
    
    byte []headerBuffer = _headerBuffer;
    
    while (address + 2 < _tailAddress) {
      readImpl(is, address, readBuffer, headerBuffer, 0, 2);
      
      address += 2;
      
      int len = BitsUtil.readInt16(headerBuffer, 0);

      if ((len & 0x8000) != 0) {
        if (len == CHECKPOINT_START) {
          setSequence(_sequence + 1);
        }
        
        startRead();
        
        continue;
      }
        
      if (len == 0) {
        readImpl(is, address, readBuffer, headerBuffer, 0, 4);
        address += 4;
        
        int crc = BitsUtil.readInt(headerBuffer, 0);
        
        int digest = (int) _crc.getValue();

        if (crc == digest) {
          return address;
        }
        else {
          return -1;
        }
      }
      
      if (_tailAddress < address + len) {
        return -1;
      }
      
      int readLen = len;
      
      while (readLen > 0) {
        int sublen = Math.min(readLen, tempBuffer.length);
      
        readImpl(is, address, readBuffer, tempBuffer, 0, sublen);
        _crc.update(tempBuffer, 0, sublen);
        
        address += sublen;
        readLen -= sublen;
      }
    }

    return -1;
  }
  
  private int readImpl(InStore is, 
                       long address, byte []readBuffer, 
                       byte []buffer, int offset, int length)
  {
    if (_tailAddress < address + length) {
      throw new IllegalStateException(L.l("tail {0} address {1} length {2}",
                                          _tailAddress, address, length));
    }
    
    int remaining = length;
    
    while (remaining > 0) {
      int readOffset = getOffset(address);
      
      if (readOffset == 0) {
        is.read(getBlockAddress(address), readBuffer, 0, readBuffer.length);
      }
      
      int sublen = Math.min(readBuffer.length - readOffset, remaining);
      
      System.arraycopy(readBuffer, readOffset, buffer, offset, sublen);

      address += sublen;
      offset += sublen;
      remaining -= sublen;
    }

    return length;
  }
  
  private boolean nextBlock()
  {
    int offset = getOffset(_index);
    
    if (offset != 0) {
      throw new IllegalStateException();
    }
    
    flush();
    
    return _index < _tailAddress;
  }
  

  public void flush()
  {
    long flushIndex = _flushIndex;
    long index = _index;
    
    if (flushIndex < index && _os != null) {
      int flushOffset = getOffset(flushIndex);
      long flushAddress = getBlockAddress(flushIndex);
      
      int offset = getOffset(index);
      long address = getBlockAddress(index);
      
      int sublen;
      
      if (flushAddress == address) {
        sublen = offset - flushOffset;
      }
      else {
        sublen = BLOCK_SIZE - flushOffset;
      }
      
      _os.write(flushIndex, _buffer, flushOffset, sublen);

      _flushIndex = index;
    }
  }
  
  private Replay readReplay(InStore jIn)
  {
    byte []buffer = _buffer;
    
    jIn.read(_tailAddress, buffer, 0, buffer.length);
    
    int offset = 0;
    
    long initSequence = BitsUtil.readLong(buffer, offset);
    offset += 8;
    
    offset += _key.length;
    
    long sequence = BitsUtil.readLong(buffer, offset);
    offset += 8;
    
    long checkpointStart = BitsUtil.readLong(buffer, offset);
    offset += 8;
    
    long checkpointEnd = BitsUtil.readLong(buffer, offset);
    offset += 8;
    
    if (sequence <= 0 || checkpointStart <= 0) {
      sequence = initSequence;
      checkpointStart = _headAddress;
      checkpointEnd = _headAddress;
    }
    else {
      checkpointStart += _startAddress;
      checkpointEnd += _startAddress;
    }
    
    if (initSequence <= 0) {
      return null;
    }
    
    return new Replay(sequence, checkpointStart, checkpointEnd);
  }
  
  public void writeTail(OutStore os)
  {
    if (os == null) {
      return; // XXX: shutdown timing?
    }
    TempBuffer tBuf = TempBuffer.allocateLarge();
    byte []buffer = tBuf.buffer();
    
    int offset = 0;
    
    BitsUtil.writeLong(buffer, offset, _initialSequence);
    offset += 8;
    
    System.arraycopy(_key, 0, buffer, offset, _key.length);
    offset += _key.length;
    
    BitsUtil.writeLong(buffer, offset, _sequence);
    offset += 8;
    
    BitsUtil.writeLong(buffer, offset, _lastCheckpointStart);
    offset += 8;
    
    BitsUtil.writeLong(buffer, offset, _lastCheckpointEnd);
    offset += 8;
    
    os.write(_tailAddress, buffer, 0, offset);
    
    tBuf.freeSelf();
  }
  
  public void close()
  {
    OutStore os = _os;

    if (os != null) {
      if (_isWhileCheckpoint) {
        System.err.println("ERROR: Closing while checkpoint " + this);
      }
      
      flush();
    
      _os = null;

      os.close();
    }

    JournalGroup group = _group;
    _group = null;
    
    if (group != null) {
      group.closeSegment(this);
    }
  }

  @Override
  public String toString()
  {
    return (getClass().getSimpleName()
            + "[" + Long.toHexString(_startAddress)
            + ",key=" + Hex.toHex(_key, 0, 4)
            + "]");
           
  }
  
  class ReplayInputStream extends StreamImpl {
    private InStore _jIn;
    private byte []_buffer;
    
    private long _address;
    private int _sublen;
    private boolean _isDone;
    private byte []_data = new byte[1];
    
    ReplayInputStream(InStore jIn, byte []readBuffer, long address)
    {
      _jIn = jIn;
      _buffer = readBuffer;
      _address = address;

      if (getOffset(address) == 0) {
        _jIn.read(address, readBuffer, 0, readBuffer.length);
      }
    }
    
    @Override
    public boolean canRead()
    {
      return true;
    }
    
    @Override
    public int read(byte []buffer, int offset, int length)
      throws IOException
    {
      if (_sublen <= 0) {
        if (_isDone) {
          return -1;
        }

        // skip checkpoint
        do {
          readImpl(_jIn, _address, _buffer, _headerBuffer, 0, 2);
          
          _address += 2;
          
          _sublen = BitsUtil.readInt16(_headerBuffer, 0);
        } while ((_sublen & 0x8000) != 0);
          
        if (_sublen == 0) {
          _address += 4;
          _isDone = true;
          return -1;
        }
      }
      
      int sublen = Math.min(_sublen, length);
        
      readImpl(_jIn, _address, _buffer, buffer, offset, sublen);
          
      _address += sublen;
      _sublen -= sublen;
          
      return sublen;
    }
  }
  
  static class Replay {
    private final long _sequence;
    private final long _checkpointStart;
    private final long _checkpointEnd;
    
    Replay(long sequence,
           long checkpointStart,
           long checkpointEnd)
    {
      _sequence = sequence;
      _checkpointStart = checkpointStart;
      _checkpointEnd = checkpointEnd;
    }
    
    long getSequence()
    {
      return _sequence;
    }
    
    long getCheckpointStart()
    {
      return _checkpointStart;
    }
    
    long getCheckpointEnd()
    {
      return _checkpointEnd;
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy