All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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 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; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy