package org.rakam.kume.service.ringmap;
import org.rakam.kume.transport.OperationContext;
import org.rakam.kume.util.ConsistentHashRing;
import io.netty.util.concurrent.EventExecutor;
import org.rakam.kume.Member;
import org.rakam.kume.ServiceContext;
import org.rakam.kume.transport.Request;
import org.rakam.kume.util.FutureUtil;
import org.rakam.kume.util.ThrowableNioEventLoopGroup;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiFunction;
import java.util.function.Supplier;
import static org.rakam.kume.util.ConsistentHashRing.hash;
public class RingMap<K, V> extends AbstractRingMap<RingMap, Map, K, V> {
private final static int DEFAULT_BUCKET_COUNT = 8;
public RingMap(ServiceContext<RingMap> serviceContext, Supplier<Map> mapSupplier, MapMergePolicy<V> mergePolicy, int bucketCount, int replicationFactor) {
super(serviceContext, mapSupplier, mergePolicy, bucketCount, replicationFactor);
}
public RingMap(ServiceContext<RingMap> serviceContext, MapMergePolicy<V> mergePolicy, int replicationFactor) {
super(serviceContext, ConcurrentHashMap::new, mergePolicy, DEFAULT_BUCKET_COUNT, replicationFactor);
}
public CompletableFuture<V> merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
int bucketId = getRing().findBucketIdFromToken(ConsistentHashRing.hash(key));
ConsistentHashRing.Bucket bucket = getRing().getBucket(bucketId);
FutureUtil.MultipleFutureListener listener = new FutureUtil.MultipleFutureListener((bucket.members.size() / 2) + 1);
for (Member next : bucket.members) {
listener.listen(getContext().ask(next, new MergeMapOperation(key, value, remappingFunction)));
}
return listener.get();
}
protected V mergeLocal(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
Map<K, V> partition = getBucket(getRing().findBucketId(key));
V oldValue = partition.get(key);
V newValue = (oldValue == null) ? value : remappingFunction.apply(oldValue, value);
if (newValue == null) {
partition.remove(key);
return null;
} else {
partition.put(key, newValue);
return newValue;
}
}
/**
* Since we use ConcurrentHashMap, most of the operation may run concurrently.
* However
*
* @param executor
* @param ctx
* @param request
*/
@Override
public void handle(ThrowableNioEventLoopGroup executor, OperationContext ctx, Request request) {
if (request instanceof PartitionRestrictedMapRequest) {
int id = ((PartitionRestrictedMapRequest) request).getPartition(this) % executor.executorCount();
EventExecutor child = executor.getChild(id);
if (child.inEventLoop()) {
try {
request.run(this, ctx);
} catch (Exception e) {
LOGGER.error("error while running throwable code block", e);
}
} else {
child.execute(() -> request.run(this, ctx));
}
} else {
executor.execute(() -> request.run(this, ctx));
}
}
public static class MergeMapOperation<V> implements PartitionRestrictedMapRequest<RingMap, V> {
private final BiFunction remappingFunction;
Object key;
Object value;
public MergeMapOperation(Object key, Object value, BiFunction remappingFunction) {
this.key = key;
this.value = value;
this.remappingFunction = remappingFunction;
}
@Override
public void run(RingMap service, OperationContext ctx) {
ctx.reply(service.mergeLocal(key, value, remappingFunction));
}
@Override
public int getPartition(AbstractRingMap service) {
return service.getPartitionId(service.getRing().findBucketId(key));
}
}
}