org.jgroups.protocols.COUNTER Maven / Gradle / Ivy
Go to download
This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including
all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and
JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up
with different versions on classes on the class path).
The newest version!
package org.jgroups.protocols;
import org.jgroups.*;
import org.jgroups.annotations.*;
import org.jgroups.blocks.atomic.*;
import org.jgroups.conf.AttributeType;
import org.jgroups.stack.Protocol;
import org.jgroups.util.Bits;
import org.jgroups.util.*;
import java.io.*;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* Protocol which is used by {@link org.jgroups.blocks.atomic.CounterService} to provide a distributed atomic counter
* @author Bela Ban
* @since 3.0.0
*/
@MBean(description="Protocol to maintain distributed atomic counters")
public class COUNTER extends Protocol {
private static final AtomicLong REQUEST_ID_GENERATOR = new AtomicLong();
//enum value() method is expensive since it creates a copy of the internal array every time it is invoked.
//we can cache it since we don't change it.
private static final RequestType[] REQUEST_TYPES_CACHED = RequestType.values();
private static final ResponseType[] RESPONSE_TYPES_CACHED = ResponseType.values();
@Property(description="Bypasses message bundling if true")
protected boolean bypass_bundling;
@Property(description="Request timeouts (in ms). If the timeout elapses, a TimeoutException will be thrown",
type=AttributeType.TIME)
protected long timeout=60000;
@Property(description="Number of milliseconds to wait for reconciliation responses from all current members",
type=AttributeType.TIME)
protected long reconciliation_timeout=10000;
@Property(description="Number of backup coordinators. Modifications are asynchronously sent to all backup coordinators")
protected int num_backups=1;
/** Set to true during reconciliation process, will cause all requests to be discarded */
protected boolean discard_requests=false;
protected View view;
/** The address of the cluster coordinator. Updated on view changes */
protected Address coord;
/** Backup coordinators. Only created if num_backups > 0 and coord=true */
protected List backup_coords=null;
@GuardedBy("this")
protected Future> reconciliation_task_future;
protected ReconciliationTask reconciliation_task;
// server side counters
protected final Map counters = Util.createConcurrentMap(20);
// (client side) pending requests
protected final Map> pending_requests = Util.createConcurrentMap(20);
protected static final byte REQUEST = 1;
protected static final byte RESPONSE = 2;
private TP transport;
protected enum RequestType {
GET_OR_CREATE {
@Override
Request create() {
return new GetOrCreateRequest();
}
},
DELETE {
@Override
Request create() {
return new DeleteRequest();
}
},
SET {
@Override
Request create() {
return new SetRequest();
}
},
COMPARE_AND_SET {
@Override
Request create() {
return new CompareAndSetRequest();
}
},
ADD_AND_GET {
@Override
Request create() {
return new AddAndGetRequest();
}
},
UPDATE {
@Override
Request create() {
return new UpdateRequest();
}
},
RECONCILE {
@Override
Request create() {
return new ReconcileRequest();
}
},
RESEND_PENDING_REQUESTS {
@Override
Request create() {
return new ResendPendingRequests();
}
},
UPDATE_FUNCTION {
@Override
Request create() {
return new UpdateFunctionRequest<>();
}
};
abstract Request create();
}
protected enum ResponseType {
VALUE {
@Override
Response create() {
return new ValueResponse();
}
},
EXCEPTION{
@Override
Response create() {
return new ExceptionResponse();
}
},
RECONCILE {
@Override
Response create() {
return new ReconcileResponse();
}
},
UPDATE_FUNCTION {
@Override
Response> create() {
return new UpdateFunctionResponse<>();
}
};
abstract Response> create();
}
public boolean getBypassBundling() {
return bypass_bundling;
}
public COUNTER setBypassBundling(boolean bypass_bundling) {
this.bypass_bundling=bypass_bundling;
return this;
}
public int getNumberOfBackups() {
return num_backups;
}
public COUNTER setNumberOfBackups(int num_backups) {
this.num_backups = num_backups;
return this;
}
@ManagedAttribute
public String getView() {
return view != null? view.toString() : null;
}
@ManagedAttribute(description="List of the backup coordinator (null if num_backups <= 0")
public String getBackupCoords() {
return backup_coords != null? backup_coords.toString() : "null";
}
@Override
public void init() throws Exception {
super.init();
transport = getTransport();
}
@Deprecated
public Counter getOrCreateCounter(String name, long initial_value) {
CounterImpl counter = CompletableFutures.join(doGetOrCreateCounter(name, initial_value));
return counter.sync;
}
public CompletionStage getOrCreateAsyncCounter(String name, long initial_value) {
return doGetOrCreateCounter(name, initial_value).thenApply(Function.identity());
}
private CompletionStage doGetOrCreateCounter(String name, long initial_value) {
Objects.requireNonNull(name);
if(local_addr == null)
throw new IllegalStateException("the channel needs to be connected before creating or getting a counter");
// is it safe?
if (counters.containsKey(name)) {
// if the counter exists, we do not need to send a request to the coordinator, right?
return CompletableFuture.completedFuture(new CounterImpl(name));
}
Owner owner=getOwner();
GetOrCreateRequest req=new GetOrCreateRequest(owner, name, initial_value);
CompletableFuture rsp = sendRequestToCoordinator(owner, req);
return rsp.thenApply(aLong -> new CounterImpl(name));
}
/** Sent asynchronously - we don't wait for an ack */
public void deleteCounter(String name) {
Owner owner=getOwner();
Request req=new DeleteRequest(owner, name);
sendRequest(coord, req);
if(!local_addr.equals(coord))
counters.remove(name);
}
public Object down(Event evt) {
switch(evt.getType()) {
case Event.VIEW_CHANGE:
handleView(evt.arg());
break;
}
return down_prot.down(evt);
}
public Object up(Event evt) {
if (evt.getType() == Event.VIEW_CHANGE) {
handleView(evt.getArg());
}
return up_prot.up(evt);
}
public Object up(Message msg) {
CounterHeader hdr = msg.getHeader(id);
if (hdr == null)
return up_prot.up(msg);
try {
assert msg.hasArray();
DataInput in = new DataInputStream(new ByteArrayInputStream(msg.getArray(), msg.getOffset(), msg.getLength()));
switch (in.readByte()) {
case REQUEST:
Request req = requestFromDataInput(in);
if (log.isTraceEnabled())
log.trace("[" + local_addr + "] <-- [" + msg.getSrc() + "] " + req);
req.execute(this, msg.getSrc());
break;
case RESPONSE:
Response> rsp = responseFromDataInput(in);
if (log.isTraceEnabled())
log.trace("[" + local_addr + "] <-- [" + msg.getSrc() + "] " + rsp);
handleResponse(rsp, msg.getSrc());
break;
default:
log.error(Util.getMessage("ReceivedObjectIsNeitherARequestNorAResponse"));
break;
}
} catch (Exception ex) {
log.error(Util.getMessage("FailedHandlingMessage"), ex);
}
return null;
}
protected VersionedValue getCounter(String name) {
VersionedValue val=counters.get(name);
if(val == null)
throw new IllegalStateException("counter \"" + name + "\" not found");
return val;
}
protected void handleResponse(Response rsp, Address sender) {
if(rsp instanceof ReconcileResponse) {
handleReconcileResponse((ReconcileResponse) rsp, sender);
return;
}
RequestCompletableFuture cf=pending_requests.remove(rsp.getOwner());
if(cf == null) {
log.warn("response for " + rsp.getOwner() + " didn't have an entry");
return;
}
rsp.complete(cf);
}
private void handleReconcileResponse(ReconcileResponse rsp, Address sender) {
if(log.isTraceEnabled() && rsp.names != null && rsp.names.length > 0)
log.trace("[" + local_addr + "] <-- [" + sender + "] RECONCILE-RSP: " + dump(rsp.names, rsp.values, rsp.versions));
if(reconciliation_task != null)
reconciliation_task.add(rsp, sender);
}
@ManagedOperation(description="Dumps all counters")
public String printCounters() {
StringBuilder sb=new StringBuilder();
for(Map.Entry entry: counters.entrySet())
sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n");
return sb.toString();
}
@ManagedOperation(description="Dumps all pending requests")
public String dumpPendingRequests() {
StringBuilder sb=new StringBuilder();
for(RequestCompletableFuture> cf: pending_requests.values()) {
Request tmp=cf.getRequest();
sb.append(tmp).append('(').append(tmp.getClass().getCanonicalName()).append(") ");
}
return sb.toString();
}
protected void handleView(View view) {
this.view=view;
if(log.isDebugEnabled())
log.debug("view=" + view);
List members=view.getMembers();
Address old_coord=coord;
if(!members.isEmpty())
coord=members.get(0);
if(Objects.equals(coord, local_addr)) {
List old_backups=backup_coords != null? new ArrayList<>(backup_coords) : null;
backup_coords=new CopyOnWriteArrayList<>(Util.pickNext(members, local_addr, num_backups));
// send the current values to all *new* backups
List new_backups=Util.newElements(old_backups,backup_coords);
for(Address new_backup: new_backups) {
for(Map.Entry entry: counters.entrySet()) {
UpdateRequest update=new UpdateRequest(entry.getKey(), entry.getValue().value, entry.getValue().version);
sendRequest(new_backup, update);
}
}
}
else
backup_coords=null;
if(old_coord != null && coord != null && !old_coord.equals(coord) && local_addr.equals(coord)) {
discard_requests=true; // set to false when the task is done
startReconciliationTask();
}
}
protected Owner getOwner() {
return new Owner(local_addr, REQUEST_ID_GENERATOR.incrementAndGet());
}
protected void updateBackups(String name, long[] versionedValue) {
if (backup_coords == null || backup_coords.isEmpty()) {
return;
}
Request req=new UpdateRequest(name, versionedValue[0], versionedValue[1]);
try {
ByteArray buffer=requestToBuffer(req);
for(Address dst: backup_coords) {
logSending(dst, req);
send(dst, buffer);
}
}
catch(Exception ex) {
log.error(Util.getMessage("FailedSending") + req + " to backup coordinator(s):" + ex);
}
}
protected void sendRequest(Address dest, Request req) {
try {
ByteArray buffer=requestToBuffer(req);
logSending(dest, req);
send(dest, buffer);
}
catch(Exception ex) {
log.error(Util.getMessage("FailedSending") + req + " request: " + ex);
}
}
protected void sendResponse(Address dest, Response> rsp) {
try {
ByteArray buffer=responseToBuffer(rsp);
logSending(dest, rsp);
send(dest, buffer);
}
catch(Exception ex) {
log.error(Util.getMessage("FailedSending") + rsp + " message to " + dest + ": " + ex);
}
}
protected void send(Address dest, ByteArray buffer) {
try {
Message rsp_msg=new BytesMessage(dest, buffer).putHeader(id, new CounterHeader());
if(bypass_bundling)
rsp_msg.setFlag(Message.Flag.DONT_BUNDLE);
down_prot.down(rsp_msg);
}
catch(Exception ex) {
log.error(Util.getMessage("FailedSendingMessageTo") + dest + ": " + ex);
}
}
private void logSending(Address dst, Object data) {
if(log.isTraceEnabled())
log.trace("[" + local_addr + "] --> [" + (dst == null? "ALL" : dst) + "]: " + data);
}
protected void sendCounterNotFoundExceptionResponse(Address dest, Owner owner, String counter_name) {
Response> rsp=new ExceptionResponse(owner, "counter \"" + counter_name + "\" not found");
sendResponse(dest, rsp);
}
private T updateCounter(ResponseData responseData) {
if(!coord.equals(local_addr)) {
counters.compute(responseData.counterName, responseData);
}
return responseData.returnValue;
}
protected static ByteArray requestToBuffer(Request req) throws Exception {
return streamableToBuffer(REQUEST,(byte)req.getRequestType().ordinal(), req);
}
protected static ByteArray responseToBuffer(Response> rsp) throws Exception {
return streamableToBuffer(RESPONSE,(byte)rsp.getResponseType().ordinal(), rsp);
}
protected static ByteArray streamableToBuffer(byte req_or_rsp, byte type, Streamable obj) throws Exception {
int expected_size=obj instanceof SizeStreamable? ((SizeStreamable)obj).serializedSize() : 100;
ByteArrayDataOutputStream out=new ByteArrayDataOutputStream(expected_size);
out.writeByte(req_or_rsp);
out.writeByte(type);
obj.writeTo(out);
return new ByteArray(out.buffer(), 0, out.position());
}
protected static Request requestFromDataInput(DataInput in) throws Exception {
Request retval=REQUEST_TYPES_CACHED[in.readByte()].create();
retval.readFrom(in);
return retval;
}
protected static Response> responseFromDataInput(DataInput in) throws Exception {
Response> retval=RESPONSE_TYPES_CACHED[in.readByte()].create();
retval.readFrom(in);
return retval;
}
protected synchronized void startReconciliationTask() {
if(reconciliation_task_future == null || reconciliation_task_future.isDone()) {
reconciliation_task=new ReconciliationTask();
reconciliation_task_future=transport.getTimer().schedule(reconciliation_task, 0, TimeUnit.MILLISECONDS);
}
}
protected synchronized void stopReconciliationTask() {
if(reconciliation_task_future != null) {
reconciliation_task_future.cancel(true);
if(reconciliation_task != null)
reconciliation_task.cancel();
reconciliation_task_future=null;
}
}
protected static void writeReconciliation(DataOutput out, String[] names, long[] values, long[] versions) throws IOException {
if(names == null) {
out.writeInt(0);
return;
}
out.writeInt(names.length);
for(String name: names)
Bits.writeString(name,out);
for(long value: values)
Bits.writeLongCompressed(value, out);
for(long version: versions)
Bits.writeLongCompressed(version, out);
}
protected static String[] readReconciliationNames(DataInput in, int len) throws IOException {
String[] retval=new String[len];
for(int i=0; i < len; i++)
retval[i]=Bits.readString(in);
return retval;
}
protected static long[] readReconciliationLongs(DataInput in, int len) throws IOException {
long[] retval=new long[len];
for(int i=0; i < len; i++)
retval[i]=Bits.readLongCompressed(in);
return retval;
}
protected static String dump(String[] names, long[] values, long[] versions) {
StringBuilder sb=new StringBuilder();
if(names != null) {
for(int i=0; i < names.length; i++) {
sb.append(names[i]).append(": ").append(values[i]).append(" (").append(versions[i]).append(")\n");
}
}
return sb.toString();
}
protected class CounterImpl implements AsyncCounter {
protected final String name;
final SyncCounterImpl sync;
protected CounterImpl(String name) {
this.name = name;
this.sync = new SyncCounterImpl(this);
}
public String getName() {
return name;
}
@Override
public CompletableFuture set(long new_value) {
if(local_addr.equals(coord)) {
VersionedValue val=getCounter(name);
long[] result = val.set(new_value);
updateBackups(name, result);
return CompletableFutures.completedNull();
}
Owner owner=getOwner();
Request req=new SetRequest(owner, name, new_value);
return sendRequestToCoordinator(owner, req).thenAccept(CompletableFutures.voidConsumer());
}
@Override
public CompletableFuture compareAndSwap(long expect, long update) {
if(local_addr.equals(coord)) {
VersionedValue val=getCounter(name);
long retval=val.compareAndSwap(expect, update)[0];
updateBackups(name, val.snapshot());
return CompletableFuture.completedFuture(retval);
}
Owner owner=getOwner();
Request req=new CompareAndSetRequest(owner, name, expect, update);
return sendRequestToCoordinator(owner, req);
}
@Override
public CompletableFuture addAndGet(long delta) {
if(local_addr.equals(coord)) {
VersionedValue val=getCounter(name);
long[] result=val.addAndGet(delta);
if(delta != 0) // get() calls addAndGet(0): in this case,we don't want replication traffic
updateBackups(name, result);
return CompletableFuture.completedFuture(result[0]);
}
Owner owner=getOwner();
Request req=new AddAndGetRequest(owner, name, delta);
return sendRequestToCoordinator(owner, req);
}
@Override
public CompletionStage update(CounterFunction updateFunction) {
if(local_addr.equals(coord)) {
try {
VersionedValue val = getCounter(name);
UpdateResult res = val.update(updateFunction);
if (res.updated) {
updateBackups(name, res.snapshot);
}
return CompletableFuture.completedFuture(res.result);
} catch (Throwable t) {
return CompletableFuture.failedFuture(t);
}
}
Owner owner = getOwner();
UpdateFunctionRequest req = new UpdateFunctionRequest<>(owner, name, updateFunction);
return sendRequestToCoordinator(owner, req);
}
@Override
public SyncCounter sync() {
return sync;
}
@Override
public String toString() {
VersionedValue val=counters.get(name);
return val != null? val.toString() : "n/a";
}
}
private static class SyncCounterImpl implements Counter {
private final AsyncCounter counter;
private SyncCounterImpl(AsyncCounter counter) {
this.counter = counter;
}
@Override
public String getName() {
return counter.getName();
}
@Override
public long get() {
return CompletableFutures.join(counter.get());
}
@Override
public void set(long new_value) {
CompletableFutures.join(counter.set(new_value));
}
@Override
public long compareAndSwap(long expect, long update) {
return CompletableFutures.join(counter.compareAndSwap(expect, update));
}
@Override
public long addAndGet(long delta) {
return CompletableFutures.join(counter.addAndGet(delta));
}
@Override
public T update(CounterFunction updateFunction) {
return CompletableFutures.join(counter.update(updateFunction));
}
@Override
public AsyncCounter async() {
return counter;
}
public String toString() {
return counter != null? counter.toString() : null;
}
}
private CompletableFuture sendRequestToCoordinator(Owner owner, Request request) {
RequestCompletableFuture cf = new RequestCompletableFuture<>(request);
pending_requests.put(owner, cf);
sendRequest(coord, request);
return cf.orTimeout(timeout, TimeUnit.MILLISECONDS).thenApply(this::updateCounter);
}
private boolean skipRequest() {
return !local_addr.equals(coord) || discard_requests;
}
protected interface Request extends Streamable {
String getCounterName();
RequestType getRequestType();
void execute(COUNTER protocol, Address sender);
}
protected abstract static class SimpleRequest implements Request {
protected Owner owner;
protected String name;
protected SimpleRequest() {
}
protected SimpleRequest(Owner owner, String name) {
this.owner=owner;
this.name=name;
}
@Override
public void writeTo(DataOutput out) throws IOException {
owner.writeTo(out);
Bits.writeString(name,out);
}
@Override
public void readFrom(DataInput in) throws IOException, ClassNotFoundException {
owner=new Owner();
owner.readFrom(in);
name=Bits.readString(in);
}
public String toString() {
return owner + " [" + name + "]";
}
@Override
public String getCounterName() {
return name;
}
}
protected static class ResendPendingRequests implements Request {
@Override
public void writeTo(DataOutput out) throws IOException {}
@Override
public void readFrom(DataInput in) throws IOException {}
public String toString() {return "ResendPendingRequests";}
@Override
public String getCounterName() {
return null;
}
@Override
public RequestType getRequestType() {
return RequestType.RESEND_PENDING_REQUESTS;
}
@Override
public void execute(COUNTER protocol, Address sender) {
for(RequestCompletableFuture> cf : protocol.pending_requests.values()) {
Request request=cf.getRequest();
protocol.traceResending(request);
protocol.sendRequest(protocol.coord, request);
}
}
}
private void traceResending(Request request) {
if(log.isTraceEnabled())
log.trace("[" + local_addr + "] --> [" + coord + "] resending " + request);
}
protected static class GetOrCreateRequest extends SimpleRequest {
protected long initial_value;
protected GetOrCreateRequest() {}
GetOrCreateRequest(Owner owner, String name, long initial_value) {
super(owner,name);
this.initial_value=initial_value;
}
@Override
public void readFrom(DataInput in) throws IOException, ClassNotFoundException {
super.readFrom(in);
initial_value=Bits.readLongCompressed(in);
}
@Override
public void writeTo(DataOutput out) throws IOException {
super.writeTo(out);
Bits.writeLongCompressed(initial_value, out);
}
@Override
public RequestType getRequestType() {
return RequestType.GET_OR_CREATE;
}
public void execute(COUNTER protocol, Address sender) {
if(protocol.skipRequest())
return;
VersionedValue new_val=new VersionedValue(initial_value);
VersionedValue val=protocol.counters.putIfAbsent(name, new_val);
if(val == null)
val=new_val;
long[] result = val.snapshot();
Response rsp=new ValueResponse(owner, result);
protocol.sendResponse(sender,rsp);
protocol.updateBackups(name, result);
}
}
protected static class DeleteRequest extends SimpleRequest {
protected DeleteRequest() {}
protected DeleteRequest(Owner owner, String name) {
super(owner,name);
}
public String toString() {return "DeleteRequest: " + super.toString();}
@Override
public RequestType getRequestType() {
return RequestType.DELETE;
}
@Override
public void execute(COUNTER protocol, Address sender) {
if(protocol.skipRequest())
return;
protocol.counters.remove(name);
}
}
protected static class AddAndGetRequest extends SetRequest {
protected AddAndGetRequest() {}
protected AddAndGetRequest(Owner owner, String name, long value) {
super(owner,name,value);
}
public String toString() {return "AddAndGetRequest: " + super.toString();}
@Override
public RequestType getRequestType() {
return RequestType.ADD_AND_GET;
}
@Override
public void execute(COUNTER protocol, Address sender) {
if(protocol.skipRequest())
return;
VersionedValue val=protocol.counters.get(name);
if(val == null) {
protocol.sendCounterNotFoundExceptionResponse(sender, owner, name);
return;
}
long[] result=val.addAndGet(value);
Response rsp=new ValueResponse(owner, result);
protocol.sendResponse(sender, rsp);
if (value != 0) {
// value == 0 means it is a counter.get(); no backup update is required.
protocol.updateBackups(name, result);
}
}
}
protected static class SetRequest extends SimpleRequest {
protected long value;
protected SetRequest() {}
protected SetRequest(Owner owner, String name, long value) {
super(owner, name);
this.value=value;
}
@Override
public void readFrom(DataInput in) throws IOException, ClassNotFoundException {
super.readFrom(in);
value=Bits.readLongCompressed(in);
}
@Override
public void writeTo(DataOutput out) throws IOException {
super.writeTo(out);
Bits.writeLongCompressed(value, out);
}
public String toString() {return super.toString() + ": " + value;}
@Override
public RequestType getRequestType() {
return RequestType.SET;
}
@Override
public void execute(COUNTER protocol, Address sender) {
if(protocol.skipRequest())
return;
VersionedValue val=protocol.counters.get(name);
if(val == null) {
protocol.sendCounterNotFoundExceptionResponse(sender, owner, name);
return;
}
long[] result=val.set(value);
Response rsp=new ValueResponse(owner, result);
protocol.sendResponse(sender, rsp);
protocol.updateBackups(name, result);
}
}
protected static class CompareAndSetRequest extends SimpleRequest {
protected long expected, update;
protected CompareAndSetRequest() {}
protected CompareAndSetRequest(Owner owner, String name, long expected, long update) {
super(owner, name);
this.expected=expected;
this.update=update;
}
@Override
public void readFrom(DataInput in) throws IOException, ClassNotFoundException {
super.readFrom(in);
expected=Bits.readLongCompressed(in);
update=Bits.readLongCompressed(in);
}
@Override
public void writeTo(DataOutput out) throws IOException {
super.writeTo(out);
Bits.writeLongCompressed(expected, out);
Bits.writeLongCompressed(update, out);
}
public String toString() {return super.toString() + ", expected=" + expected + ", update=" + update;}
@Override
public RequestType getRequestType() {
return RequestType.COMPARE_AND_SET;
}
@Override
public void execute(COUNTER protocol, Address sender) {
if(protocol.skipRequest())
return;
VersionedValue val=protocol.counters.get(name);
if(val == null) {
protocol.sendCounterNotFoundExceptionResponse(sender, owner, name);
return;
}
long[] result=val.compareAndSwap(expected, update);
Response rsp=new ValueResponse(owner, result);
protocol.sendResponse(sender, rsp);
protocol.updateBackups(name, val.snapshot());
}
}
protected static class ReconcileRequest implements Request {
protected String[] names;
protected long[] values;
protected long[] versions;
protected ReconcileRequest() {}
protected ReconcileRequest(String[] names, long[] values, long[] versions) {
this.names=names;
this.values=values;
this.versions=versions;
}
@Override
public void writeTo(DataOutput out) throws IOException {
writeReconciliation(out, names, values, versions);
}
@Override
public void readFrom(DataInput in) throws IOException {
int len=in.readInt();
names=readReconciliationNames(in, len);
values=readReconciliationLongs(in, len);
versions=readReconciliationLongs(in,len);
}
public String toString() {return "ReconcileRequest (" + names.length + ") entries";}
@Override
public String getCounterName() {
return null;
}
@Override
public RequestType getRequestType() {
return RequestType.RECONCILE;
}
@Override
public void execute(COUNTER protocol, Address sender) {
if(sender.equals(protocol.local_addr)) // we don't need to reply to our own reconciliation request
return;
// return all values except those with lower or same versions than the ones in the ReconcileRequest
Map map=new HashMap<>(protocol.counters);
if(names != null) {
for(int i=0; i < names.length; i++) {
String counter_name=names[i];
long version=versions[i];
VersionedValue my_value=map.get(counter_name);
if(my_value != null && my_value.version <= version)
map.remove(counter_name);
}
}
int len=map.size();
String[] names=new String[len];
long[] values=new long[len];
long[] versions=new long[len];
int index=0;
for(Map.Entry entry: map.entrySet()) {
names[index]=entry.getKey();
values[index]=entry.getValue().value;
versions[index]=entry.getValue().version;
index++;
}
Response rsp=new ReconcileResponse(names, values, versions);
protocol.sendResponse(sender, rsp);
}
}
protected static class UpdateRequest implements Request, BiFunction {
protected String name;
protected long value;
protected long version;
protected UpdateRequest() {}
protected UpdateRequest(String name, long value, long version) {
this.name=name;
this.value=value;
this.version=version;
}
@Override
public void writeTo(DataOutput out) throws IOException {
Bits.writeString(name,out);
Bits.writeLongCompressed(value, out);
Bits.writeLongCompressed(version, out);
}
@Override
public void readFrom(DataInput in) throws IOException {
name=Bits.readString(in);
value=Bits.readLongCompressed(in);
version=Bits.readLongCompressed(in);
}
public String toString() {return "UpdateRequest(" + name + ": "+ value + " (" + version + ")";}
@Override
public String getCounterName() {
return name;
}
@Override
public RequestType getRequestType() {
return RequestType.UPDATE;
}
@Override
public void execute(COUNTER protocol, Address sender) {
protocol.counters.compute(name, this);
}
@Override
public VersionedValue apply(String name, VersionedValue versionedValue) {
if (versionedValue == null) {
versionedValue = new VersionedValue(value, version);
} else {
versionedValue.updateIfBigger(value, version);
}
return versionedValue;
}
}
private static class UpdateFunctionRequest extends SimpleRequest {
CounterFunction updateFunction;
UpdateFunctionRequest() {}
UpdateFunctionRequest(Owner owner, String name, CounterFunction updateFunction) {
super(owner, name);
this.updateFunction = updateFunction;
}
@Override
public RequestType getRequestType() {
return RequestType.UPDATE_FUNCTION;
}
@Override
public void execute(COUNTER protocol, Address sender) {
if(protocol.skipRequest())
return;
VersionedValue val=protocol.counters.get(name);
if(val == null) {
protocol.sendCounterNotFoundExceptionResponse(sender, owner, name);
return;
}
UpdateResult result = val.update(updateFunction);
Response rsp= new UpdateFunctionResponse<>(owner, result);
protocol.sendResponse(sender, rsp);
if (result.updated) {
protocol.updateBackups(name, result.snapshot);
}
}
@Override
public void writeTo(DataOutput out) throws IOException {
super.writeTo(out);
Util.writeGenericStreamable(updateFunction, out);
}
@Override
public void readFrom(DataInput in) throws IOException, ClassNotFoundException {
super.readFrom(in);
updateFunction = Util.readGenericStreamable(in);
}
}
private static abstract class Response implements Streamable {
private Owner owner;
Response() {}
Response(Owner owner) {
this.owner = owner;
}
abstract ResponseType getResponseType();
abstract void complete(RequestCompletableFuture completableFuture);
final Owner getOwner() {
return owner;
}
@Override
public void writeTo(DataOutput out) throws IOException {
owner.writeTo(out);
}
@Override
public void readFrom(DataInput in) throws IOException, ClassNotFoundException {
owner = new Owner();
owner.readFrom(in);
}
}
protected static class ValueResponse extends Response {
protected long result;
protected long version;
protected ValueResponse() {}
ValueResponse(Owner owner, long[] versionedValue) {
this(owner, versionedValue[0], versionedValue[1]);
}
protected ValueResponse(Owner owner, long result, long version) {
super(owner);
this.result=result;
this.version=version;
}
@Override
public void readFrom(DataInput in) throws IOException, ClassNotFoundException {
super.readFrom(in);
result=Bits.readLongCompressed(in);
version=Bits.readLongCompressed(in);
}
@Override
public void writeTo(DataOutput out) throws IOException {
super.writeTo(out);
Bits.writeLongCompressed(result, out);
Bits.writeLongCompressed(version, out);
}
public String toString() {return "ValueResponse(" + result + ")";}
@Override
public ResponseType getResponseType() {
return ResponseType.VALUE;
}
@Override
void complete(RequestCompletableFuture cf) {
cf.requestCompleted(result, version, result);
}
}
protected static class ExceptionResponse extends Response {
protected String error_message;
protected ExceptionResponse() {}
protected ExceptionResponse(Owner owner, String error_message) {
super(owner);
this.error_message=error_message;
}
@Override
public void readFrom(DataInput in) throws IOException, ClassNotFoundException {
super.readFrom(in);
error_message=Bits.readString(in);
}
@Override
public void writeTo(DataOutput out) throws IOException {
super.writeTo(out);
Bits.writeString(error_message,out);
}
public String toString() {return "ExceptionResponse: " + super.toString();}
@Override
public ResponseType getResponseType() {
return ResponseType.EXCEPTION;
}
@Override
void complete(RequestCompletableFuture cf) {
cf.requestFailed(error_message);
}
}
protected static class ReconcileResponse extends Response {
protected String[] names;
protected long[] values;
protected long[] versions;
protected ReconcileResponse() {}
protected ReconcileResponse(String[] names, long[] values, long[] versions) {
this.names=names;
this.values=values;
this.versions=versions;
}
@Override
public void writeTo(DataOutput out) throws IOException {
writeReconciliation(out,names,values,versions);
}
@Override
public void readFrom(DataInput in) throws IOException {
int len=in.readInt();
names=readReconciliationNames(in, len);
values=readReconciliationLongs(in, len);
versions=readReconciliationLongs(in,len);
}
public String toString() {
int num=names != null? names.length : 0;
return "ReconcileResponse (" + num + ") entries";
}
@Override
public ResponseType getResponseType() {
return ResponseType.RECONCILE;
}
@Override
void complete(RequestCompletableFuture cf) {
//no-op
}
}
private static class UpdateFunctionResponse extends Response {
private long value;
private long version;
private T response;
UpdateFunctionResponse() {}
UpdateFunctionResponse(Owner owner, UpdateResult result) {
super(owner);
this.value = result.snapshot[0];
this.version = result.snapshot[1];
this.response = result.result;
}
@Override
public ResponseType getResponseType() {
return ResponseType.UPDATE_FUNCTION;
}
@Override
void complete(RequestCompletableFuture completableFuture) {
completableFuture.requestCompleted(value, version, response);
}
@Override
public void readFrom(DataInput in) throws IOException, ClassNotFoundException {
super.readFrom(in);
response = Util.readGenericStreamable(in);
}
@Override
public void writeTo(DataOutput out) throws IOException {
super.writeTo(out);
Util.writeGenericStreamable(response, out);
}
@Override
public String toString() {
return "UpdateFunctionResponse{" +
"response=" + response +
", value=" + value +
", version=" + version +
", owner=" + getOwner() +
'}';
}
}
public static class CounterHeader extends Header {
public Supplier extends Header> create() {return CounterHeader::new;}
public short getMagicId() {return 74;}
@Override
public int serializedSize() {return 0;}
@Override
public void writeTo(DataOutput out) {}
@Override
public void readFrom(DataInput in) {}
}
protected static class VersionedValue {
protected long value;
protected long version=1;
protected VersionedValue(long value) {
this.value=value;
}
protected VersionedValue(long value, long version) {
this.value=value;
this.version=version;
}
/** num == 0 --> GET */
protected synchronized long[] addAndGet(long num) {
return num == 0? new long[]{value, version} : new long[]{value+=num, ++version};
}
protected synchronized long[] set(long value) {
return new long[]{this.value=value,++version};
}
protected synchronized long[] compareAndSwap(long expected, long update) {
long oldValue = value;
if (oldValue == expected) {
value = update;
++version;
}
return new long[]{oldValue, version};
}
/** Sets the value only if the version argument is greater than the own version */
protected synchronized void updateIfBigger(long value, long version) {
if(version > this.version) {
this.version=version;
this.value=value;
}
}
synchronized long[] snapshot() {
return new long[] {value, version};
}
synchronized UpdateResult update(CounterFunction updateFunction) {
// copy the value to a new instance
// if the function escapes the CounterView, it is working over a copy and does not change the original counter
CounterViewImpl view = new CounterViewImpl(value);
T res = updateFunction.apply(view);
boolean updated = false;
if (value != view.value) {
value = view.value;
++version;
updated = true;
}
return new UpdateResult<>(updated, res, new long[] {value, version});
}
public String toString() {return value + " (version=" + version + ")";}
}
private static class CounterViewImpl implements CounterView {
private long value;
private CounterViewImpl(long value) {
this.value = value;
}
@Override
public long get() {
return value;
}
@Override
public void set(long value) {
this.value = value;
}
}
private static class UpdateResult {
final boolean updated;
final T result;
final long[] snapshot;
private UpdateResult(boolean updated, T result, long[] snapshot) {
this.updated = updated;
this.result = result;
this.snapshot = snapshot;
}
}
protected class ReconciliationTask implements Runnable {
protected ResponseCollector responses;
public void run() {
try {
_run();
}
finally {
discard_requests=false;
}
Request req=new ResendPendingRequests();
sendRequest(null, req);
}
protected void _run() {
Map copy=new HashMap<>(counters);
int len=copy.size();
String[] names=new String[len];
long[] values=new long[len], versions=new long[len];
int index=0;
for(Map.Entry entry: copy.entrySet()) {
names[index]=entry.getKey();
values[index]=entry.getValue().value;
versions[index]=entry.getValue().version;
index++;
}
List targets=new ArrayList<>(view.getMembers());
targets.remove(local_addr);
responses=new ResponseCollector<>(targets); // send to everyone but us
Request req=new ReconcileRequest(names, values, versions);
sendRequest(null, req);
responses.waitForAllResponses(reconciliation_timeout);
Map reconcile_results=responses.getResults();
for(Map.Entry entry: reconcile_results.entrySet()) {
if(entry.getKey().equals(local_addr))
continue;
ReconcileResponse rsp=entry.getValue();
if (rsp == null || rsp.names == null) {
continue;
}
for(int i=0; i < rsp.names.length; i++) {
String counter_name=rsp.names[i];
long version=rsp.versions[i];
long value=rsp.values[i];
VersionedValue my_value=counters.get(counter_name);
if(my_value == null) {
counters.put(counter_name, new VersionedValue(value, version));
continue;
}
my_value.updateIfBigger(value, version);
}
}
}
public void add(ReconcileResponse rsp, Address sender) {
if(responses != null)
responses.add(sender, rsp);
}
protected void cancel() {
if(responses != null)
responses.reset();
}
public String toString() {
return COUNTER.class.getSimpleName() + ": " + getClass().getSimpleName();
}
}
private static class RequestCompletableFuture extends CompletableFuture> {
final Request request;
private RequestCompletableFuture(Request request) {
this.request = request;
}
Request getRequest() {
return request;
}
void requestCompleted(long value, long version, T returnValue) {
this.complete(new ResponseData<>(request.getCounterName(), value, version, returnValue));
}
void requestFailed(String errorMessage) {
this.completeExceptionally(new Throwable(errorMessage));
}
}
private static class ResponseData implements BiFunction {
private final String counterName;
private final long value;
private final long version;
private final T returnValue;
private ResponseData(String counterName, long value, long version, T returnValue) {
this.counterName = counterName;
this.value = value;
this.version = version;
this.returnValue = returnValue;
}
/**
* Updates the VersionedValue if the version is bigger.
*/
@Override
public VersionedValue apply(String s, VersionedValue versionedValue) {
if (versionedValue == null) {
versionedValue = new VersionedValue(value, version);
} else {
versionedValue.updateIfBigger(value, version);
}
return versionedValue;
}
}
}