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).
package org.jgroups.protocols;
import org.jgroups.*;
import org.jgroups.annotations.*;
import org.jgroups.blocks.atomic.Counter;
import org.jgroups.stack.Protocol;
import org.jgroups.util.*;
import java.io.*;
import java.util.*;
import java.util.concurrent.*;
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 {
@Property(description="Bypasses message bundling if true")
protected boolean bypass_bundling=true;
@Property(description="Request timeouts (in ms). If the timeout elapses, a Timeout (runtime) exception will be thrown")
protected long timeout=60000;
@Property(description="Number of milliseconds to wait for reconciliation responses from all current members")
protected long reconciliation_timeout=10000;
@Property(description="Number of backup coordinators. Modifications are asynchronously sent to all backup coordinators")
protected int num_backups=1;
protected Address local_addr;
/** 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;
protected Future> reconciliation_task_future;
protected ReconciliationTask reconciliation_task;
// server side counters
protected final ConcurrentMap 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;
protected enum RequestType {
GET_OR_CREATE,
DELETE,
SET,
COMPARE_AND_SET,
ADD_AND_GET,
UPDATE,
RECONCILE,
RESEND_PENDING_REQUESTS
}
protected enum ResponseType {
VOID,
GET_OR_CREATE,
BOOLEAN,
VALUE,
EXCEPTION,
RECONCILE
}
protected static RequestType requestToRequestType(Request req) {
if(req instanceof GetOrCreateRequest) return RequestType.GET_OR_CREATE;
if(req instanceof DeleteRequest) return RequestType.DELETE;
if(req instanceof AddAndGetRequest) return RequestType.ADD_AND_GET;
if(req instanceof UpdateRequest) return RequestType.UPDATE;
if(req instanceof SetRequest) return RequestType.SET;
if(req instanceof CompareAndSetRequest) return RequestType.COMPARE_AND_SET;
if(req instanceof ReconcileRequest) return RequestType.RECONCILE;
if(req instanceof ResendPendingRequests) return RequestType.RESEND_PENDING_REQUESTS;
throw new IllegalStateException("request " + req + " cannot be mapped to request type");
}
protected static ResponseType responseToResponseType(Response rsp) {
if(rsp instanceof GetOrCreateResponse) return ResponseType.GET_OR_CREATE;
if(rsp instanceof BooleanResponse) return ResponseType.BOOLEAN;
if(rsp instanceof ValueResponse) return ResponseType.VALUE;
if(rsp instanceof ExceptionResponse) return ResponseType.EXCEPTION;
if(rsp instanceof ReconcileResponse) return ResponseType.RECONCILE;
if(rsp != null) return ResponseType.VOID;
throw new IllegalStateException("response " + rsp + " cannot be mapped to response type");
}
public boolean getBypassBundling() {
return bypass_bundling;
}
public void setBypassBundling(boolean bypass_bundling) {
this.bypass_bundling=bypass_bundling;
}
@ManagedAttribute
public String getAddress() {
return local_addr != null? local_addr.toString() : null;
}
@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";
}
public Counter getOrCreateCounter(String name, long initial_value) {
if(local_addr == null)
throw new IllegalArgumentException("the channel needs to be connected before creating or getting a counter");
Owner owner=getOwner();
GetOrCreateRequest req=new GetOrCreateRequest(owner, name, initial_value);
Promise promise=new Promise<>();
pending_requests.put(owner, new Tuple<>(req, promise));
sendRequest(coord, req);
long[] result=new long[0];
try {
result=promise.getResultWithTimeout(timeout);
long value=result[0], version=result[1];
if(!coord.equals(local_addr))
counters.put(name, new VersionedValue(value, version));
return new CounterImpl(name);
}
catch(TimeoutException e) {
throw new RuntimeException(e);
}
}
/** 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.SET_LOCAL_ADDRESS:
local_addr=evt.getArg();
break;
case Event.VIEW_CHANGE:
handleView(evt.arg());
break;
}
return down_prot.down(evt);
}
public Object up(Event evt) {
switch(evt.getType()) {
case Event.VIEW_CHANGE:
handleView(evt.getArg());
break;
}
return up_prot.up(evt);
}
public Object up(Message msg) {
CounterHeader hdr=msg.getHeader(id);
if(hdr == null)
return up_prot.up(msg);
try {
Object obj=streamableFromBuffer(msg.getRawBuffer(), msg.getOffset(), msg.getLength());
if(log.isTraceEnabled())
log.trace("[" + local_addr + "] <-- [" + msg.getSrc() + "] " + obj);
if(obj instanceof Request) {
handleRequest((Request)obj, msg.getSrc());
}
else if(obj instanceof Response) {
handleResponse((Response)obj, msg.getSrc());
}
else {
log.error(Util.getMessage("ReceivedObjectIsNeitherARequestNorAResponse") + obj);
}
}
catch(Exception ex) {
log.error(Util.getMessage("FailedHandlingMessage"), ex);
}
return null;
}
protected void handleRequest(Request req, Address sender) {
RequestType type=requestToRequestType(req);
switch(type) {
case GET_OR_CREATE:
if(!local_addr.equals(coord) || discard_requests)
return;
GetOrCreateRequest tmp=(GetOrCreateRequest)req;
VersionedValue new_val=new VersionedValue(tmp.initial_value);
VersionedValue val=counters.putIfAbsent(tmp.name, new_val);
if(val == null)
val=new_val;
Response rsp=new GetOrCreateResponse(tmp.owner, val.value, val.version);
sendResponse(sender,rsp);
if(backup_coords != null)
updateBackups(tmp.name, val.value, val.version);
break;
case DELETE:
if(!local_addr.equals(coord) || discard_requests)
return;
counters.remove(((SimpleRequest)req).name);
break;
case SET:
if(!local_addr.equals(coord) || discard_requests)
return;
val=counters.get(((SimpleRequest)req).name);
if(val == null) {
sendCounterNotFoundExceptionResponse(sender, ((SimpleRequest)req).owner, ((SimpleRequest)req).name);
return;
}
long[] result=val.set(((SetRequest)req).value);
rsp=new ValueResponse(((SimpleRequest)req).owner, result[0], result[1]);
sendResponse(sender, rsp);
if(backup_coords != null)
updateBackups(((SimpleRequest)req).name, result[0], result[1]);
break;
case COMPARE_AND_SET:
if(!local_addr.equals(coord) || discard_requests)
return;
val=counters.get(((SimpleRequest)req).name);
if(val == null) {
sendCounterNotFoundExceptionResponse(sender, ((SimpleRequest)req).owner, ((SimpleRequest)req).name);
return;
}
result=val.compareAndSet(((CompareAndSetRequest)req).expected,((CompareAndSetRequest)req).update);
rsp=new ValueResponse(((SimpleRequest)req).owner, result == null? -1 : result[0], result == null? -1 : result[1]);
sendResponse(sender, rsp);
if(backup_coords != null) {
VersionedValue value=counters.get(((SimpleRequest)req).name);
updateBackups(((SimpleRequest)req).name, value.value, value.version);
}
break;
case ADD_AND_GET:
if(!local_addr.equals(coord) || discard_requests)
return;
val=counters.get(((SimpleRequest)req).name);
if(val == null) {
sendCounterNotFoundExceptionResponse(sender, ((SimpleRequest)req).owner, ((SimpleRequest)req).name);
return;
}
result=val.addAndGet(((AddAndGetRequest)req).value);
rsp=new ValueResponse(((SimpleRequest)req).owner, result[0], result[1]);
sendResponse(sender, rsp);
if(backup_coords != null)
updateBackups(((SimpleRequest)req).name, result[0], result[1]);
break;
case UPDATE:
String counter_name=((UpdateRequest)req).name;
long new_value=((UpdateRequest)req).value, new_version=((UpdateRequest)req).version;
VersionedValue current=counters.get(counter_name);
if(current == null)
counters.put(counter_name, new VersionedValue(new_value, new_version));
else {
current.updateIfBigger(new_value, new_version);
}
break;
case RECONCILE:
if(sender.equals(local_addr)) // we don't need to reply to our own reconciliation request
break;
// return all values except those with lower or same versions than the ones in the ReconcileRequest
ReconcileRequest reconcile_req=(ReconcileRequest)req;
Map map=new HashMap<>(counters);
if(reconcile_req.names != null) {
for(int i=0; i < reconcile_req.names.length; i++) {
counter_name=reconcile_req.names[i];
long version=reconcile_req.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++;
}
rsp=new ReconcileResponse(names, values, versions);
sendResponse(sender, rsp);
break;
case RESEND_PENDING_REQUESTS:
for(Tuple tuple: pending_requests.values()) {
Request request=tuple.getVal1();
if(log.isTraceEnabled())
log.trace("[" + local_addr + "] --> [" + coord + "] resending " + request);
sendRequest(coord, request);
}
break;
default:
break;
}
}
protected VersionedValue getCounter(String name) {
VersionedValue val=counters.get(name);
if(val == null)
throw new IllegalStateException("counter \"" + name + "\" not found");
return val;
}
@SuppressWarnings("unchecked")
protected void handleResponse(Response rsp, Address sender) {
if(rsp instanceof ReconcileResponse) {
if(log.isTraceEnabled() && ((ReconcileResponse)rsp).names != null && ((ReconcileResponse)rsp).names.length > 0)
log.trace("[" + local_addr + "] <-- [" + sender + "] RECONCILE-RSP: " +
dump(((ReconcileResponse)rsp).names, ((ReconcileResponse)rsp).values, ((ReconcileResponse)rsp).versions));
if(reconciliation_task != null)
reconciliation_task.add((ReconcileResponse)rsp, sender);
return;
}
Tuple tuple=pending_requests.remove(((SimpleResponse)rsp).owner);
if(tuple == null) {
log.warn("response for " + ((SimpleResponse)rsp).owner + " didn't have an entry");
return;
}
Promise promise=tuple.getVal2();
if(rsp instanceof ValueResponse) {
ValueResponse tmp=(ValueResponse)rsp;
if(tmp.result == -1 && tmp.version == -1)
promise.setResult(null);
else {
long[] result={tmp.result,tmp.version};
promise.setResult(result);
}
}
else if(rsp instanceof BooleanResponse)
promise.setResult(((BooleanResponse)rsp).result);
else if(rsp instanceof ExceptionResponse) {
promise.setResult(new Throwable(((ExceptionResponse)rsp).error_message));
}
else
promise.setResult(null);
}
@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(Tuple tuple: pending_requests.values()) {
Request tmp=tuple.getVal1();
sb.append(tmp + " (" + tmp.getClass().getCanonicalName() + ") ");
}
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, Thread.currentThread().getId());
}
protected void sendRequest(Address dest, Request req) {
try {
Buffer buffer=requestToBuffer(req);
Message msg=new Message(dest, buffer).putHeader(id, new CounterHeader());
if(bypass_bundling)
msg.setFlag(Message.Flag.DONT_BUNDLE);
if(log.isTraceEnabled())
log.trace("[" + local_addr + "] --> [" + (dest == null? "ALL" : dest) + "] " + req);
down_prot.down(msg);
}
catch(Exception ex) {
log.error(Util.getMessage("FailedSending") + req + " request: " + ex);
}
}
protected void sendResponse(Address dest, Response rsp) {
try {
Buffer buffer=responseToBuffer(rsp);
Message rsp_msg=new Message(dest, buffer).putHeader(id, new CounterHeader());
if(bypass_bundling)
rsp_msg.setFlag(Message.Flag.DONT_BUNDLE);
if(log.isTraceEnabled())
log.trace("[" + local_addr + "] --> [" + dest + "] " + rsp);
down_prot.down(rsp_msg);
}
catch(Exception ex) {
log.error(Util.getMessage("FailedSending") + rsp + " message to " + dest + ": " + ex);
}
}
protected void updateBackups(String name, long value, long version) {
Request req=new UpdateRequest(name, value, version);
try {
Buffer buffer=requestToBuffer(req);
if(backup_coords != null && !backup_coords.isEmpty()) {
for(Address backup_coord: backup_coords)
send(backup_coord, buffer);
}
}
catch(Exception ex) {
log.error(Util.getMessage("FailedSending") + req + " to backup coordinator(s):" + ex);
}
}
protected void send(Address dest, Buffer buffer) {
try {
Message rsp_msg=new Message(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);
}
}
protected void sendCounterNotFoundExceptionResponse(Address dest, Owner owner, String counter_name) {
Response rsp=new ExceptionResponse(owner, "counter \"" + counter_name + "\" not found");
sendResponse(dest, rsp);
}
protected static Buffer requestToBuffer(Request req) throws Exception {
return streamableToBuffer(REQUEST,(byte)requestToRequestType(req).ordinal(), req);
}
protected static Buffer responseToBuffer(Response rsp) throws Exception {
return streamableToBuffer(RESPONSE,(byte)responseToResponseType(rsp).ordinal(), rsp);
}
protected static Buffer 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 Buffer(out.buffer(), 0, out.position());
}
protected static Streamable streamableFromBuffer(byte[] buf, int offset, int length) throws Exception {
switch(buf[offset]) {
case REQUEST:
return requestFromBuffer(buf, offset+1, length-1);
case RESPONSE:
return responseFromBuffer(buf, offset+1, length-1);
default:
throw new IllegalArgumentException("type " + buf[offset] + " is invalid (expected Request (1) or RESPONSE (2)");
}
}
protected static final Request requestFromBuffer(byte[] buf, int offset, int length) throws Exception {
ByteArrayInputStream input=new ByteArrayInputStream(buf, offset, length);
DataInputStream in=new DataInputStream(input);
RequestType type=RequestType.values()[in.readByte()];
Request retval=createRequest(type);
retval.readFrom(in);
return retval;
}
protected static Request createRequest(RequestType type) {
switch(type) {
case COMPARE_AND_SET: return new CompareAndSetRequest();
case ADD_AND_GET: return new AddAndGetRequest();
case UPDATE: return new UpdateRequest();
case GET_OR_CREATE: return new GetOrCreateRequest();
case DELETE: return new DeleteRequest();
case SET: return new SetRequest();
case RECONCILE: return new ReconcileRequest();
case RESEND_PENDING_REQUESTS: return new ResendPendingRequests();
default: throw new IllegalArgumentException("failed creating a request from " + type);
}
}
protected static final Response responseFromBuffer(byte[] buf, int offset, int length) throws Exception {
ByteArrayInputStream input=new ByteArrayInputStream(buf, offset, length);
DataInputStream in=new DataInputStream(input);
ResponseType type=ResponseType.values()[in.readByte()];
Response retval=createResponse(type);
retval.readFrom(in);
return retval;
}
protected static Response createResponse(ResponseType type) {
switch(type) {
case VOID: return new SimpleResponse();
case GET_OR_CREATE: return new GetOrCreateResponse();
case BOOLEAN: return new BooleanResponse();
case VALUE: return new ValueResponse();
case EXCEPTION: return new ExceptionResponse();
case RECONCILE: return new ReconcileResponse();
default: throw new IllegalArgumentException("failed creating a response from " + type);
}
}
protected synchronized void startReconciliationTask() {
if(reconciliation_task_future == null || reconciliation_task_future.isDone()) {
reconciliation_task=new ReconciliationTask();
reconciliation_task_future=getTransport().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.writeLong(value, out);
for(long version: versions)
Bits.writeLong(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.readLong(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 Counter {
protected final String name;
protected CounterImpl(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public long get() {
return addAndGet(0);
}
@Override
public void set(long new_value) {
if(local_addr.equals(coord)) {
VersionedValue val=getCounter(name);
val.set(new_value);
if(backup_coords != null)
updateBackups(name, val.value, val.version);
return;
}
Owner owner=getOwner();
Request req=new SetRequest(owner, name, new_value);
Promise promise=new Promise<>();
pending_requests.put(owner, new Tuple<>(req, promise));
sendRequest(coord, req);
Object obj=null;
try {
obj=promise.getResultWithTimeout(timeout);
if(obj instanceof Throwable)
throw new IllegalStateException((Throwable)obj);
long[] result=(long[])obj;
long value=result[0], version=result[1];
if(!coord.equals(local_addr))
counters.put(name, new VersionedValue(value, version));
}
catch(TimeoutException e) {
throw new RuntimeException(e);
}
}
@Override
public boolean compareAndSet(long expect, long update) {
if(local_addr.equals(coord)) {
VersionedValue val=getCounter(name);
boolean retval=val.compareAndSet(expect, update) != null;
if(backup_coords != null)
updateBackups(name, val.value, val.version);
return retval;
}
Owner owner=getOwner();
Request req=new CompareAndSetRequest(owner, name, expect, update);
Promise promise=new Promise<>();
pending_requests.put(owner, new Tuple<>(req, promise));
sendRequest(coord, req);
Object obj=null;
try {
obj=promise.getResultWithTimeout(timeout);
if(obj instanceof Throwable)
throw new IllegalStateException((Throwable)obj);
if(obj == null)
return false;
long[] result=(long[])obj;
long value=result[0], version=result[1];
if(!coord.equals(local_addr))
counters.put(name, new VersionedValue(value, version));
return true;
}
catch(TimeoutException e) {
throw new RuntimeException(e);
}
}
@Override
public long incrementAndGet() {
return addAndGet(1);
}
@Override
public long decrementAndGet() {
return addAndGet(-1);
}
@Override
public long addAndGet(long delta) {
if(local_addr.equals(coord)) {
VersionedValue val=getCounter(name);
long retval=val.addAndGet(delta)[0];
if(backup_coords != null)
updateBackups(name, val.value, val.version);
return retval;
}
Owner owner=getOwner();
Request req=new AddAndGetRequest(owner, name, delta);
Promise promise=new Promise<>();
pending_requests.put(owner, new Tuple<>(req, promise));
sendRequest(coord, req);
Object obj=null;
try {
obj=promise.getResultWithTimeout(timeout);
if(obj instanceof Throwable)
throw new IllegalStateException((Throwable)obj);
long[] result=(long[])obj;
long value=result[0], version=result[1];
if(!coord.equals(local_addr))
counters.put(name, new VersionedValue(value, version));
return value;
}
catch(TimeoutException e) {
throw new RuntimeException(e);
}
}
@Override
public String toString() {
VersionedValue val=counters.get(name);
return val != null? val.toString() : "n/a";
}
}
protected interface Request extends Streamable {
}
protected 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 + "]";
}
}
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";}
}
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.readLong(in);
}
@Override
public void writeTo(DataOutput out) throws IOException {
super.writeTo(out);
Bits.writeLong(initial_value, out);
}
}
protected static class DeleteRequest extends SimpleRequest {
protected DeleteRequest() {}
protected DeleteRequest(Owner owner, String name) {
super(owner,name);
}
public String toString() {return "DeleteRequest: " + super.toString();}
}
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();}
}
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.readLong(in);
}
@Override
public void writeTo(DataOutput out) throws IOException {
super.writeTo(out);
Bits.writeLong(value, out);
}
public String toString() {return super.toString() + ": " + value;}
}
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.readLong(in);
update=Bits.readLong(in);
}
@Override
public void writeTo(DataOutput out) throws IOException {
super.writeTo(out);
Bits.writeLong(expected, out);
Bits.writeLong(update, out);
}
public String toString() {return super.toString() + ", expected=" + expected + ", update=" + update;}
}
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";}
}
protected static class UpdateRequest implements Request {
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.writeLong(value, out);
Bits.writeLong(version, out);
}
@Override
public void readFrom(DataInput in) throws IOException {
name=Bits.readString(in);
value=Bits.readLong(in);
version=Bits.readLong(in);
}
public String toString() {return "UpdateRequest(" + name + ": "+ value + " (" + version + ")";}
}
protected interface Response extends Streamable {}
/** Response without data */
protected static class SimpleResponse implements Response {
protected Owner owner;
protected long version;
protected SimpleResponse() {}
protected SimpleResponse(Owner owner, long version) {
this.owner=owner;
this.version=version;
}
@Override
public void readFrom(DataInput in) throws IOException, ClassNotFoundException {
owner=new Owner();
owner.readFrom(in);
version=Bits.readLong(in);
}
@Override
public void writeTo(DataOutput out) throws IOException {
owner.writeTo(out);
Bits.writeLong(version, out);
}
public String toString() {return "Response";}
}
protected static class BooleanResponse extends SimpleResponse {
protected boolean result;
protected BooleanResponse() {}
protected BooleanResponse(Owner owner, long version, boolean result) {
super(owner, version);
this.result=result;
}
@Override
public void readFrom(DataInput in) throws IOException, ClassNotFoundException {
super.readFrom(in);
result=in.readBoolean();
}
@Override
public void writeTo(DataOutput out) throws IOException {
super.writeTo(out);
out.writeBoolean(result);
}
public String toString() {return "BooleanResponse(" + result + ")";}
}
protected static class ValueResponse extends SimpleResponse {
protected long result;
protected ValueResponse() {}
protected ValueResponse(Owner owner, long result, long version) {
super(owner, version);
this.result=result;
}
@Override
public void readFrom(DataInput in) throws IOException, ClassNotFoundException {
super.readFrom(in);
result=Bits.readLong(in);
}
@Override
public void writeTo(DataOutput out) throws IOException {
super.writeTo(out);
Bits.writeLong(result, out);
}
public String toString() {return "ValueResponse(" + result + ")";}
}
protected static class GetOrCreateResponse extends ValueResponse {
protected GetOrCreateResponse() {}
protected GetOrCreateResponse(Owner owner, long result, long version) {
super(owner,result, version);
}
public String toString() {return "GetOrCreateResponse(" + result + ")";}
}
protected static class ExceptionResponse extends SimpleResponse {
protected String error_message;
protected ExceptionResponse() {}
protected ExceptionResponse(Owner owner, String error_message) {
super(owner, 0);
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();}
}
protected static class ReconcileResponse implements 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";
}
}
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[] compareAndSet(long expected, long update) {
if(value == expected)
return new long[]{value=update, ++version};
return null;
}
/** 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;
}
}
public String toString() {return value + " (version=" + version + ")";}
}
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) {
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;
}
if(my_value.version < version)
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();
}
}
}