![JAR search and dependency download from the Maven repository](/logo.png)
com.scalar.db.util.groupcommit.Group Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of scalardb Show documentation
Show all versions of scalardb Show documentation
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 java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
// An abstract class that has logics and implementations to manage slots and trigger to emit it.
@ThreadSafe
abstract class Group {
private static final Logger logger = LoggerFactory.getLogger(Group.class);
protected final Emittable emitter;
protected final KeyManipulator
keyManipulator;
private final int capacity;
private final AtomicReference size = new AtomicReference<>();
protected final Map<
CHILD_KEY, Slot>
slots;
// Whether to reject a new value slot.
protected final AtomicReference status = new AtomicReference<>(Status.OPEN);
private final long oldGroupAbortTimeoutAtMillis;
// Status of the group.
enum Status {
// Accepting new slot reservation since the number of slots isn't fixed yet.
//
// Initial status of groups. No group can move back to OPEN.
OPEN(false, false, false),
// Not accepting new slot reservations since the number of slots is already fixed.
// Waiting all the slots are set with values.
//
// Groups with OPEN status can move to SIZE_FIXED.
SIZE_FIXED(true, false, false),
// All the slots are set with values. Ready to commit.
//
// Groups with OPEN or SIZE_FIXED status can move to READY.
READY(true, true, false),
// Group commit is done and all the clients have get the results.
//
// Groups with OPEN, SIZE_FIXED or READY status can move to DONE.
DONE(true, true, true);
final boolean isSizeFixed;
final boolean isReady;
final boolean isDone;
Status(boolean isSizeFixed, boolean isReady, boolean isDone) {
this.isSizeFixed = isSizeFixed;
this.isReady = isReady;
this.isDone = isDone;
}
}
Group(
Emittable emitter,
KeyManipulator
keyManipulator,
int capacity,
long oldGroupAbortTimeoutMillis) {
this.emitter = emitter;
this.keyManipulator = keyManipulator;
this.capacity = capacity;
this.slots = new HashMap<>(capacity);
this.oldGroupAbortTimeoutAtMillis = System.currentTimeMillis() + oldGroupAbortTimeoutMillis;
}
private boolean noMoreSlot() {
return slots.size() >= capacity;
}
abstract FULL_KEY fullKey(CHILD_KEY childKey);
// If it returns null, the Group is already size-fixed and a retry is needed.
@Nullable
protected synchronized FULL_KEY reserveNewSlot(
Slot slot) {
if (isSizeFixed()) {
return null;
}
reserveSlot(slot);
if (noMoreSlot()) {
fixSize();
}
return slot.fullKey();
}
private void reserveSlot(
Slot slot) {
Slot, ?, ?, ?, ?, ?> oldSlot = slots.put(slot.key(), slot);
if (oldSlot != null) {
throw new AssertionError(
String.format("An old slot exist unexpectedly. Slot: %s, Old slot: %s", slot, oldSlot));
}
updateStatus();
}
// This sync is for moving timed-out value slot from a normal buf to a new delayed buf.
// Returns null if the state of the group is changed (e.g., the slot is moved to another group).
@Nullable
private synchronized Slot
putValueToSlot(CHILD_KEY childKey, V value) {
if (isReady()) {
logger.debug(
"This group is already ready, but trying to put a value to the slot. Probably the slot is moved to a DelayedGroup. Retrying... Group: {}, Child key: {}",
this,
childKey);
return null;
}
Slot slot =
slots.get(childKey);
if (slot == null) {
return null;
}
slot.setValue(value);
return slot;
}
boolean putValueToSlotAndWait(CHILD_KEY childKey, V value) throws GroupCommitException {
Slot slot;
synchronized (this) {
slot = putValueToSlot(childKey, value);
if (slot == null) {
// Needs a retry since the state of the group is changed.
return false;
}
updateStatus();
delegateEmitTaskToWaiterIfReady();
}
slot.waitUntilEmit();
return true;
}
private synchronized void updateSlotsSize() {
size.set(slots.size());
}
synchronized void fixSize() {
updateSlotsSize();
updateStatus();
delegateEmitTaskToWaiterIfReady();
}
@Nullable
protected Integer size() {
return size.get();
}
boolean isSizeFixed() {
return status.get().isSizeFixed;
}
boolean isReady() {
return status.get().isReady;
}
boolean isDone() {
return status.get().isDone;
}
synchronized void updateStatus() {
if (slots.isEmpty()) {
updateSlotsSize();
status.set(Status.DONE);
return;
}
Status newStatus = status.get();
if (newStatus == Status.OPEN) {
if (size.get() != null) {
newStatus = Status.SIZE_FIXED;
}
}
if (newStatus == Status.SIZE_FIXED) {
int readySlotCount = 0;
for (Slot slot :
slots.values()) {
if (slot.isReady()) {
readySlotCount++;
}
}
if (readySlotCount >= size.get()) {
newStatus = Status.READY;
}
}
if (newStatus == Status.READY) {
int doneSlotCount = 0;
for (Slot slot :
slots.values()) {
if (slot.isDone()) {
doneSlotCount++;
}
}
if (doneSlotCount >= size.get()) {
newStatus = Status.DONE;
}
}
status.set(newStatus);
}
synchronized boolean removeSlot(CHILD_KEY childKey) {
Slot slot =
slots.get(childKey);
if (slot == null) {
// Probably, the slot is already removed by the client or moved from NormalGroup to
// DelayedGroup.
return false;
}
if (slot.isReady()) {
// Technically, it's possible to remove the ready slot from the group since the client thread
// is waiting on the slot already. But removing it might cause more complicated state,
// so leave it as-is.
logger.debug(
"Attempted to remove this slot, but it will not be removed because it is already ready. Group: {}, Slot: {}",
this,
slot);
return false;
}
Slot removed =
slots.remove(childKey);
assert removed != null;
if (size.get() != null && size.get() > 0) {
size.set(size.get() - 1);
}
updateStatus();
delegateEmitTaskToWaiterIfReady();
return true;
}
synchronized void abort() {
for (Slot slot :
slots.values()) {
// Tell the clients that the slots are aborted.
slot.markAsFailed(
new GroupCommitException(
String.format(
"The slot in the old group is timed out and aborted. Group:%s, Slot:%s",
this, slot)));
}
}
protected abstract void delegateEmitTaskToWaiter();
synchronized void delegateEmitTaskToWaiterIfReady() {
if (isDone()) {
return;
}
if (isReady()) {
try {
delegateEmitTaskToWaiter();
} finally {
updateStatus();
}
}
}
public long oldGroupAbortTimeoutAtMillis() {
return oldGroupAbortTimeoutAtMillis;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy