/* Receive a response. * Because only one receiver, so no synchronization on in. */ protected void receiveResponse() { if (shouldCloseConnection.get()) { return; } touch(); try { // See HBaseServer.Call.setResponse for where we write out the response. // It writes the call.id (int), a flag byte, then optionally the length // of the response (int) followed by data. // Read the call id. int id = in.readInt(); if (LOG.isDebugEnabled()) LOG.debug(getName() + " got value #" + id); Call call = calls.get(id); // Read the flag byte byte flag = in.readByte(); boolean isError = ResponseFlag.isError(flag); if (ResponseFlag.isLength(flag)) { // Currently length if present is unused. in.readInt(); } int state = in.readInt(); // Read the state. Currently unused. if (isError) { if (call != null) { //noinspection ThrowableInstanceNeverThrown call.setException( new RemoteException(WritableUtils.readString(in), WritableUtils.readString(in))); } } else { Writable value = ReflectionUtils.newInstance(valueClass, conf); value.readFields(in); // read value // it's possible that this call may have been cleaned up due to a RPC // timeout, so check if it still exists before setting the value. if (call != null) { call.setValue(value); } } calls.remove(id); } catch (IOException e) { if (e instanceof SocketTimeoutException && remoteId.rpcTimeout > 0) { // Clean up open calls but don't treat this as a fatal condition, // since we expect certain responses to not make it by the specified // {@link ConnectionId#rpcTimeout}. closeException = e; } else { // Since the server did not respond within the default ping interval // time, treat this as a fatal condition and close this connection markClosed(e); } } finally { if (remoteId.rpcTimeout > 0) { cleanupCalls(remoteId.rpcTimeout); } } }
/** * Add a call to this connection's call queue and notify a listener; synchronized. If the * connection is dead, the call is not added, and the caller is notified. This function can * return a connection that is already marked as 'shouldCloseConnection' It is up to the user * code to check this status. * * @param call to add */ protected synchronized void addCall(Call call) { // If the connection is about to close, we manage this as if the call was already added // to the connection calls list. If not, the connection creations are serialized, as // mentioned in HBASE-6364 if (this.shouldCloseConnection.get()) { if (this.closeException == null) { call.setException( new IOException( "Call " + call.id + " not added as the connection " + remoteId + " is closing")); } else { call.setException(this.closeException); } synchronized (call) { call.notifyAll(); } } else { calls.put(call.id, call); notify(); } }
protected void cleanupCalls(long rpcTimeout) { Iterator<Entry<Integer, Call>> itor = calls.entrySet().iterator(); while (itor.hasNext()) { Call c = itor.next().getValue(); long waitTime = System.currentTimeMillis() - c.getStartTime(); if (waitTime >= rpcTimeout) { if (this.closeException == null) { // There may be no exception in the case that there are many calls // being multiplexed over this connection and these are succeeding // fine while this Call object is taking a long time to finish // over on the server; e.g. I just asked the regionserver to bulk // open 3k regions or its a big fat multiput into a heavily-loaded // server (Perhaps this only happens at the extremes?) this.closeException = new CallTimeoutException( "Call id=" + c.id + ", waitTime=" + waitTime + ", rpcTimetout=" + rpcTimeout); } c.setException(this.closeException); synchronized (c) { c.notifyAll(); } itor.remove(); } else { break; } } try { if (!calls.isEmpty()) { Call firstCall = calls.get(calls.firstKey()); long maxWaitTime = System.currentTimeMillis() - firstCall.getStartTime(); if (maxWaitTime < rpcTimeout) { rpcTimeout -= maxWaitTime; } } if (!shouldCloseConnection.get()) { closeException = null; if (socket != null) { socket.setSoTimeout((int) rpcTimeout); } } } catch (SocketException e) { LOG.debug("Couldn't lower timeout, which may result in longer than expected calls"); } }