/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.cassandra.service; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicIntegerArray; import org.apache.log4j.Logger; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.net.IAsyncCallback; import org.apache.cassandra.net.Message; import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.SimpleCondition; public class WriteResponseHandler implements IAsyncCallback { protected static final Logger logger = Logger.getLogger( WriteResponseHandler.class ); protected final SimpleCondition condition = new SimpleCondition(); protected final SimpleCondition conditionAll = new SimpleCondition(); // start time in NANOSECONDS (because need to evaluate 10ths of millis in #getAllResponses private final long startNanos; private final int allEndpointCount; private int remoteEndpointCount = 0; private final int responseCount; // keeping all stuff in single atomicintegerobject is faster 1.5 - 2x for RF=3 than LBQ // 0tn element has count of remaining responses // endpoint addresses are stored starting from 1st and till the end private AtomicIntegerArray responses; public WriteResponseHandler(int responseCount, int endpoints, String table) { this.allEndpointCount = endpoints; // at most one node per range can bootstrap at a time, and these will be added to the write until // bootstrap finishes (at which point we no longer need to write to the old ones). assert 1 <= responseCount && responseCount <= 2 * DatabaseDescriptor.getReplicationFactor(table) : "invalid response count " + responseCount; responses = new AtomicIntegerArray(endpoints+1); this.responseCount = responseCount; startNanos = System.nanoTime(); } public static final int toInt(byte[] b) { return b[0]<<24 | (b[1]&0xff)<<16 | (b[2]&0xff)<<8 | (b[3]&0xff); } public void addEndpoint(InetAddress endpoint) { responses.set(++remoteEndpointCount, toInt(endpoint.getAddress()) ); } public void get() throws TimeoutException{ long timeout = DatabaseDescriptor.getRpcTimeout() - (System.nanoTime() - startNanos)/1000000; get(timeout); } public void get(long timeout) throws TimeoutException { boolean success; try { success = condition.await(timeout, TimeUnit.MILLISECONDS); } catch (InterruptedException ex) { throw new AssertionError(ex); } if (!success) { throw new TimeoutException("Operation timed out - not received " + responses.get(0) + " responses"); } } public void response(Message message) { for (int i = 1;i<=remoteEndpointCount && !responses.compareAndSet(i, toInt(message.getFrom().getAddress()), 0);i++); maybeSignal(); } public void localResponse() { maybeSignal(); } /** * Wait at most for a specified millis for all endpoints' responses * * @param timeout additional time to wait (tenths of millis) * @return true - all endpoints responded, false otherwise */ public boolean getAllResponses(long timeout) { if ( conditionAll.isSignaled() ) return true; timeout -= (System.nanoTime() - startNanos)/1000000; if (timeout<=0l) return conditionAll.isSignaled(); // waiting try { return conditionAll.await(timeout, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { throw new AssertionError(e); } } /** * @return endpoints form which we did not received responses */ public List<InetAddress> getLaggingEndpoints() { int laggedEndpointCount = allEndpointCount - responses.get(0); ArrayList<InetAddress> rc = new ArrayList<InetAddress>(laggedEndpointCount); try { for (int i=remoteEndpointCount+1;i-->1;) { int endpoint = responses.get(i); if (endpoint!=0) rc.add(InetAddress.getByAddress(FBUtilities.toByteArray(endpoint))); } } catch (UnknownHostException e) { throw new AssertionError(responses); } // there is a small chance that while we did checking remote endpoint did registered // its response. So rechecking response count and returning null, // if it is registered itself if (rc.size()>0) { return allEndpointCount - responses.get(0) > 0 ? rc : null; } else { // not found failed remote endpoint for this missed mutation. So this is local one! if (allEndpointCount - responses.get(0) > 0) { rc.add(FBUtilities.getLocalAddress()); return rc; } } return null; } private void maybeSignal() { int responsesReceived = responses.incrementAndGet(0); if (responsesReceived == responseCount ) { condition.signal(); } if (responsesReceived == allEndpointCount) { conditionAll.signal(); } } }