public long writeCommit(CommitUpdateCommand cmd, int flags) {
    LogCodec codec = new LogCodec(resolver);
    synchronized (this) {
      try {
        long pos = fos.size(); // if we had flushed, this should be equal to channel.position()

        if (pos == 0) {
          writeLogHeader(codec);
          pos = fos.size();
        }
        codec.init(fos);
        codec.writeTag(JavaBinCodec.ARR, 3);
        codec.writeInt(UpdateLog.COMMIT | flags); // should just take one byte
        codec.writeLong(cmd.getVersion());
        codec.writeStr(END_MESSAGE); // ensure these bytes are (almost) last in the file

        endRecord(pos);

        fos.flush(); // flush since this will be the last record in a log fill
        assert fos.size() == channel.size();

        return pos;
      } catch (IOException e) {
        throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
      }
    }
  }
  private void checkWriteHeader(LogCodec codec, SolrInputDocument optional) throws IOException {

    // Unsynchronized access.  We can get away with an unsynchronized access here
    // since we will never get a false non-zero when the position is in fact 0.
    // rollback() is the only function that can reset to zero, and it blocks updates.
    if (fos.size() != 0) return;

    synchronized (this) {
      if (fos.size() != 0) return; // check again while synchronized
      if (optional != null) {
        addGlobalStrings(optional.getFieldNames());
      }
      writeLogHeader(codec);
    }
  }
  public long writeDelete(DeleteUpdateCommand cmd, int flags) {
    LogCodec codec = new LogCodec(resolver);

    try {
      checkWriteHeader(codec, null);

      BytesRef br = cmd.getIndexedId();

      MemOutputStream out = new MemOutputStream(new byte[20 + br.length]);
      codec.init(out);
      codec.writeTag(JavaBinCodec.ARR, 3);
      codec.writeInt(UpdateLog.DELETE | flags); // should just take one byte
      codec.writeLong(cmd.getVersion());
      codec.writeByteArray(br.bytes, br.offset, br.length);

      synchronized (this) {
        long pos = fos.size(); // if we had flushed, this should be equal to channel.position()
        assert pos != 0;
        out.writeAll(fos);
        endRecord(pos);
        // fos.flushBuffer();  // flush later
        return pos;
      }

    } catch (IOException e) {
      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
    }
  }
 /**
  * takes a snapshot of the current position and number of records for later possible rollback, and
  * returns the position
  */
 public long snapshot() {
   synchronized (this) {
     snapshot_size = fos.size();
     snapshot_numRecords = numRecords;
     return snapshot_size;
   }
 }
 // This could mess with any readers or reverse readers that are open, or anything that might try
 // to do a log lookup.
 // This should only be used to roll back buffered updates, not actually applied updates.
 public void rollback(long pos) throws IOException {
   synchronized (this) {
     assert snapshot_size == pos;
     fos.flush();
     raf.setLength(pos);
     fos.setWritten(pos);
     assert fos.size() == pos;
     numRecords = snapshot_numRecords;
   }
 }
  protected void writeLogHeader(LogCodec codec) throws IOException {
    long pos = fos.size();
    assert pos == 0;

    Map header = new LinkedHashMap<String, Object>();
    header.put("SOLR_TLOG", 1); // a magic string + version number
    header.put("strings", globalStringList);
    codec.marshal(header, fos);

    endRecord(pos);
  }
 public long writeData(Object o) {
   LogCodec codec = new LogCodec(resolver);
   try {
     long pos = fos.size(); // if we had flushed, this should be equal to channel.position()
     codec.init(fos);
     codec.writeVal(o);
     return pos;
   } catch (IOException e) {
     throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
   }
 }
  public boolean endsWithCommit() throws IOException {
    long size;
    synchronized (this) {
      fos.flush();
      size = fos.size();
    }

    // the end of the file should have the end message (added during a commit) plus a 4 byte size
    byte[] buf = new byte[END_MESSAGE.length()];
    long pos = size - END_MESSAGE.length() - 4;
    if (pos < 0) return false;
    ChannelFastInputStream is = new ChannelFastInputStream(channel, pos);
    is.read(buf);
    for (int i = 0; i < buf.length; i++) {
      if (buf[i] != END_MESSAGE.charAt(i)) return false;
    }
    return true;
  }
  public long write(AddUpdateCommand cmd, int flags) {
    LogCodec codec = new LogCodec(resolver);
    SolrInputDocument sdoc = cmd.getSolrInputDocument();

    try {
      checkWriteHeader(codec, sdoc);

      // adaptive buffer sizing
      int bufSize = lastAddSize; // unsynchronized access of lastAddSize should be fine
      bufSize = Math.min(1024 * 1024, bufSize + (bufSize >> 3) + 256);

      MemOutputStream out = new MemOutputStream(new byte[bufSize]);
      codec.init(out);
      codec.writeTag(JavaBinCodec.ARR, 3);
      codec.writeInt(UpdateLog.ADD | flags); // should just take one byte
      codec.writeLong(cmd.getVersion());
      codec.writeSolrInputDocument(cmd.getSolrInputDocument());
      lastAddSize = (int) out.size();

      synchronized (this) {
        long pos = fos.size(); // if we had flushed, this should be equal to channel.position()
        assert pos != 0;

        /**
         * * System.out.println("###writing at " + pos + " fos.size()=" + fos.size() + "
         * raf.length()=" + raf.length()); if (pos != fos.size()) { throw new
         * RuntimeException("ERROR" + "###writing at " + pos + " fos.size()=" + fos.size() + "
         * raf.length()=" + raf.length()); } *
         */
        out.writeAll(fos);
        endRecord(pos);
        // fos.flushBuffer();  // flush later
        return pos;
      }

    } catch (IOException e) {
      // TODO: reset our file pointer back to "pos", the start of this record.
      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error logging add", e);
    }
  }
  public long writeDeleteByQuery(DeleteUpdateCommand cmd, int flags) {
    LogCodec codec = new LogCodec(resolver);
    try {
      checkWriteHeader(codec, null);

      MemOutputStream out = new MemOutputStream(new byte[20 + (cmd.query.length())]);
      codec.init(out);
      codec.writeTag(JavaBinCodec.ARR, 3);
      codec.writeInt(UpdateLog.DELETE_BY_QUERY | flags); // should just take one byte
      codec.writeLong(cmd.getVersion());
      codec.writeStr(cmd.query);

      synchronized (this) {
        long pos = fos.size(); // if we had flushed, this should be equal to channel.position()
        out.writeAll(fos);
        endRecord(pos);
        // fos.flushBuffer();  // flush later
        return pos;
      }
    } catch (IOException e) {
      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
    }
  }
 /** returns the current position in the log file */
 public long position() {
   synchronized (this) {
     return fos.size();
   }
 }
 protected void endRecord(long startRecordPosition) throws IOException {
   fos.writeInt((int) (fos.size() - startRecordPosition));
   numRecords++;
 }
  TransactionLog(File tlogFile, Collection<String> globalStrings, boolean openExisting) {
    boolean success = false;
    try {
      if (debug) {
        log.debug(
            "New TransactionLog file="
                + tlogFile
                + ", exists="
                + tlogFile.exists()
                + ", size="
                + tlogFile.length()
                + ", openExisting="
                + openExisting);
      }

      this.tlogFile = tlogFile;
      raf = new RandomAccessFile(this.tlogFile, "rw");
      long start = raf.length();
      channel = raf.getChannel();
      os = Channels.newOutputStream(channel);
      fos = new FastOutputStream(os, new byte[65536], 0);
      // fos = FastOutputStream.wrap(os);

      if (openExisting) {
        if (start > 0) {
          readHeader(null);
          raf.seek(start);
          assert channel.position() == start;
          fos.setWritten(start); // reflect that we aren't starting at the beginning
          assert fos.size() == channel.size();
        } else {
          addGlobalStrings(globalStrings);
        }
      } else {
        if (start > 0) {
          log.warn("New transaction log already exists:" + tlogFile + " size=" + raf.length());
          return;
        }

        if (start > 0) {
          raf.setLength(0);
        }
        addGlobalStrings(globalStrings);
      }

      success = true;

      assert ObjectReleaseTracker.track(this);

    } catch (IOException e) {
      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
    } finally {
      if (!success && raf != null) {
        try {
          raf.close();
        } catch (Exception e) {
          log.error("Error closing tlog file (after error opening)", e);
        }
      }
    }
  }