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

com.metamx.emitter.core.Batch Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2017 Metamarkets Group Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


package com.metamx.emitter.core;

import com.google.common.base.Preconditions;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.AbstractQueuedLongSynchronizer;

/**
 * Buffer for batched data + synchronization state.
 *
 * The state structure ({@link AbstractQueuedLongSynchronizer#state}):
 * Bits 0-30 - bufferWatermark
 * Bit 31 - always 0
 * Bits 32-62 - "parties" (the number of concurrent writers)
 * Bit 63 - sealed flag
 *
 * Writer threads (callers of {@link HttpPostEmitter#emit(Event)}) are eligible to come, increment bufferWatermark and
 * write data into the buffer, as long as sealed flag is false.
 *
 * {@link HttpPostEmitter#emittingThread} is eligible to emit the buffer, when sealed flag=true and parties=0 (all
 * writes are completed). See {@link #isEmittingAllowed(long)}.
 *
 * In this class, "lock" means "increment number of parties by 1", i. e. lock the emitter thread from emitting this
 * batch. "Unlock" means "decrement number of parties by 1".
 */
class Batch extends AbstractQueuedLongSynchronizer
{
  private static final long PARTY = 1L << 32;
  private static final long SEAL_BIT = 1L << 63;

  private static int bufferWatermark(long state)
  {
    return (int) state;
  }

  private static int parties(long state)
  {
    return ((int) (state >>> 32)) & Integer.MAX_VALUE;
  }

  private static boolean isSealed(long state)
  {
    // The highest bit is 1.
    return state < 0;
  }

  private static boolean isEmittingAllowed(long state)
  {
    return isSealed(state) && parties(state) == 0;
  }

  /**
   * Tags (arg values) to transmit request from {@link #releaseShared(long)} to {@link #tryReleaseShared(long)}.
   */
  private static final long UNLOCK_TAG = 0;
  private static final long UNLOCK_AND_SEAL_TAG = 1;
  private static final long SEAL_TAG = 2;

  /**
   * The emitter this batch belongs to.
   */
  private final HttpPostEmitter emitter;

  /**
   * The data buffer of the batch.
   */
  final byte[] buffer;

  /**
   * Ordering number of this batch, as they filled & emitted in {@link HttpPostEmitter} serially, starting from 0.
   */
  final int batchNumber;

  /**
   * The number of events in this batch, needed for event count-based batch emitting.
   */
  final AtomicInteger eventCount = new AtomicInteger(0);

  /**
   * The time when the first event was written into this batch, needed for timeout-based batch emitting.
   */
  private long firstEventTimestamp = -1;

  Batch(HttpPostEmitter emitter, byte[] buffer, int batchNumber)
  {
    this.emitter = emitter;
    this.buffer = buffer;
    this.batchNumber = batchNumber;
  }

  int getSealedBufferWatermark()
  {
    long state = getState();
    Preconditions.checkState(isSealed(state));
    return bufferWatermark(state);
  }

  /**
   * Tries to add (write) event to the batch, returns true, if successful. If fails, no subsequent attempts to add event
   * to this batch will succeed, the next batch should be taken.
   */
  boolean tryAddEvent(byte[] event)
  {
    while (true) {
      long state = getState();
      if (isSealed(state)) {
        return false;
      }
      int bufferWatermark = bufferWatermark(state);
      if (bufferWatermark == 0) {
        if (tryAddFirstEvent(event)) {
          return true;
        }
      } else if (newBufferWatermark(bufferWatermark, event) <= emitter.maxBufferWatermark) {
        if (tryAddNonFirstEvent(state, event)) {
          return true;
        }
      } else {
        seal();
        return false;
      }
    }
  }

  private boolean tryAddFirstEvent(byte[] event)
  {
    if (!tryReserveFirstEventSizeAndLock(event)) {
      return false;
    }
    try {
      int bufferOffset = emitter.batchingStrategy.writeBatchStart(buffer);
      writeEvent(event, bufferOffset);
      eventCount.incrementAndGet();
      firstEventTimestamp = System.currentTimeMillis();
      return true;
    }
    finally {
      unlock();
    }
  }

  private boolean tryReserveFirstEventSizeAndLock(byte[] event)
  {
    return compareAndSetState(0, emitter.batchingStrategy.batchStartLength() + event.length + PARTY);
  }

  private int newBufferWatermark(int bufferWatermark, byte[] eventBytes)
  {
    return bufferWatermark + emitter.batchingStrategy.separatorLength() + eventBytes.length;
  }

  private boolean tryAddNonFirstEvent(long state, byte[] event)
  {
    int bufferOffset = tryReserveEventSizeAndLock(state, emitter.batchingStrategy.separatorLength() + event.length);
    if (bufferOffset < 0) {
      return false;
    }
    try {
      bufferOffset = emitter.batchingStrategy.writeMessageSeparator(buffer, bufferOffset);
      writeEvent(event, bufferOffset);
      return true;
    }
    finally {
      unlockAndSealIfNeeded();
    }
  }

  /**
   * Returns the buffer offset at which the caller has reserved the ability to write `size` bytes exclusively,
   * or negative number, if the reservation attempt failed.
   */
  private int tryReserveEventSizeAndLock(long state, int size)
  {
    Preconditions.checkArgument(size > 0);
    int bufferWatermark = bufferWatermark(state);
    while (true) {
      if (compareAndSetState(state, state + size + PARTY)) {
        return bufferWatermark;
      }
      state = getState();
      if (isSealed(state)) {
        return -1;
      }
      bufferWatermark = bufferWatermark(state);
      int newBufferWatermark = bufferWatermark + size;
      Preconditions.checkState(newBufferWatermark > 0);
      if (newBufferWatermark > emitter.maxBufferWatermark) {
        return -1;
      }
    }
  }

  private void unlockAndSealIfNeeded()
  {
    if (eventCount.incrementAndGet() >= emitter.config.getFlushCount()) {
      unlockAndSeal();
    } else {
      long timeSinceFirstEvent = System.currentTimeMillis() - firstEventTimestamp;
      if (firstEventTimestamp > 0 && timeSinceFirstEvent > emitter.config.getFlushMillis()) {
        unlockAndSeal();
      } else {
        unlock();
      }
    }
  }

  void sealIfFlushNeeded() {
    long timeSinceFirstEvent = System.currentTimeMillis() - firstEventTimestamp;
    if (firstEventTimestamp > 0 && timeSinceFirstEvent > emitter.config.getFlushMillis()) {
      seal();
    }
  }

  private void writeEvent(byte[] event, int bufferOffset)
  {
    System.arraycopy(event, 0, buffer, bufferOffset, event.length);
  }


  private void unlock()
  {
    releaseShared(UNLOCK_TAG);
  }

  private void unlockAndSeal()
  {
    releaseShared(UNLOCK_AND_SEAL_TAG);
  }

  void seal()
  {
    releaseShared(SEAL_TAG);
  }

  @Override
  protected boolean tryReleaseShared(long tag)
  {
    if (tag == UNLOCK_TAG) {
      while (true) {
        long state = getState();
        int parties = parties(state);
        if (parties == 0) {
          throw new IllegalMonitorStateException();
        }
        long newState = state - PARTY;
        if (compareAndSetState(state, newState)) {
          return isEmittingAllowed(newState);
        }
      }
    } else if (tag == UNLOCK_AND_SEAL_TAG) {
      while (true) {
        long state = getState();
        int parties = parties(state);
        if (parties == 0) {
          throw new IllegalMonitorStateException();
        }
        long newState = (state - PARTY) | SEAL_BIT;
        if (compareAndSetState(state, newState)) {
          // Ensures only one thread calls emitter.onSealExclusive() for each batch.
          if (!isSealed(state)) {
            emitter.onSealExclusive(
                this,
                firstEventTimestamp > 0 ? System.currentTimeMillis() - firstEventTimestamp : -1
            );
          }
          return isEmittingAllowed(newState);
        }
      }
    } else if (tag == SEAL_TAG) {
      while (true) {
        long state = getState();
        if (isSealed(state)) {
          // Returning false, despite acquisition could be possible now, because this thread actually didn't update the
          // state, i. e. didn't "release" in AbstractQueuedLongSynchronizer's terms.
          return false;
        }
        long newState = state | SEAL_BIT;
        if (compareAndSetState(state, newState)) {
          emitter.onSealExclusive(
              this,
              firstEventTimestamp > 0 ? System.currentTimeMillis() - firstEventTimestamp : -1
          );
          return isEmittingAllowed(newState);
        }
      }
    } else {
      throw new IllegalStateException("Unknown tag: " + tag);
    }
  }

  void awaitEmittingAllowed()
  {
    acquireShared(1);
  }

  @Override
  protected long tryAcquireShared(long ignored)
  {
    return isEmittingAllowed(getState()) ? 1 : -1;
  }

  @Override
  public String toString()
  {
    long state = getState();
    return "Batch{"
           + "bufferWatermark=" + bufferWatermark(state) +
           ", parties=" + parties(state) +
           ", isSealed=" + isSealed(state) +
           "}";
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy