package org.infinispan.remoting.transport.jgroups; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; import java.util.concurrent.atomic.AtomicReferenceArray; import org.infinispan.commons.util.SimpleImmutableEntry; import org.infinispan.remoting.responses.Response; import org.jgroups.Address; import org.jgroups.util.Rsp; import org.jgroups.util.RspList; import net.jcip.annotations.ThreadSafe; /** * Pre-allocated holder for multiple responses. Until the response is received, * or node is marked as suspected/unreachable the value is <code>null</code>. * * This class is safe against concurrent access by multiple threads. */ @ThreadSafe public class Responses implements Iterable<Map.Entry<Address, Rsp<Response>>> { public static final Responses EMPTY = new Responses(Collections.emptyList()); public static final Rsp<Response> SUSPECTED_RSP = new Rsp<>(); private final Address[] addresses; private final AtomicReferenceArray<Rsp<Response>> responses; private volatile boolean timedOut = false; static { SUSPECTED_RSP.setSuspected(); } public Responses(Collection<Address> addresses) { int size = addresses.size(); this.addresses = addresses.toArray(new Address[size]); this.responses = new AtomicReferenceArray<>(size); } /** * Constructs new instance using existing {@link RspList}. If the response * was not received and the node was neither marked as suspected nor unreachable * the response in this instance will be <code>null</code>. * * @param results Received responses. */ public Responses(Collection<Address> dests, RspList<Response> results) { if (dests != null && results.size() < dests.size()) { // We don't have a Rsp for each destination, and we have to mark the missing destinations as suspect int size = dests.size(); this.addresses = new Address[size]; this.responses = new AtomicReferenceArray<>(size); int i = 0; for (Address address : dests) { this.addresses[i] = address; Rsp<Response> rsp = results.get(address); if (rsp == null) { // The Rsp is only missing if the recipient was not a view member responses.set(i, SUSPECTED_RSP); } else if (rsp.wasReceived() || rsp.wasSuspected() || rsp.wasUnreachable()) { // keep non-received response as null responses.set(i, rsp); } ++i; } } else { int size = results.size(); this.addresses = new Address[size]; this.responses = new AtomicReferenceArray<>(size); int i = 0; for (Map.Entry<Address, Rsp<Response>> e : results.entrySet()) { addresses[i] = e.getKey(); Rsp<Response> rsp = e.getValue(); // keep non-received response as null if (rsp.wasReceived() || rsp.wasSuspected() || rsp.wasUnreachable()) { responses.set(i, rsp); } ++i; } } } public void addResponse(Address sender, Rsp rsp) { for (int i = 0; i < addresses.length; ++i) { if (addresses[i].equals(sender)) { if (!responses.compareAndSet(i, null, rsp)) { throw new IllegalArgumentException("Response from " + sender + " already received: " + responses.get(i)); } } } } public boolean isMissingResponses() { for (int i = 0; i < responses.length(); ++i) { if (responses.get(i) == null) return true; } return false; } public int size() { return addresses.length; } @Override public Iterator<Map.Entry<Address, Rsp<Response>>> iterator() { return new Iterator<Map.Entry<Address, Rsp<Response>>>() { int i = 0; @Override public boolean hasNext() { return i < responses.length(); } @Override public Map.Entry<Address, Rsp<Response>> next() { if (hasNext()) { int index = this.i++; return new SimpleImmutableEntry<Address, Rsp<Response>>(addresses[index], responses.get(index)); } else { throw new NoSuchElementException(); } } }; } @Override public String toString() { final StringBuilder sb = new StringBuilder("Responses{"); for (int i = 0; i < addresses.length; ++i) { sb.append('\n').append(addresses[i]).append(": ").append(responses.get(i)); } sb.append('}'); return sb.toString(); } public void setTimedOut() { this.timedOut = true; } public boolean isTimedOut() { return timedOut; } }