org.jgroups.blocks.GroupRequest Maven / Gradle / Ivy
package org.jgroups.blocks;
import org.jgroups.Address;
import org.jgroups.MergeView;
import org.jgroups.Message;
import org.jgroups.View;
import org.jgroups.annotations.GuardedBy;
import org.jgroups.protocols.relay.SiteAddress;
import org.jgroups.util.Rsp;
import org.jgroups.util.RspList;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Sends a message to all members of the group and waits for all responses (or timeout). Returns a boolean value
* (success or failure). Results (if any) can be retrieved when done.
*
* The supported transport to send requests is currently either a RequestCorrelator or a generic Transport. One of
* them has to be given in the constructor. It will then be used to send a request. When a message is received by
* either one, the receiveResponse() of this class has to be called (this class does not actively receive
* requests/responses itself). Also, when a view change or suspicion is received, the methods viewChange() or suspect()
* of this class have to be called.
*
* When started, an array of responses, correlating to the membership, is created. Each response is added to the
* corresponding field in the array. When all fields have been set, the algorithm terminates. This algorithm can
* optionally use a suspicion service (failure detector) to detect (and exclude from the membership) fauly members.
* If no suspicion service is available, timeouts can be used instead (see {@code execute()}). When done, a
* list of suspected members can be retrieved.
*
* Because a channel might deliver requests, and responses to different requests, the {@code GroupRequest}
* class cannot itself receive and process requests/responses from the channel. A mechanism outside this class
* has to do this; it has to determine what the responses are for the message
* sent by the {@code execute()} method and call {@code receiveResponse()} to do so.
*
* @author Bela Ban
*/
public class GroupRequest extends Request> {
protected final Lock lock=new ReentrantLock();
/** Correlates requests and responses */
@GuardedBy("lock")
protected final RspList rsps;
@GuardedBy("lock")
protected int num_valid; // valid responses (values or exceptions that passed the response filter)
@GuardedBy("lock")
protected int num_received; // number of responses (values, exceptions or suspicions)
/**
* @param corr The request correlator to be used. A request correlator sends requests tagged with a unique ID and
* notifies the sender when matching responses are received. The reason {@code GroupRequest} uses
* it instead of a {@code Transport} is that multiple requests/responses might be sent/received concurrently
* @param targets The targets, which are supposed to receive the message. Any receiver not in this set will
* discard the message. Targets are always a subset of the current membership
* @param options The request options to be used for this call
*/
public GroupRequest(RequestCorrelator corr, Collection targets, RequestOptions options) {
super(corr, options);
rsps=new RspList<>(targets.size());
targets.forEach(target -> rsps.put(target, new Rsp<>()));
}
@Override public void sendRequest(Message msg) throws Exception {
sendRequest(msg, rsps.keySet());
}
/* ---------------------- Interface RspCollector -------------------------- */
/**
* Callback (called by RequestCorrelator or Transport).
* Adds a response to the response table. When all responses have been received, {@code execute()} returns.
*/
public void receiveResponse(Object response_value, Address sender, boolean is_exception) {
Rsp rsp;
if(isDone() || (rsp=rsps.get(sender)) == null)
return;
RspFilter rsp_filter=options.rspFilter();
lock.lock();
try {
if(!rsp.wasReceived()) {
if(!(rsp.wasSuspected() || rsp.wasUnreachable()))
num_received++;
if(rsp_filter == null || rsp_filter.isAcceptable(response_value, sender)) {
if(is_exception && response_value instanceof Throwable)
rsp.setException((Throwable)response_value);
else
rsp.setValue((T)response_value);
num_valid++;
}
}
if(responsesComplete() || (rsp_filter != null && !rsp_filter.needMoreResponses())) {
complete(this.rsps);
corrDone();
}
}
finally {
lock.unlock();
}
}
public void siteUnreachable(String site) {
lock.lock();
try {
for(Map.Entry> entry : rsps.entrySet()) {
Address member=entry.getKey();
if(!(member instanceof SiteAddress))
continue;
SiteAddress addr=(SiteAddress)member;
if(addr.getSite().equals(site)) {
Rsp rsp=entry.getValue();
if(rsp != null && rsp.setUnreachable()) {
lock.lock();
try {
if(!(rsp.wasReceived() || rsp.wasSuspected()))
num_received++;
}
finally {
lock.unlock();
}
}
}
}
if(responsesComplete()) {
complete(this.rsps);
corrDone();
}
}
finally {
lock.unlock();
}
}
/**
* Any member of 'membership' that is not in the new view is flagged as SUSPECTED. Any member in the new view that
* is not in the membership (ie, the set of responses expected for the current RPC) will not be
* added to it. If we did this we might run into the following problem:
*
* - Membership is {A,B}
*
- A sends a synchronous group RPC (which sleeps for 60 secs in the invocation handler)
*
- C joins while A waits for responses from A and B
*
- If this would generate a new view {A,B,C} and if this expanded the response set to {A,B,C}, A would wait
* forever on C's response because C never received the request in the first place, therefore won't send a response.
*
*/
public void viewChange(View view, boolean handle_previous_subgroups) {
if(view == null || rsps == null || rsps.isEmpty())
return;
boolean changed=false;
lock.lock();
try {
if(view instanceof MergeView && handle_previous_subgroups) {
// we need to set the rsp for member of a subview that doesn't contain local_addr to 'suspected',
// unless that rsp has already been received (https://issues.redhat.com/browse/JGRP-2575)
for(View v: ((MergeView)view).getSubgroups()) {
if(!v.containsMember(corr.local_addr)) {
for(Address mbr: v.getMembersRaw())
if(setSuspected(mbr))
changed=true;
}
}
}
for(Map.Entry> entry : rsps.entrySet()) {
Address mbr=entry.getKey();
// SiteAddresses are not checked as they might be in a different cluster
if(!(mbr instanceof SiteAddress) && !view.containsMember(mbr)) {
Rsp rsp=entry.getValue();
if(rsp.setSuspected()) {
if(!(rsp.wasReceived() || rsp.wasUnreachable()))
num_received++;
changed=true;
}
}
}
if(changed && responsesComplete()) {
complete(this.rsps);
corrDone();
}
}
finally {
lock.unlock();
}
}
/** Marks all responses with an exception (unless a response was already marked as done) */
public void transportClosed() {
boolean changed=false;
lock.lock();
try {
for(Map.Entry> entry: rsps.entrySet()) {
Rsp rsp=entry.getValue();
if(rsp != null && !(rsp.wasReceived() || rsp.wasSuspected() || rsp.wasUnreachable())) {
rsp.setException(new IllegalStateException("transport was closed"));
num_received++;
changed=true;
}
}
if(changed && responsesComplete()) {
complete(this.rsps);
corrDone();
}
}
finally {
lock.unlock();
}
}
/* -------------------- End of Interface RspCollector ----------------------------------- */
public boolean getResponsesComplete() {
lock.lock();
try {
return responsesComplete();
}
finally {
lock.unlock();
}
}
public RspList get() throws InterruptedException, ExecutionException {
return waitForCompletion();
}
public RspList get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
return waitForCompletion(timeout, unit);
}
public RspList join() {
return doAndComplete(super::join);
}
public RspList getNow(RspList valueIfAbsent) {
return doAndComplete(() -> super.getNow(valueIfAbsent));
}
public RspList waitForCompletion(long timeout, TimeUnit unit) {
return doAndComplete(() -> super.get(timeout, unit));
}
public RspList waitForCompletion() throws ExecutionException, InterruptedException {
return doAndComplete(super::get);
}
public String toString() {
StringBuilder ret=new StringBuilder(128);
ret.append(super.toString());
if(!rsps.isEmpty()) {
ret.append(", rsps:\n");
for(Map.Entry> entry: rsps.entrySet()) {
Address mbr=entry.getKey();
Rsp rsp=entry.getValue();
ret.append(mbr).append(": ").append(rsp).append("\n");
}
}
return ret.toString();
}
protected boolean setSuspected(Address mbr) {
Rsp rsp=rsps.get(mbr);
if(rsp != null && rsp.setSuspected()) {
if(!(rsp.wasReceived() || rsp.wasUnreachable()))
num_received++;
return true;
}
return false;
}
protected RspList doAndComplete(Callable> supplier) {
try {
return supplier.call();
}
catch(Throwable t) {
complete(this.rsps);
return this.rsps;
}
finally {
corrDone();
}
}
protected void sendRequest(Message msg, final Collection targetMembers) throws Exception {
try {
corr.sendMulticastRequest(targetMembers, msg, options.mode() == ResponseMode.GET_NONE? null : this, options);
}
catch(Exception ex) {
corrDone();
throw ex;
}
}
@GuardedBy("lock")
protected boolean responsesComplete() {
if(isDone())
return true;
final int num_total=rsps.size();
switch(options.mode()) {
case GET_FIRST: return num_valid >= 1 || num_received >= num_total;
case GET_ALL: return num_valid >= num_total || num_received >= num_total;
case GET_NONE: return true;
}
return false;
}
}