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

org.jgroups.protocols.COUNTER Maven / Gradle / Ivy

There is a newer version: 5.4.1.Final
Show 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 - 2025 Weber Informatics LLC | Privacy Policy