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

com.scalar.db.util.groupcommit.NormalGroup Maven / Gradle / Ivy

Go to download

A universal transaction manager that achieves database-agnostic transactions and distributed transactions that span multiple databases

The newest version!
package com.scalar.db.util.groupcommit;

import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.scalar.db.util.ThrowableRunnable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

// A group for multiple slots that will be group-committed at once.
@ThreadSafe
class NormalGroup
    extends Group {
  private static final Logger logger = LoggerFactory.getLogger(NormalGroup.class);

  private final PARENT_KEY parentKey;
  private final int delayedSlotMoveTimeoutMillis;
  private final long groupSizeFixTimeoutAtMillis;
  private final AtomicLong delayedSlotMoveTimeoutAtMillis = new AtomicLong();

  NormalGroup(
      GroupCommitConfig config,
      Emittable emitter,
      KeyManipulator
          keyManipulator) {
    super(emitter, keyManipulator, config.slotCapacity(), config.oldGroupAbortTimeoutMillis());
    this.delayedSlotMoveTimeoutMillis = config.delayedSlotMoveTimeoutMillis();
    this.groupSizeFixTimeoutAtMillis =
        System.currentTimeMillis() + config.groupSizeFixTimeoutMillis();
    updateDelayedSlotMoveTimeoutAt();
    this.parentKey = keyManipulator.generateParentKey();
  }

  PARENT_KEY parentKey() {
    return parentKey;
  }

  @Override
  FULL_KEY fullKey(CHILD_KEY childKey) {
    return keyManipulator.fullKey(parentKey, childKey);
  }

  // If it returns null, the Group is already size-fixed and a retry is needed.
  @Nullable
  FULL_KEY reserveNewSlot(CHILD_KEY childKey) {
    return reserveNewSlot(new Slot<>(childKey, this));
  }

  @Nullable
  synchronized List>
      removeNotReadySlots() {
    if (!isSizeFixed()) {
      logger.info(
          "No need to remove any slot since the size isn't fixed yet. Too early. Group: {}", this);
      return null;
    }

    // Lazy instantiation might be better, but it's likely there is a not-ready value slot since
    // it's already timed-out.
    List> removed =
        new ArrayList<>();
    for (Entry>
        entry : slots.entrySet()) {
      Slot slot =
          entry.getValue();
      if (!slot.isReady()) {
        removed.add(slot);
      }
    }

    // The size must be already fixed since the group is already size-fixed.
    Integer size = size();
    assert size != null;
    if (removed.size() >= size) {
      logger.debug("No need to remove any slot since all the slots are not ready. Group: {}", this);
      return null;
    }

    for (Slot slot : removed) {
      removeSlot(slot.key());
      logger.debug(
          "Removed a value slot from group to move it to delayed group. Group: {}, Slot: {}",
          this,
          slot);
    }
    return removed;
  }

  @Override
  public synchronized void delegateEmitTaskToWaiter() {
    final AtomicReference>
        emitterSlot = new AtomicReference<>();

    boolean isFirst = true;
    List values = new ArrayList<>(slots.size());
    for (Slot slot :
        slots.values()) {
      // Use the first slot as an emitter.
      if (isFirst) {
        isFirst = false;
        emitterSlot.set(slot);
      }
      values.add(slot.value());
    }

    // This task is passed to only the first slot, so the slot will be resumed.
    // Other slots will be blocked until `markAsXxxx()` is called.
    ThrowableRunnable taskForEmitterSlot =
        () -> {
          try {
            if (isDone()) {
              logger.info("This group is already done, but trying to emit. Group: {}", this);
              return;
            }
            emitter.emitNormalGroup(keyManipulator.emitParentKeyFromParentKey(parentKey), values);

            synchronized (this) {
              // Wake up the other waiting threads.
              // Pass null since the value is already emitted by the thread of `firstSlot`.
              for (Slot slot :
                  slots.values()) {
                if (slot != emitterSlot.get()) {
                  slot.markAsSuccess();
                }
              }
            }
          } catch (Exception e) {
            GroupCommitException exception =
                new GroupCommitException(String.format("Group commit failed. Group: %s", this), e);

            // Let other threads know the exception.
            synchronized (this) {
              for (Slot slot :
                  slots.values()) {
                if (slot != emitterSlot.get()) {
                  slot.markAsFailed(exception);
                }
              }
            }

            // Throw the exception for the thread which executed the group commit.
            throw exception;
          }
        };

    emitterSlot.get().delegateTaskToWaiter(taskForEmitterSlot);
  }

  void updateDelayedSlotMoveTimeoutAt() {
    delayedSlotMoveTimeoutAtMillis.set(System.currentTimeMillis() + delayedSlotMoveTimeoutMillis);
  }

  long groupSizeFixTimeoutAtMillis() {
    return groupSizeFixTimeoutAtMillis;
  }

  long delayedSlotMoveTimeoutAtMillis() {
    return delayedSlotMoveTimeoutAtMillis.get();
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof NormalGroup)) return false;
    NormalGroup that = (NormalGroup) o;
    return Objects.equal(parentKey, that.parentKey);
  }

  @Override
  public int hashCode() {
    return Objects.hashCode(parentKey);
  }

  @Override
  public String toString() {
    return MoreObjects.toStringHelper(this)
        .add("parentKey", parentKey)
        .add("status", status)
        .add("valueSlots.size", slots.size())
        .toString();
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy