Hadoop源码分析5: RPC基本线程

1. 数据记录FileStatus

public class FileStatus implements Writable {
            private String filename;
        private long time;    
    static {  // register IPCFileStatus
        WritableFactories.setFactory
            (FileStatus.class,
              new WritableFactory() {
                  publicWritable newInstance() { return new FileStatus(); } } );
    }
     
    public FileStatus() {   
    }
     
      publicFileStatus(String filename) {
            this.filename=filename;
            this.time=(newDate()).getTime();
      }
 

      @Override
      publicvoid readFields(DataInput in) throws IOException {
            this.filename= Text.readString(in);
            this.time =in.readLong();
      }

      @Override
      publicvoid write(DataOutput out) throws IOException {
            Text.writeString(out, filename);
            out.writeLong(time);
       
    //getter , setter
}

2.服务接口和实现

public interface Query extends VersionedProtocol{
      FileStatus getFileStatus(Stringfilename);
 }

public class QueryImpl implements Query {
      protected QueryImpl() {
      }

      @Override
      publicFileStatus getFileStatus(String filename) {
            FileStatus status=newFileStatus(filename);
            System.out.println("MethodgetFileStatus Called, return: "+status);
            return status;
      }

      @Override
      publiclong getProtocolVersion(String protocol, long clientVersion)throws IOException {
            System.out.println("protocol:"+protocol);
            System.out.println("clientVersion:"+clientVersion);
            returnMyServer.IPC_VER;
      }
}
3.服务器端
public class MyServer {
      publicstatic final int IPC_PORT = 32121;
      publicstatic final long IPC_VER = 5473L;
      publicstatic void main(String[] args) throws IOException{ 

        QueryImpl queryService=new QueryImpl();         
        Server server = RPC.getServer(queryService, 
                         "0.0.0.0",IPC_PORT, 
                         2,true,
                         newConfiguration());
            server. start();
            System.out.println("Serverready, press any key to stop");
            System.in.read();
            server. stop();
            System.out.println("Serverstopped");
      }
}

4.客户端
public class MyClient {
      publicstatic void main(String[] args) throws Exception { 
              InetSocketAddressaddr=new InetSocketAddress("localhost", MyServer.IPC_PORT);
            Query query=(Query) RPC.getProxy(Query.class, MyServer.IPC_VER,addr,new Configuration());
            FileStatusstatus=query. getFileStatus("/tmp/testIPC");
            System.out.println(status);
            RPC. stopProxy(query); 
      }
}

5.服务器端执行以下getServer时候:
Server server=  RPC.getServer(queryService, 
                              "0.0.0.0",IPC_PORT, 
                              2, true,
                              newConfiguration()) ;
即以下方法,
使用 2
Handlers
public static Server getServer(final Object instance,final String bindAddress, final int port, 
final intnumHandlers, 
 final boolean verbose,Configuration conf) 

(1).构造newConfiguration()时候会加载  core-default.xml,core-site.xml。

(2).实际返回的是 org.apache.hadoop.ipc.RPC.Server  对象(继承自 org.apache.hadoop.ipc.Server

  (3).生成
1 org.apache.hadoop.ipc.Server.Listener  线程,注册 SelectionKey.OP_ACCEPT 事件
1org.apache.hadoop.ipc.Server.Listener.Reader线程(个数在IPC_SERVER_RPC_READ_THREADS_KEY中配置),并且立即启动 线程,监控  Readable的事件。
1org.apache.hadoop.ipc.Server.Responder线程,监控 Writable事件和 Call
 
6.服务器端执行start 的时候

public synchronized void start() {
    responder.start();
    listener.start();
    handlers = new Handler[handlerCount];
     
    for (int i = 0; i <handlerCount; i++) {
      handlers[i] = new Handler(i);
      handlers[i]. start();
    }
  }

立即启动 org.apache.hadoop.ipc.Server.Responder 线程
立即启动 org.apache.hadoop.ipc.Server.Listener   线程
立即构造启动2 org.apache.hadoop.ipc.Server.Handler   线程,用于监控  Call。

此时共有5个线程,其内容主要如下:

1个 org.apache.hadoop.ipc.Server.Listener

    privateServerSocketChannel acceptChannel = null; //the accept channel
    private Selectorselector = null; //the selector that we usefor the server
    private Reader[] readers= null;
    private intcurrentReader = 0;
    privateInetSocketAddress address; //the address webind at
    private Random rand =new Random();
    private longlastCleanupRunTime = 0; //the last time whena cleanup connec-
                                  //-tion (for idle connections) ran
    private longcleanupInterval = 10000; //the minimuminterval between 
                                   //two cleanup runs
    private intbacklogLength = conf.getInt("ipc.server.listen.queue.size",128);
    private ExecutorServicereadPool; 

    public void run(){
    //ThreadLocal
      SERVER.set(Server.this);
      while(running) {
        SelectionKey key = null;
        try {
          selector.select();
          Iterator iter =selector.selectedKeys().iterator();
          while (iter.hasNext()){
            key =iter.next();
            iter.remove();
            try{
              if (key. isValid()){ 
               //如有了连接事件
                if(key. isAcceptable())
                  doAccept(key);
              }
            } catch(IOException e) {
            }
            key =null;
          }
        } catch (OutOfMemoryError e) {
          ....................
        } catch (Exception e) {
              ....................
        }
        cleanupConnections(false);
      }
      LOG.info("Stopping " + this.getName());

      synchronized (this) {
        try {
          acceptChannel.close();
          selector.close();
        } catch (IOException e) { }
        selector= null;
        acceptChannel= null;        
        // clean up allconnections
        while (!connectionList.isEmpty()) {
          closeConnection(connectionList.remove(0));
        }
      }
    }

void doAccept(SelectionKey key) throws IOException,  OutOfMemoryError {
      Connection c = null;
      ServerSocketChannel server = (ServerSocketChannel)key. channel();
      SocketChannel channel;
      while((channel = server.accept()) != null) {
        channel.configureBlocking(false);
        channel.socket().setTcpNoDelay(tcpNoDelay);
        //使用简单的robin轮询算法选择Reader
        Reader reader = getReader();
        try {
         //唤醒readSelector
          reader. startAdd();
          //registerChannel实际就是注册写事件
          SelectionKey readKey =reader. registerChannel(channel);
          //构建Connection对象
          c = new Connection(readKey, channel,System.currentTimeMillis());
          readKey.attach(c);
          synchronized (connectionList){
            //connectionList 是 List,用于维护多个客户端连接
           connectionList.add(numConnections, c);
            numConnections++;
          }
          if(LOG.isDebugEnabled())
            LOG.debug("Server connection from " + c.toString() +
                "; # active connections: " +numConnections +
                "; # queued calls: " +callQueue.size());          
        } finally {
          reader. finishAdd(); 
        }

      }
    }

   使用简单的robin轮询算法选择Reader
    // The method that willreturn the next reader to work with
    // Simplisticimplementation of round robin for now
    Reader getReader() {
      currentReader = (currentReader + 1) % readers.length;
      return readers[currentReader];
    }

     
      public void startAdd() {
        adding = true;
        readSelector. wakeup();
      }

    //registerChannel实际就是注册写事件
    public synchronizedSelectionKey registerChannel(SocketChannel channel)
                                                          throws IOException {
          returnchannel.register(readSelector, SelectionKey.OP_READ);
      }

  //唤醒其他进程   
    public synchronized void finishAdd() {
        adding = false;
        this.notify();        
      }


1个 org.apache.hadoop.ipc.Server.Listener.Reader 

      private volatile boolean adding = false;
      private Selector readSelector = null;

      Reader(Selector readSelector) {
        this.readSelector = readSelector;
      }
      public void run() {
        LOG.info("Starting SocketReader");
        synchronized (this) {
          while (running) {
            SelectionKey key = null;
            try{
              readSelector.select();
              while (adding) {
                this.wait(1000);
              }              

              Iterator iter =readSelector.selectedKeys().iterator();
              while (iter.hasNext()) {
                key = iter.next();
                iter.remove();
                if (key.isValid()) {
                  if(key.isReadable()) {
                    doRead(key);
                  }
                }
                key = null;
              }
            } catch(InterruptedException e) {
              if (running) {                       // unexpected -- log it
                LOG.info(getName() + "caught: " +
                          StringUtils.stringifyException(e));
              }
            } catch(IOException ex) {
              LOG.error("Error in Reader", ex);
            }
          }
        }
      }

    void doRead(SelectionKey key) throws InterruptedException {
      intcount = 0;
      Connection c = (Connection)key.attachment();
      if (c== null) {
        return;  
      }
      c.setLastContact(System.currentTimeMillis());
       
      try{
        count = c. readAndProcess();
      }catch (InterruptedException ieo) {
        LOG.info(getName() + ": readAndProcess caughtInterruptedException", ieo);
        throw ieo;
      }catch (Exception e) {
        LOG.info(getName() + ": readAndProcess threwexception " + e + ". Count of bytes read: " + count, e);
        count = -1; //so that the (count < 0) blockis executed
      }
      if(count < 0) {
        if (LOG.isDebugEnabled())
          LOG.debug(getName() + ":disconnecting client " + 
                    c + ". Number of active connections: "+
                    numConnections);
        closeConnection(c);
        c = null;
      }
      else{
        c.setLastContact(System.currentTimeMillis());
      }
     

    public int readAndProcess() throws IOException, InterruptedException{
      while(true) {
           
        int count = -1;
        if (dataLengthBuffer.remaining() > 0) {
          count = channelRead(channel, dataLengthBuffer);        
          if (count < 0 ||dataLengthBuffer.remaining() > 0) 
            returncount;
        }
       
        if (!rpcHeaderRead) {
          //Every connection isexpected to send the header.
          if (rpcHeaderBuffer == null){
            rpcHeaderBuffer = ByteBuffer.allocate(2);
          }
          count = channelRead(channel, rpcHeaderBuffer);
          if (count < 0 ||rpcHeaderBuffer.remaining() > 0) {
            returncount;
          }
          int version =rpcHeaderBuffer.get(0);
          byte[] method = new byte[]{rpcHeaderBuffer.get(1)};
          authMethod =AuthMethod.read(new DataInputStream(
              new ByteArrayInputStream(method)));
          dataLengthBuffer.flip();         
          if(!HEADER.equals(dataLengthBuffer) || version != CURRENT_VERSION){
            //Warningis ok since this is not supposed to happen.
            LOG.warn("Incorrect header or version mismatch from "+ 
                      hostAddress + ":" + remotePort+
                      " got version " + version+ 
                      " expected version " +CURRENT_VERSION);
            return-1;
          }
          dataLengthBuffer.clear();
          if (authMethod == null){
            throw newIOException("Unable to read authentication method");
          }
          if (isSecurityEnabled&& authMethod == AuthMethod.SIMPLE) {
            AccessControlException ae = new AccessControlException(
                "Authentication isrequired");
            setupResponse(authFailedResponse, authFailedCall,Status.FATAL,
                null,ae.getClass().getName(), ae.getMessage());
            responder.doRespond(authFailedCall);
            throwae;
          }
          if (!isSecurityEnabled&& authMethod != AuthMethod.SIMPLE) {
            doSaslReply(SaslStatus.SUCCESS, new IntWritable(
                SaslRpcServer.SWITCH_TO_SIMPLE_AUTH), null, null);
            authMethod= AuthMethod.SIMPLE;
            // clienthas already sent the initial Sasl message and we
            // shouldignore it. Both client and server should fall back
            // tosimple auth from now on.
            skipInitialSaslHandshake = true;
          }
          if (authMethod !=AuthMethod.SIMPLE) {
            useSasl =true;
          }
           
          rpcHeaderBuffer = null;
          rpcHeaderRead = true;
          continue;
        }
         
        if (data == null) {
          dataLengthBuffer.flip();
          dataLength =dataLengthBuffer.getInt();
       
          if (dataLength ==Client.PING_CALL_ID) {
            if(!useWrap) { //covers the !useSasl too
              dataLengthBuffer.clear();
              return 0;   //ping message
            }
          }
          if (dataLength < 0){
            LOG.warn("Unexpected data length " + dataLength + "!! from "+ 
                getHostAddress());
          }
          data =ByteBuffer.allocate(dataLength);
        }
         
        count = channelRead(channel, data);
         
        if (data.remaining() == 0) {
          dataLengthBuffer.clear();
          data.flip();
          if (skipInitialSaslHandshake ){
            data =null;
            skipInitialSaslHandshake = false;
            continue;
          }
          boolean isHeaderRead =headerRead;
          if (useSasl) {
            saslReadAndProcess(data.array());
          } else {
            processOneRpc(data.array());
          }
          data = null;
          if (!isHeaderRead) {
            continue;
          }
       
        return count;
      }
    }

  private int channelRead(ReadableByteChannelchannel, 
                          ByteBuffer buffer) throws IOException {
     
    int count =(buffer.remaining() <= NIO_BUFFER_LIMIT) ?
                channel.read(buffer) : channelIO(channel, null, buffer);
    if (count > 0){
      rpcMetrics.incrReceivedBytes(count);
    }
    return count;
  }

  private void processOneRpc(byte[]buf) throws IOException,
        InterruptedException {
      if(headerRead) {
        processData(buf);
      }else {
        processHeader(buf);
        headerRead = true;
        if (!authorizeConnection()) {
          throw newAccessControlException("Connection from " + this
              + " for protocol " + header.getProtocol()
              + " is unauthorized for user " + user);
        }
      }
    }

    private void processData(byte[] buf) throws  IOException, InterruptedException {
      DataInputStream dis =
        new DataInputStream(newByteArrayInputStream(buf));
      intid = dis.readInt();                     // try to read an id
         
      if(LOG.isDebugEnabled())
        LOG.debug(" got #" + id);

      Writable param = ReflectionUtils.newInstance(paramClass,conf);//read param
     param.readFields(dis);        
         
      Call call = new Call(id, param, this);
      callQueue.put(call);               // queue the call; maybeblocked here
      incRpcCount();   // Increment the rpc count
    }

org.apache.hadoop.ipc.Server.Listener.Reader线程接受客户端输入,将其包装为Call对象,放置在callQueue( 一个BlockingQueue )中。

2个org.apache.hadoop.ipc.Server.Handler 线程

@Override
    public void run(){
      LOG.info(getName() + ": starting");
      SERVER.set(Server.this);
      ByteArrayOutputStream buf = 
        newByteArrayOutputStream(INITIAL_RESP_BUF_SIZE);
      while(running) {
        try {
          final Call call =callQueue.take(); // pop the queue; maybe blocked here

          if(LOG.isDebugEnabled())
            LOG.debug(getName() + ": has #" + call.id + " from " +
                      call.connection);
           
          String errorClass =null;
          String error = null;
          Writable value = null;

          CurCall.set(call);
          try {
            // Makethe call as the user via Subject.doAs, thus associating
            // thecall with the Subject
            if(call.connection.user == null) {
              value = call(call.connection.protocol,call.param, 
                       call.timestamp);
            } else{
              value = 
                call.connection.user.doAs
                  (newPrivilegedExceptionActio n() {
                      @Override
                      public Writable run() throwsException {
                        // make thecall
                        return call(call.connection.protocol, 
                              call.param,call.timestamp);

                      }
                    }
                  );
            }
          } catch (Throwable e) {
            LOG.info(getName()+", call "+call+": error: " + e, e);
            errorClass= e.getClass().getName();
            error =StringUtils.stringifyException(e);
          }
          CurCall.set(null);
          synchronized(call.connection.responseQueue) {
            //setupResponse() needs to be sync'ed togetherwith 
            //responder.doResponse() since setupResponse may use
            // SASL toencrypt response data and SASL enforces
            // its ownmessage ordering.
           setupResponse(buf, call, 
                     (error ==null) ? Status.SUCCESS : Status.ERROR, 
                     value,errorClass, error);
          // Discard the large buf andreset it back to 
          // smaller size to freeupheap
          if (buf.size() >maxRespSize) {
            LOG.warn("Large response size " + buf.size() + " for call "+ 
                call.toString());
              buf = newByteArrayOutputStream(INITIAL_RESP_BUF_SIZE);
            }
            responder.doRespond(call);
          }
        } catch (InterruptedException e) {
          if (running) {                          // unexpected -- log it
            LOG.info(getName() + " caught: " +
                      StringUtils.stringifyException(e));
          }
        } catch (Exception e) {
          LOG.info(getName() + "caught: " +
                    StringUtils.stringifyException(e));
        }
      }
      LOG.info(getName() + ": exiting");
    }

call的一个实现例子,直接调用反射

public Writable call(Class   protocol,Writable param, long receivedTime) 
    throws IOException{
      try{
        Invocation call = (Invocation)param;
        if (verbose) log("Call: " + call);

        Method method =
        protocol.getMethod(call.getMethodName(),
                             call.getParameterClasses());
       method.setAccessible(true);

        long startTime =System.currentTimeMillis();
        Object value = method.invoke(instance,call.getParameters());
        int processingTime = (int)(System.currentTimeMillis() - startTime);
        int qTime = (int)(startTime-receivedTime);
        if (LOG.isDebugEnabled()) {
          LOG.debug("Served: " +call.getMethodName() +
                    " queueTime= " + qTime +
                    " procesingTime= " + processingTime);
        }
        rpcMetrics.addRpcQueueTime(qTime);
        rpcMetrics.addRpcProcessingTime(processingTime);
        rpcMetrics.addRpcProcessingTime(call.getMethodName(),processingTime);
        if (verbose) log("Return: "+value);

        return newObjectWritable(method.getReturnType(), value);

      }catch (InvocationTargetExceptio n e) {
        Throwable target = e.getTargetException();
        if (target instanceof IOException) {
          throw(IOException)target;
        } else {
          IOException ioe = newIOException(target.toString());
          ioe.setStackTrace(target.getStackTrace());
          throw ioe;
        }
      }catch (Throwable e) {
        if (!(e instanceof IOException)) {
          LOG.error("Unexpectedthrowable object ", e);
        }
        IOException ioe = newIOException(e.toString());
        ioe.setStackTrace(e.getStackTrace());
        throw ioe;
      }
    }
  }

private void setupResponse(ByteArrayOutputStreamresponse, 
                              Call call,Status status, 
                              Writablerv, String errorClass, String error) 
  throws IOException {
    response.reset();
    DataOutputStream out =new DataOutputStream(response);
    out.writeInt(call.id);                // write call id
    out.writeInt(status.state);           // writestatus

    if (status ==Status.SUCCESS) {
      rv.write(out);
    } else {
      WritableUtils.writeString(out, errorClass);
      WritableUtils.writeString(out, error);
    }
    if(call.connection.useWrap) {
      wrapWithSasl(response, call);
    }
    call.setResponse(ByteBuffer.wrap(response.toByteArray()));
  }

  void doRespond(Call call) throwsIOException {
      synchronized (call.connection.responseQueue) {
        call.connection.responseQueue.addLast(call);
        if (call.connection.responseQueue.size() == 1){
          processResponse(call.connection.responseQueue, true);
        }
      }
    }

    private boolean processResponse(LinkedList responseQueue,
                                    booleaninHandler) throws IOException {
      boolean error = true;
      boolean done = false;       // there is more data for this channel.
      intnumElements = 0;
      Callcall = null;
      try{
        synchronized (responseQueue) {
          //
          // If there are no items forthis channel, then we are done
          //
          numElements =responseQueue.size();
          if (numElements == 0) {
            error =false;
            returntrue;               // no more data for this channel.
          }
          //
          // Extract the firstcall
          //
          call =responseQueue.removeFirst();
          SocketChannel channel =call.connection.channel;
          if (LOG.isDebugEnabled()){
            LOG.debug(getName() + ": responding to #" + call.id + " from "+
                      call.connection);
          }
          //
          // Send as much data as wecan in the non-blocking fashion
          //
          int numBytes = channelWrite(channel, call.response);
          if (numBytes < 0) {
            returntrue;
          }
          if(!call.response.hasRemaining()) {
            call.connection.decRpcCount();
            if(numElements == 1) {     // lastcall fully processes.
              done = true;             // no more data for thischannel.
            } else{
              done = false;             // more calls pending to besent.
            }
            if(LOG.isDebugEnabled()) {
              LOG.debug(getName() + ": responding to #" +call.id + " from " +
                        call.connection + " Wrote " + numBytes + " bytes.");
            }
          } else {
            //
            // If wewere unable to write the entire response out,then 
            // insertin Selector queue. 
            //
            call.connection.responseQueue.addFirst(call);
             
            if(inHandler) {
              // set the serve time when the response has tobe sent later
              call.timestamp =System.currentTimeMillis();
               
              incPending();
              try {
                // Wakeup the thread blockedon select, only then can the call 
                // to channel.register()complete.
                writeSelector.wakeup();
             channel.register(writeSelector, SelectionKey.OP_WRITE,call);
              } catch (ClosedChannelException e) {
                //Its ok. channel might beclosed else where.
                done = true;
              } finally {
                decPending();
              }
            }
            if(LOG.isDebugEnabled()) {
              LOG.debug(getName() + ": responding to #" +call.id + " from " +
                        call.connection + " Wrote partial " + numBytes+ 
                        "bytes.");
            }
          }
          error = false;              // everything went off well
        }
      }finally {
        if (error && call != null) {
          LOG.warn(getName()+", call "+ call + ": output error");
          done = true;              // error. no more data for this channel.
          closeConnection(call.connection);
        }
      }
      return done;
    }

  private int channelWrite(WritableByteChannelchannel, 
                            ByteBuffer buffer) throwsIOException {
     
    int count =  (buffer.remaining() <= NIO_BUFFER_LIMIT)?
                  channel.write(buffer) : channelIO(null, channel,buffer);
    if (count > 0){
      rpcMetrics.incrSentBytes(count);
    }
    return count;
  }
org.apache.hadoop.ipc.Server.Handler 线程从callQueue拿出Call,使用反射调用方法运行,将结果放入responseQueue或者直接写给客户端

1org.apache.hadoop.ipc.Server.Responder 

 private Selector writeSelector;
    private int pending;        // connections waiting to register
     
    final static intPURGE_INTERVAL = 900000; // 15mins

    Responder() throwsIOException {
      this.setName("IPC Server Responder");
      this.setDaemon(true);
      writeSelector = Selector.open(); // create a selector
      pending = 0;
    }

    @Override
    public void run(){
      LOG.info(getName() + ": starting");
      SERVER.set(Server.this);
      longlastPurgeTime = 0;   // last check for oldcalls.

      while(running) {
        try {
          waitPending();    // If a channel is beingregistered, wait.
          writeSelector.select(PURGE_INTERVAL);
          Iterator iter =writeSelector.selectedKeys().iterator();
          while (iter.hasNext()){
            SelectionKey key = iter.next();
            iter.remove();
            try{
              if (key.isValid() && key.isWritable()) {
                 doAsyncWrite(key);
              }
            } catch(IOException e) {
              LOG.info(getName() + ": doAsyncWrite threwexception " + e);
            }
          }
          long now =System.currentTimeMillis();
          if (now < lastPurgeTime +PURGE_INTERVAL) {
            continue;
          }
          lastPurgeTime = now;
          //
          // If there were some callsthat have not been sent out for a
          // long time, discardthem.
          //
          LOG.debug("Checking for oldcall responses.");
          ArrayList calls;
           
          // get the list of channelsfrom list of keys.
          synchronized(writeSelector.keys()) {
            calls =new ArrayList(writeSelector.keys().size());
            iter =writeSelector.keys().iterator();
            while(iter.hasNext()) {
              SelectionKey key = iter.next();
              Call call = (Call)key.attachment();
              if (call != null && key.channel() ==call.connection.channel) { 
                calls.add(call);
              }
            }
          }
           
          for(Call call : calls){
            try{
             doPurge(call, now);
            } catch(IOException e) {
              LOG.warn("Error in purging old calls " +e);
            }
          }
        } catch (OutOfMemoryError e) {
          //
          // we can run out of memoryif we have too many threads
          // log the event and sleepfor a minute and give
          // some thread(s) a chance tofinish
          //
          LOG.warn("Out of Memory inserver select", e);
          try { Thread.sleep(60000); }catch (Exception ie) {}
        } catch (Exception e) {
          LOG.warn("Exception inResponder " + 
                    StringUtils.stringifyException(e));
        }
      }
      LOG.info("Stopping " + this.getName());
    }



  private void doAsyncWrite(SelectionKey key) throws IOException {
      Callcall = (Call)key.attachment();
      if(call == null) {
        return;
      }
      if(key.channel() != call.connection.channel) {
        throw new IOException("doAsyncWrite: badchannel");
      }

      synchronized(call.connection.responseQueue) {
        if( processResponse(call.connection.responseQueue, false)){
          try {
            key.interestOps(0);
          } catch(CancelledKeyException e) {
           
            LOG.warn("Exception while changing ops : " + e);
          }
        }
      }
    }

processResponse 的实现在org.apache.hadoop.ipc.Server.Handler中也有调用

// Remove calls that have beenpending in the responseQueue 
    // for a longtime.
    //
    private void doPurge(Call call, long now) throws IOException {
      LinkedList responseQueue = call.connection.responseQueue;
      synchronized (responseQueue) {
        Iterator iter =responseQueue.listIterator(0);
        while (iter.hasNext()) {
          call = iter.next();
          if (now > call.timestamp +PURGE_INTERVAL) {
            closeConnection(call.connection);
            break;
          }
        }
      }
    }


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值