/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.ignite.internal.processors.hadoop.shuffle.collections;
import java.io.DataInput;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Random;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.IgniteException;
import org.apache.ignite.internal.processors.hadoop.HadoopJobInfo;
import org.apache.ignite.internal.processors.hadoop.HadoopSerialization;
import org.apache.ignite.internal.processors.hadoop.HadoopTaskContext;
import org.apache.ignite.internal.processors.hadoop.HadoopTaskInput;
import org.apache.ignite.internal.processors.hadoop.io.PartiallyOffheapRawComparatorEx;
import org.apache.ignite.internal.util.GridLongList;
import org.apache.ignite.internal.util.GridRandom;
import org.apache.ignite.internal.util.offheap.unsafe.GridUnsafeMemory;
import org.apache.ignite.internal.util.typedef.internal.A;
import org.jetbrains.annotations.Nullable;
/**
* Skip list.
*/
public class HadoopSkipList extends HadoopMultimapBase {
/** */
private static final int HEADS_SIZE = 24 + 33 * 8; // Offset + max level is from 0 to 32 inclusive.
/** Top level. */
private final AtomicInteger topLevel = new AtomicInteger(-1);
/** Heads for all the lists. */
private final long heads;
/** */
private final AtomicBoolean visitGuard = new AtomicBoolean();
/**
* @param jobInfo Job info.
* @param mem Memory.
*/
public HadoopSkipList(HadoopJobInfo jobInfo, GridUnsafeMemory mem) {
super(jobInfo, mem);
heads = mem.allocate(HEADS_SIZE, true);
}
/** {@inheritDoc} */
@Override public void close() {
super.close();
mem.release(heads, HEADS_SIZE);
}
/** {@inheritDoc} */
@Override public boolean visit(boolean ignoreLastVisited, Visitor v) throws IgniteCheckedException {
if (!visitGuard.compareAndSet(false, true))
return false;
for (long meta = nextMeta(heads, 0); meta != 0L; meta = nextMeta(meta, 0)) {
long valPtr = value(meta);
long lastVisited = ignoreLastVisited ? 0 : lastVisitedValue(meta);
if (valPtr != lastVisited) {
long k = key(meta);
v.onKey(k + 4, keySize(k));
lastVisitedValue(meta, valPtr); // Set it to the first value in chain.
do {
v.onValue(valPtr + 12, valueSize(valPtr));
valPtr = nextValue(valPtr);
}
while (valPtr != lastVisited);
}
}
visitGuard.lazySet(false);
return true;
}
/** {@inheritDoc} */
@Override public Adder startAdding(HadoopTaskContext ctx) throws IgniteCheckedException {
return new AdderImpl(ctx);
}
/** {@inheritDoc} */
@Override public HadoopTaskInput input(HadoopTaskContext taskCtx) throws IgniteCheckedException {
Input in = new Input(taskCtx);
Comparator<Object> grpCmp = taskCtx.groupComparator();
if (grpCmp != null)
return new GroupedInput(grpCmp, in);
return in;
}
/**
* @param meta Meta pointer.
* @return Key pointer.
*/
private long key(long meta) {
return mem.readLong(meta);
}
/**
* @param meta Meta pointer.
* @param key Key pointer.
*/
private void key(long meta, long key) {
mem.writeLong(meta, key);
}
/**
* @param meta Meta pointer.
* @return Value pointer.
*/
private long value(long meta) {
return mem.readLongVolatile(meta + 8);
}
/**
* @param meta Meta pointer.
* @param valPtr Value pointer.
*/
private void value(long meta, long valPtr) {
mem.writeLongVolatile(meta + 8, valPtr);
}
/**
* @param meta Meta pointer.
* @param oldValPtr Old first value pointer.
* @param newValPtr New first value pointer.
* @return {@code true} If operation succeeded.
*/
private boolean casValue(long meta, long oldValPtr, long newValPtr) {
return mem.casLong(meta + 8, oldValPtr, newValPtr);
}
/**
* @param meta Meta pointer.
* @return Last visited value pointer.
*/
private long lastVisitedValue(long meta) {
return mem.readLong(meta + 16);
}
/**
* @param meta Meta pointer.
* @param valPtr Last visited value pointer.
*/
private void lastVisitedValue(long meta, long valPtr) {
mem.writeLong(meta + 16, valPtr);
}
/**
* @param meta Meta pointer.
* @param level Level.
* @return Next meta pointer.
*/
private long nextMeta(long meta, int level) {
assert meta > 0 : meta;
return mem.readLongVolatile(meta + 24 + 8 * level);
}
/**
* @param meta Meta pointer.
* @param level Level.
* @param oldNext Old next meta pointer.
* @param newNext New next meta pointer.
* @return {@code true} If operation succeeded.
*/
private boolean casNextMeta(long meta, int level, long oldNext, long newNext) {
assert meta > 0 : meta;
return mem.casLong(meta + 24 + 8 * level, oldNext, newNext);
}
/**
* @param meta Meta pointer.
* @param level Level.
* @param nextMeta Next meta.
*/
private void nextMeta(long meta, int level, long nextMeta) {
assert meta != 0;
mem.writeLong(meta + 24 + 8 * level, nextMeta);
}
/**
* @param keyPtr Key pointer.
* @return Key size.
*/
private int keySize(long keyPtr) {
return mem.readInt(keyPtr);
}
/**
* @param keyPtr Key pointer.
* @param keySize Key size.
*/
private void keySize(long keyPtr, int keySize) {
mem.writeInt(keyPtr, keySize);
}
/**
* @param rnd Random.
* @return Next level.
*/
public static int randomLevel(Random rnd) {
int x = rnd.nextInt();
int level = 0;
while ((x & 1) != 0) { // Count sequential 1 bits.
level++;
x >>>= 1;
}
return level;
}
/**
* Reader.
*/
private class Reader extends ReaderBase {
/**
* @param ser Serialization.
*/
protected Reader(HadoopSerialization ser) {
super(ser);
}
/**
* @param meta Meta pointer.
* @return Key.
*/
public Object readKey(long meta) {
assert meta > 0 : meta;
long k = key(meta);
try {
return read(k + 4, keySize(k));
}
catch (IgniteCheckedException e) {
throw new IgniteException(e);
}
}
}
/**
* Adder.
*/
private class AdderImpl extends AdderBase {
/** */
private final Comparator<Object> cmp;
/** */
private final PartiallyOffheapRawComparatorEx<Object> partialRawCmp;
/** */
private final Random rnd = new GridRandom();
/** */
private final GridLongList stack = new GridLongList(16);
/** */
private final Reader keyReader;
/**
* @param ctx Task context.
* @throws IgniteCheckedException If failed.
*/
protected AdderImpl(HadoopTaskContext ctx) throws IgniteCheckedException {
super(ctx);
keyReader = new Reader(keySer);
cmp = ctx.sortComparator();
partialRawCmp = ctx.partialRawSortComparator();
}
/** {@inheritDoc} */
@Override public void write(Object key, Object val) throws IgniteCheckedException {
A.notNull(val, "val");
add(key, val);
}
/** {@inheritDoc} */
@Override public Key addKey(DataInput in, @Nullable Key reuse) throws IgniteCheckedException {
KeyImpl k = reuse == null ? new KeyImpl() : (KeyImpl)reuse;
k.tmpKey = keySer.read(in, k.tmpKey);
k.meta = add(k.tmpKey, null);
return k;
}
/**
* @param key Key.
* @param val Value.
* @param level Level.
* @return Meta pointer.
*/
private long createMeta(long key, long val, int level) {
int size = 32 + 8 * level;
long meta = allocate(size);
key(meta, key);
value(meta, val);
lastVisitedValue(meta, 0L);
for (int i = 32; i < size; i += 8) // Fill with 0.
mem.writeLong(meta + i, 0L);
return meta;
}
/**
* @param key Key.
* @return Pointer.
* @throws IgniteCheckedException If failed.
*/
private long writeKey(Object key) throws IgniteCheckedException {
long keyPtr = write(4, key, keySer);
int keySize = writtenSize() - 4;
keySize(keyPtr, keySize);
return keyPtr;
}
/**
* @param prevMeta Previous meta.
* @param meta Next meta.
*/
private void stackPush(long prevMeta, long meta) {
stack.add(prevMeta);
stack.add(meta);
}
/**
* Drops last remembered frame from the stack.
*/
private void stackPop() {
stack.pop(2);
}
/**
* @param key Key.
* @param val Value.
* @return Meta pointer.
* @throws IgniteCheckedException If failed.
*/
private long add(Object key, @Nullable Object val) throws IgniteCheckedException {
assert key != null;
stack.clear();
long valPtr = 0;
if (val != null) { // Write value.
valPtr = write(12, val, valSer);
int valSize = writtenSize() - 12;
nextValue(valPtr, 0);
valueSize(valPtr, valSize);
}
long keyPtr = 0;
long newMeta = 0;
int newMetaLevel = -1;
long prevMeta = heads;
int level = topLevel.get();
long meta = level < 0 ? 0 : nextMeta(heads, level);
for (;;) {
if (level < 0) { // We did not find our key, trying to add new meta.
if (keyPtr == 0) { // Write key and create meta only once.
keyPtr = writeKey(key);
newMetaLevel = randomLevel(rnd);
newMeta = createMeta(keyPtr, valPtr, newMetaLevel);
}
nextMeta(newMeta, 0, meta); // Set next to new meta before publishing.
if (casNextMeta(prevMeta, 0, meta, newMeta)) { // New key was added successfully.
laceUp(key, newMeta, newMetaLevel);
return newMeta;
}
else { // Add failed, need to check out what was added by another thread.
meta = nextMeta(prevMeta, level = 0);
stackPop();
}
}
int cmpRes = cmp(key, meta);
if (cmpRes == 0) { // Key found.
if (newMeta != 0) // Deallocate if we've allocated something.
localDeallocate(keyPtr);
if (valPtr == 0) // Only key needs to be added.
return meta;
for (;;) { // Add value for the key found.
long nextVal = value(meta);
nextValue(valPtr, nextVal);
if (casValue(meta, nextVal, valPtr))
return meta;
}
}
assert cmpRes != 0;
if (cmpRes > 0) { // Go right.
prevMeta = meta;
meta = nextMeta(meta, level);
if (meta != 0) // If nothing to the right then go down.
continue;
}
while (--level >= 0) { // Go down.
stackPush(prevMeta, meta); // Remember the path.
long nextMeta = nextMeta(prevMeta, level);
if (nextMeta != meta) { // If the meta is the same as on upper level go deeper.
meta = nextMeta;
assert meta != 0;
break;
}
}
}
}
/**
* @param key Key.
* @param meta Meta pointer.
* @return Comparison result.
*/
@SuppressWarnings("unchecked")
private int cmp(Object key, long meta) {
assert meta != 0;
if (partialRawCmp != null) {
long keyPtr = key(meta);
int keySize = keySize(keyPtr);
return partialRawCmp.compare(key, keyPtr + 4, keySize);
}
else
return cmp.compare(key, keyReader.readKey(meta));
}
/**
* Adds appropriate index links between metas.
*
* @param newMeta Just added meta.
* @param newMetaLevel New level.
*/
private void laceUp(Object key, long newMeta, int newMetaLevel) {
for (int level = 1; level <= newMetaLevel; level++) { // Go from the bottom up.
long prevMeta = heads;
long meta = 0;
if (!stack.isEmpty()) { // Get the path back.
meta = stack.remove();
prevMeta = stack.remove();
}
for (;;) {
nextMeta(newMeta, level, meta);
if (casNextMeta(prevMeta, level, meta, newMeta))
break;
long oldMeta = meta;
meta = nextMeta(prevMeta, level); // Reread meta.
for (;;) {
int cmpRes = cmp(key, meta);
if (cmpRes > 0) { // Go right.
prevMeta = meta;
meta = nextMeta(prevMeta, level);
if (meta != oldMeta) // Old meta already known to be greater than ours or is 0.
continue;
}
assert cmpRes != 0; // Two different metas with equal keys must be impossible.
break; // Retry cas.
}
}
}
if (!stack.isEmpty())
return; // Our level already lower than top.
for (;;) { // Raise top level.
int top = topLevel.get();
if (newMetaLevel <= top || topLevel.compareAndSet(top, newMetaLevel))
break;
}
}
/**
* Key.
*/
private class KeyImpl implements Key {
/** */
private long meta;
/** */
private Object tmpKey;
/**
* @return Meta pointer for the key.
*/
public long address() {
return meta;
}
/**
* @param val Value.
*/
@Override public void add(Value val) {
int size = val.size();
long valPtr = allocate(size + 12);
val.copyTo(valPtr + 12);
valueSize(valPtr, size);
long nextVal;
do {
nextVal = value(meta);
nextValue(valPtr, nextVal);
}
while(!casValue(meta, nextVal, valPtr));
}
}
}
/**
* Task input.
*/
private class Input implements HadoopTaskInput {
/** */
private long metaPtr = heads;
/** */
private final Reader keyReader;
/** */
private final Reader valReader;
/**
* @param taskCtx Task context.
* @throws IgniteCheckedException If failed.
*/
private Input(HadoopTaskContext taskCtx) throws IgniteCheckedException {
keyReader = new Reader(taskCtx.keySerialization());
valReader = new Reader(taskCtx.valueSerialization());
}
/** {@inheritDoc} */
@Override public boolean next() {
metaPtr = nextMeta(metaPtr, 0);
return metaPtr != 0;
}
/** {@inheritDoc} */
@Override public Object key() {
return keyReader.readKey(metaPtr);
}
/** {@inheritDoc} */
@Override public Iterator<?> values() {
return new ValueIterator(value(metaPtr), valReader);
}
/** {@inheritDoc} */
@Override public void close() throws IgniteCheckedException {
keyReader.close();
valReader.close();
}
}
/**
* Grouped input using grouping comparator.
*/
private class GroupedInput implements HadoopTaskInput {
/** */
private final Comparator<Object> grpCmp;
/** */
private final Input in;
/** */
private Object prevKey;
/** */
private Object nextKey;
/** */
private final GridLongList vals = new GridLongList();
/**
* @param grpCmp Grouping comparator.
* @param in Input.
*/
private GroupedInput(Comparator<Object> grpCmp, Input in) {
this.grpCmp = grpCmp;
this.in = in;
}
/** {@inheritDoc} */
@Override public boolean next() {
if (prevKey == null) { // First call.
if (!in.next())
return false;
prevKey = in.key();
assert prevKey != null;
in.keyReader.resetReusedObject(null); // We need 2 instances of key object for comparison.
vals.add(value(in.metaPtr));
}
else {
if (in.metaPtr == 0) // We reached the end of the input.
return false;
vals.clear();
vals.add(value(in.metaPtr));
in.keyReader.resetReusedObject(prevKey); // Switch key instances.
prevKey = nextKey;
}
while (in.next()) { // Fill with head value pointers with equal keys.
if (grpCmp.compare(prevKey, nextKey = in.key()) == 0)
vals.add(value(in.metaPtr));
else
break;
}
assert !vals.isEmpty();
return true;
}
/** {@inheritDoc} */
@Override public Object key() {
return prevKey;
}
/** {@inheritDoc} */
@Override public Iterator<?> values() {
assert !vals.isEmpty();
final ValueIterator valIter = new ValueIterator(vals.get(0), in.valReader);
return new Iterator<Object>() {
/** */
private int idx;
@Override public boolean hasNext() {
if (!valIter.hasNext()) {
if (++idx == vals.size())
return false;
valIter.head(vals.get(idx));
assert valIter.hasNext();
}
return true;
}
@Override public Object next() {
return valIter.next();
}
@Override public void remove() {
valIter.remove();
}
};
}
/** {@inheritDoc} */
@Override public void close() throws IgniteCheckedException {
in.close();
}
}
}