/** * Allocate a new context big enough for globalCount + localCount + remoteCount elements and * return the initial corresponding ContextState. */ public static ContextState allocate(int globalCount, int localCount, int remoteCount) { int headerLength = HEADER_SIZE_LENGTH + (globalCount + localCount) * HEADER_ELT_LENGTH; int bodyLength = (globalCount + localCount + remoteCount) * STEP_LENGTH; ByteBuffer buffer = ByteBuffer.allocate(headerLength + bodyLength); buffer.putShort(buffer.position(), (short) (globalCount + localCount)); return ContextState.wrap(buffer); }
/** * Human-readable String from context. * * @param context counter context. * @return a human-readable String of the context. */ public String toString(ByteBuffer context) { ContextState state = ContextState.wrap(context); StringBuilder sb = new StringBuilder(); sb.append("["); while (state.hasRemaining()) { if (state.getElementIndex() > 0) sb.append(","); sb.append("{"); sb.append(state.getCounterId()).append(", "); sb.append(state.getClock()).append(", "); sb.append(state.getCount()); sb.append("}"); if (state.isGlobal()) sb.append("$"); else if (state.isLocal()) sb.append("*"); state.moveToNext(); } sb.append("]"); return sb.toString(); }
/** * Return a context w/ an aggregated count for each counter id. * * @param left counter context. * @param right counter context. */ public ByteBuffer merge(ByteBuffer left, ByteBuffer right) { boolean leftIsSuperSet = true; boolean rightIsSuperSet = true; int globalCount = 0; int localCount = 0; int remoteCount = 0; ContextState leftState = ContextState.wrap(left); ContextState rightState = ContextState.wrap(right); while (leftState.hasRemaining() && rightState.hasRemaining()) { int cmp = leftState.compareIdTo(rightState); if (cmp == 0) { Relationship rel = compare(leftState, rightState); if (rel == Relationship.GREATER_THAN) rightIsSuperSet = false; else if (rel == Relationship.LESS_THAN) leftIsSuperSet = false; else if (rel == Relationship.DISJOINT) leftIsSuperSet = rightIsSuperSet = false; if (leftState.isGlobal() || rightState.isGlobal()) globalCount += 1; else if (leftState.isLocal() || rightState.isLocal()) localCount += 1; else remoteCount += 1; leftState.moveToNext(); rightState.moveToNext(); } else if (cmp > 0) { leftIsSuperSet = false; if (rightState.isGlobal()) globalCount += 1; else if (rightState.isLocal()) localCount += 1; else remoteCount += 1; rightState.moveToNext(); } else // cmp < 0 { rightIsSuperSet = false; if (leftState.isGlobal()) globalCount += 1; else if (leftState.isLocal()) localCount += 1; else remoteCount += 1; leftState.moveToNext(); } } if (leftState.hasRemaining()) rightIsSuperSet = false; else if (rightState.hasRemaining()) leftIsSuperSet = false; // if one of the contexts is a superset, return it early. if (leftIsSuperSet) return left; else if (rightIsSuperSet) return right; while (leftState.hasRemaining()) { if (leftState.isGlobal()) globalCount += 1; else if (leftState.isLocal()) localCount += 1; else remoteCount += 1; leftState.moveToNext(); } while (rightState.hasRemaining()) { if (rightState.isGlobal()) globalCount += 1; else if (rightState.isLocal()) localCount += 1; else remoteCount += 1; rightState.moveToNext(); } leftState.reset(); rightState.reset(); return merge( ContextState.allocate(globalCount, localCount, remoteCount), leftState, rightState); }
/** * Determine the count relationship between two contexts. * * <p>EQUAL: Equal set of nodes and every count is equal. GREATER_THAN: Superset of nodes and * every count is equal or greater than its corollary. LESS_THAN: Subset of nodes and every count * is equal or less than its corollary. DISJOINT: Node sets are not equal and/or counts are not * all greater or less than. * * <p>Strategy: compare node logical clocks (like a version vector). * * @param left counter context. * @param right counter context. * @return the Relationship between the contexts. */ public Relationship diff(ByteBuffer left, ByteBuffer right) { Relationship relationship = Relationship.EQUAL; ContextState leftState = ContextState.wrap(left); ContextState rightState = ContextState.wrap(right); while (leftState.hasRemaining() && rightState.hasRemaining()) { // compare id bytes int compareId = leftState.compareIdTo(rightState); if (compareId == 0) { long leftClock = leftState.getClock(); long rightClock = rightState.getClock(); long leftCount = leftState.getCount(); long rightCount = rightState.getCount(); // advance leftState.moveToNext(); rightState.moveToNext(); // process clock comparisons if (leftClock == rightClock) { if (leftCount != rightCount) { // Inconsistent shard (see the corresponding code in merge()). We return DISJOINT in // this // case so that it will be treated as a difference, allowing read-repair to work. return Relationship.DISJOINT; } } else if ((leftClock >= 0 && rightClock > 0 && leftClock > rightClock) || (leftClock < 0 && (rightClock > 0 || leftClock < rightClock))) { if (relationship == Relationship.EQUAL) relationship = Relationship.GREATER_THAN; else if (relationship == Relationship.LESS_THAN) return Relationship.DISJOINT; // relationship == Relationship.GREATER_THAN } else { if (relationship == Relationship.EQUAL) relationship = Relationship.LESS_THAN; else if (relationship == Relationship.GREATER_THAN) return Relationship.DISJOINT; // relationship == Relationship.LESS_THAN } } else if (compareId > 0) { // only advance the right context rightState.moveToNext(); if (relationship == Relationship.EQUAL) relationship = Relationship.LESS_THAN; else if (relationship == Relationship.GREATER_THAN) return Relationship.DISJOINT; // relationship == Relationship.LESS_THAN } else // compareId < 0 { // only advance the left context leftState.moveToNext(); if (relationship == Relationship.EQUAL) relationship = Relationship.GREATER_THAN; else if (relationship == Relationship.LESS_THAN) return Relationship.DISJOINT; // relationship == Relationship.GREATER_THAN } } // check final lengths if (leftState.hasRemaining()) { if (relationship == Relationship.EQUAL) return Relationship.GREATER_THAN; else if (relationship == Relationship.LESS_THAN) return Relationship.DISJOINT; } if (rightState.hasRemaining()) { if (relationship == Relationship.EQUAL) return Relationship.LESS_THAN; else if (relationship == Relationship.GREATER_THAN) return Relationship.DISJOINT; } return relationship; }