okhttp3源码解析(6)-ConnectionPool、StreamAllocation补充

okhttp3源码解析(6)-ConnectionPool、StreamAllocation补充

前言

上一篇文章把RealConnection和Http2Connection讲了讲,写得有点又臭又长了,这篇主要讲讲ConnectionPool,感觉会稍微简单点,顺带把Internal看看,并补一补StreamAllocation漏掉得一些东西。

ConnectionPool

ConnectionPool是在OkHttpClient.Builder里面用默认构造方法创建的,设置最大空闲连接数和保持连接的时间(5分钟)。

    public ConnectionPool() {
        this(5, 5, TimeUnit.MINUTES);
    }

    public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
        this.maxIdleConnections = maxIdleConnections;
        this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);

        // Put a floor on the keep alive duration, otherwise cleanup will spin loop.
        if (keepAliveDuration <= 0) {
            throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration);
        }
    }

下面来好好介绍下ConnectionPool的一些内容,代码从上往下看吧。

清理线程

ConnectionPool上来就是个线程池配Runnable,还是有点懵逼的,不过首先我们要知道它们的作用,即cleanup工作。

第一个线程池是直接创建的,我还记得有个阿里巴巴手册好像也是写的线程池要手动创建,要明白其中用的东西。

    // 配合下面的cleanupRunnable用来清楚超时的Connection,SynchronousQueue所以保证了只有一个线程嘛?
    private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,
            Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
            new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));

下面就是对应的runnable,maxIdleConnections和keepAliveDurationNs是构造传进来的。

    /** The maximum number of idle connections for each address. */
    private final int maxIdleConnections;
    private final long keepAliveDurationNs;
    // 用来清理connectionPool内的connection,清理一个只会会在延迟时间后清理下一个
    private final Runnable cleanupRunnable = new Runnable() {
        @Override public void run() {
            while (true) {
                // 得到延迟执行的时间
                long waitNanos = cleanup(System.nanoTime());
                // 对应没有connection的情况,会停止这个runnable,在put方法内会重新开始
                if (waitNanos == -1) return;
                if (waitNanos > 0) {
                    // 分成秒和纳秒两部分,Java的wait方法需要,接收纳秒范围0-999999
                    long waitMillis = waitNanos / 1000000L;
                    waitNanos -= (waitMillis * 1000000L);
                    // 连接池是全局单例?使用累锁进行卡线程
                    synchronized (ConnectionPool1.this) {
                        try {
                            ConnectionPool1.this.wait(waitMillis, (int) waitNanos);
                        } catch (InterruptedException ignored) {
                        }
                    }
                }
            }
        }
    };

这里就是清除的代码,不过放在这里看得不是很明白,这里只是数据域,接下来我们开始追踪到底是在哪调用的,会找到put方法;

    // 通过Internal在StreamAllocation的findConnection中使用
    void put(RealConnection connection) {
        assert (Thread.holdsLock(this));
        // 启动清理connection的线程,正常情况按keepAliveDurationNs周期执行
        if (!cleanupRunning) {
            cleanupRunning = true;
            executor.execute(cleanupRunnable);
        }
        connections.add(connection);
    }

Internal方法暂时不说,这里会判断下清理线程是否开启了,没开启的话,就运行cleanupRunnable,并将connection添加到ConnectionPool1中。

下面我们再回过头来分析cleanupRunnable,注释写的很清楚了,我们进去看cleanup方法,这里才是清除操作的核心所在:

    long cleanup(long now) {
        int inUseConnectionCount = 0;
        int idleConnectionCount = 0;
        RealConnection longestIdleConnection = null;
        long longestIdleDurationNs = Long.MIN_VALUE;

        // 找到要去除的connection,或者下一个要调用驱除的时间
        // Find either a connection to evict, or the time that the next eviction is due.
        synchronized (this) {
            for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
                RealConnection connection = i.next();

                // 修剪任何泄漏的分配(整理connection内的StreamAllocation),返回正在使用的流的数量,并对泄露情况处理
                // If the connection is in use, keep searching.
                if (pruneAndGetAllocationCount(connection, now) > 0) {
                    inUseConnectionCount++;
                    continue;
                }

                // connection没有分配流,即该connection不在使用了
                idleConnectionCount++;

                // 拿到空闲最久的connection,只拿一个?
                // If the connection is ready to be evicted, we're done.
                long idleDurationNs = now - connection.idleAtNanos;
                if (idleDurationNs > longestIdleDurationNs) {
                    longestIdleDurationNs = idleDurationNs;
                    longestIdleConnection = connection;
                }
            }

            // 对当前最长空闲的connection处理,超时、超多时移除该connection
            // 这里会return 0 => 直接进入cleanupRunnable下一个cleanup
            if (longestIdleDurationNs >= this.keepAliveDurationNs
                    || idleConnectionCount > this.maxIdleConnections) {
                // We've found a connection to evict. Remove it from the list, then close it below (outside
                // of the synchronized block).
                connections.remove(longestIdleConnection);

                // 最长空闲的connection还没超时,计算下下次触发cleanup的等待时间
            } else if (idleConnectionCount > 0) {
                // A connection will be ready to evict soon.
                return keepAliveDurationNs - longestIdleDurationNs;

                // connection还在使用,进入下一个keepAliveDurationNs周期的cleanup
            } else if (inUseConnectionCount > 0) {
                // All connections are in use. It'll be at least the keep alive duration 'til we run again.
                return keepAliveDurationNs;

                // 当前connectionPool里面没有connection,return -1 => cleanupRunnable周期循环结束
            } else {
                // 为什么写在这,不写到cleanupRunnable里面去。。。
                // No connections, idle or in use.
                cleanupRunning = false;
                return -1;
            }
        }

        // 对应上面if第一个情况,所以最长等待的connection需要关闭socket
        closeQuietly(longestIdleConnection.socket());

        // Cleanup again immediately.
        return 0;
    }

这里比较长,可以结合注释看下,大致内容就是遍历当前池中的connection,看看有没有使用(需不需要使用)了,一次关闭一个,关闭那个到空闲最久的connection,并得到一个延迟时间,来关闭下一个connection。

为什么括号里有“需不需要使用”,这里我们要看下pruneAndGetAllocationCount这个方法了,发生泄露的时候他也会处理下:

    // 修剪任何泄漏的分配,然后返回连接上剩余的活动分配数。如果连接正在跟踪分配但应用程序代码已放弃它们,则分配会泄漏。
    // 泄漏检测是不精确的并且依赖于垃圾收集。
    /**
     * Prunes any leaked allocations and then returns the number of remaining live allocations on
     * {@code connection}. Allocations are leaked if the connection is tracking them but the
     * application code has abandoned them. Leak detection is imprecise and relies on garbage
     * collection.
     */
    private int pruneAndGetAllocationCount(RealConnection connection, long now) {
        // RealConnection中储存的流的情况,在ConnectionPool的get方法中创建的弱引用
        List<Reference<StreamAllocation>> references = connection.allocations;
        for (int i = 0; i < references.size(); ) {
            Reference<StreamAllocation> reference = references.get(i);

            // 流还在使用
            if (reference.get() != null) {
                i++;
                continue;
            }

            // 流已经被GC了,弱引用还存在,内存泄露了,可能是没有关闭response body的流
            // We've discovered a leaked allocation. This is an application bug.
            StreamAllocation.StreamAllocationReference streamAllocRef =
                    (StreamAllocation.StreamAllocationReference) reference;
            String message = "A connection to " + connection.route().address().url()
                    + " was leaked. Did you forget to close a response body?";
            Platform.get().logCloseableLeak(message, streamAllocRef.callStackTrace);

            // 没关闭response body的流所以不能创建新流了?等着已经有的流关闭?
            references.remove(i);
            connection.noNewStreams = true;

            // If this was the last allocation, the connection is eligible for immediate eviction.
            if (references.isEmpty()) {
                // 用当前的time减去连接保持的时间,修改开始的time,在cleanup里面就会认为这个连接正好超时了,会被清理
                connection.idleAtNanos = now - keepAliveDurationNs;
                return 0;
            }
        }

        return references.size();
    }

这里就是说的一个connection里面可能有多个StreamAllocation,里面如果有泄露的话(response body),需要将这个StreamAllocation去除掉,然后返回还在使用中的StreamAllocation数量。

到这清理的功能就看完了,下面看下其他数据域。

其他数据域

    // 内部保存的RealConnection,遍历比较多所以用ArrayDeque不用LinkedList
    private final Deque<RealConnection> connections = new ArrayDeque<>();
    // RouteDatabase是对失败Route的一个记录,会在Internal中向外公开
    // 在StreamAllocation中参与routeSelector的创建,在findConnection中记录成功,在routeSelector中会有连接失败
    final RouteDatabase routeDatabase = new RouteDatabase();
    boolean cleanupRunning;

cleanupRunning上面已经讲到了,connections也比较简单,上面put方法也是放在它里面,注意下遍历比较多所以用的ArrayDeque,routeDatabase比较有意思,我们这里追踪下。

public final class RouteDatabase {
  private final Set<Route> failedRoutes = new LinkedHashSet<>();

  /** Records a failure connecting to {@code failedRoute}. */
  public synchronized void failed(Route failedRoute) {
    failedRoutes.add(failedRoute);
  }

  /** Records success connecting to {@code route}. */
  public synchronized void connected(Route route) {
    failedRoutes.remove(route);
  }

  /** Returns true if {@code route} has failed recently and should be avoided. */
  public synchronized boolean shouldPostpone(Route route) {
    return failedRoutes.contains(route);
  }
}

里面实现比较简单,但是我发现前面文章漏了很多东西,所以这里值得我追踪下,这里会跳到Internal里面,OkHttpClient -> Internal:

  static {
    Internal.instance = new Internal() {
      //...其他代码
    
      @Override public RouteDatabase routeDatabase(ConnectionPool connectionPool) {
        return connectionPool.routeDatabase;
      }
    
    }
  }

这里Internal是在OkHttpClient里面的一个匿名类,并在static块中完成初始化,看看它的说明:

/**
 * Escalate internal APIs in {@code okhttp3} so they can be used from OkHttp's implementation
 * packages. The only implementation of this interface is in {@link OkHttpClient}.
 */
 // 升级 okhttp3 中的内部 API,以便它们可以从 OkHttp 的实现包中使用。这个接口的唯一实现是在 OkHttpClient 中。

也就是说,它是用来给okhttp3增加能力的工具,很有设计意思啊。里面还有一些方法,也是ConnectionPool里面的,后面讲到了再说。

再追踪RouteDatabase,就是在StreamAllocation中参与routeSelector的创建,和在findConnection中记录成功了,至于失败,会在routeSelector中会有连接失败。

get方法

    @Nullable
    RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
        assert (Thread.holdsLock(this));
        for (RealConnection connection : connections) {
            // connection对address、route可用(Eligible),connection能创建流、不在黑名单、特殊情况
            if (connection.isEligible(address, route)) {
                // 让当前的streamAllocation去持有这个connection,并且让connection中记录这个流的分配
                streamAllocation.acquire(connection, true);
                return connection;
            }
        }
        return null;
    }

这里get方法就是ConnectionPool的取了,就是遍历dequeue找出合适的connection,isEligible可以看下里面的内容,比较长,主要是判断是否合适。

如果connection合适的话,会调用streamAllocation去acquire,这里向connection的allocations里面加了一个StreamAllocation的弱引用,还记得HTTP2的内容不,HTTP可是没有多个StreamAllocation的。

  public void acquire(RealConnection connection, boolean reportedAcquired) {
    assert (Thread.holdsLock(connectionPool));
    if (this.connection != null) throw new IllegalStateException();

    this.connection = connection;
    this.reportedAcquired = reportedAcquired;
    connection.allocations.add(new StreamAllocationReference(this, callStackTrace));
  }

deduplicate方法

    // 如果可能,将 streamAllocation 持有的连接替换为共享连接。当同时创建多个多路复用连接时,这会恢复
    // deduplicate在Internal.instance调用,最终用在StreamAllocation的findConnection中,防止异步创建两个相同地址的connection
    @Nullable
    Socket deduplicate(Address address, StreamAllocation streamAllocation) {
        assert (Thread.holdsLock(this));
        for (RealConnection connection : connections) {
            // isMultiplexed返回http2Connection!=null,只有HTTP2才复用连接
            if (connection.isEligible(address, null)
                    && connection.isMultiplexed()
                    && connection != streamAllocation.connection()) {
                return streamAllocation.releaseAndAcquire(connection);
            }
        }
        return null;
    }

这个方法是在streamAllocation中使用的,用来消除重复情况,可能会异步创建多个同地址的连接。这里也是通过Internal拐了个弯。

    Socket socket = null;
    synchronized (connectionPool) {
      reportedAcquired = true;

      // Pool the connection.
      Internal.instance.put(connectionPool, result);

      // If another multiplexed connection to the same address was created concurrently, then
      // release this connection and acquire that one.
      if (result.isMultiplexed()) {
        socket = Internal.instance.deduplicate(connectionPool, address, this);
        result = connection;
      }
    }

connectionBecameIdle方法

    // 通过Internal在StreamAllocation的deallocate(解除分配)方法中调用
    // 通知此连接已变为空闲。如果连接已从池中删除并应关闭,则返回 true。
    boolean connectionBecameIdle(RealConnection connection) {
        assert (Thread.holdsLock(this));
        if (connection.noNewStreams || maxIdleConnections == 0) {
            connections.remove(connection);
            return true;
        } else {
            // 唤醒cleanup线程,我们可能已经超过空闲连接限制
            // 在cleanup方法中synchronized (this)了,其他地方对connection加了对象锁的话,会卡住cleanupRunnable的cleanup方法
            notifyAll(); // Awake the cleanup thread: we may have exceeded the idle connection limit.
            return false;
        }
    }

注释已经写了,这个也是通过Internal来给okhttpClient扩展的,在解除分配的时候用来清除无效connection,或者释放当前ConnectionPool的锁,其他代码地方大量对ConnectionPool进行了锁,释放对象锁,让清除线程能正常工作。

evictAll方法

    // 向外提供的方法,通过client.connectionPool().evictAll()调用
    /** Close and remove all idle connections in the pool. */
    public void evictAll() {
        List<RealConnection> evictedConnections = new ArrayList<>();
        synchronized (this) {
            for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
                RealConnection connection = i.next();
                if (connection.allocations.isEmpty()) {
                    connection.noNewStreams = true;
                    evictedConnections.add(connection);
                    i.remove();
                }
            }
        }

        for (RealConnection connection : evictedConnections) {
            closeQuietly(connection.socket());
        }
    }

前面的方法都是在Internal中拐弯,向外提供,但是这个evictAll好像不一样,这个我没有找到调用的地方,不过看了OkHttpClient的说明,里面说到了用法:

Clear the connection pool with evictAll(). Note that the connection pool's daemon thread may not exit immediately.

     client.connectionPool().evictAll();

到这ConnectionPool就说完了,整个类加注释也就300多行,不多里面出现了一些前面没注意的东西,下面还得继续说说。

Internal

前面已经说过了,Internal是在OkHttpClient里面的一个匿名类,并在static块中完成初始化,它是用来给okhttp3增加能力的工具,,说明如下:

/**
 * Escalate internal APIs in {@code okhttp3} so they can be used from OkHttp's implementation
 * packages. The only implementation of this interface is in {@link OkHttpClient}.
 */
 // 升级 okhttp3 中的内部 API,以便它们可以从 OkHttp 的实现包中使用。这个接口的唯一实现是在 OkHttpClient 中。

里面的方法下面整理了一下,就这么几类:

builder.addLenient(line);
builder.addLenient(name, value);
builder.setInternalCache(internalCache);

pool.connectionBecameIdle(connection);
pool.get(address, streamAllocation, route);
pool.deduplicate(address, streamAllocation);
pool.put(connection);
connectionPool.routeDatabase;

address_a.equalsNonHost(address_b);

responseBuilder.code;

tlsConfiguration.apply(sslSocket, isFallback);

e.getMessage().startsWith(HttpUrl.Builder.INVALID_HOST);

((RealCall) call).streamAllocation();
((RealCall) call).timeoutExit(e);
RealCall.newRealCall(client, originalRequest, true);

StreamAllocation再补充

看完这节的ConnectionPool,我就发现前面StreamAllocation有些比较难理解的东西,真就拨云见日了,下面再对StreamAllocation做一些补充。

reportedAcquired

reportedAcquired是个数据标志,前面我们跳过了,现在终于知道了,就是connection的allocations添加了StreamAllocation的弱引用嘛,上面有讲到。

releaseIfNoNewStreams方法

  private Socket releaseIfNoNewStreams() {
    assert (Thread.holdsLock(connectionPool));
    RealConnection allocatedConnection = this.connection;
    if (allocatedConnection != null && allocatedConnection.noNewStreams) {
      return deallocate(false, false, true);
    }
    return null;
  }

这里看不出什么,再看下deallocate方法:

// 释放此分配持有的资源。如果分配了足够的资源,连接将被分离或关闭。调用者必须在连接池上同步。
private Socket deallocate(boolean noNewStreams, boolean released, boolean streamFinished) {
    assert (Thread.holdsLock(connectionPool));

    // 注意这里streamFinished将this.codec置空了,下面逻辑用到了
    if (streamFinished) {
      this.codec = null;
    }
    if (released) {
      this.released = true;
    }
    Socket socket = null;
    if (connection != null) {
      if (noNewStreams) {
        connection.noNewStreams = true;
      }
      // 不再使用或不能使用connection了
      if (this.codec == null && (this.released || connection.noNewStreams)) {
        // 释放连接
        release(connection);
        if (connection.allocations.isEmpty()) {
          connection.idleAtNanos = System.nanoTime();
          if (Internal.instance.connectionBecameIdle(connectionPool, connection)) {
            socket = connection.socket();
          }
        }
        // 注意这里对StreamAllocation持有的connection置空了
        connection = null;
      }
    }
    return socket;
  }

这里代码一大堆,实际就做了一件事,就是关闭流(分离流),release关闭了当前connection的当前StreamAllocation。后面判断了下connection里面是否还有流,空了的话,设置连接的最新空闲的时刻,并调用connectionBecameIdle方法,在ConnectionPool中清除连接。

这里清除成功会返回true,所以这里得到的socket就是一个没用使用到的socket了,回到releaseIfNoNewStreams被调用的findConnection方法,这里会把它关闭了

结合releaseIfNoNewStreams这个名字,再来理解下这个过程,也就是说StreamAllocation里面持有的一个connection不能新建流了,且没有其他的StreamAllocation在使用这个connection了,那就会关闭这个socket,这个过程在StreamAllocation创建connection的时候,目的时用于清除旧的且没有用了的connection.

streamFinished方法

public void streamFinished(boolean noNewStreams, HttpCodec codec, long bytesRead, IOException e) {
    eventListener.responseBodyEnd(call, bytesRead);

    Socket socket;
    Connection releasedConnection;
    boolean callEnd;
    synchronized (connectionPool) {
      // 这里保证了this.codec==codec!=null
      if (codec == null || codec != this.codec) {
        throw new IllegalStateException("expected " + this.codec + " but was " + codec);
      }
      
      // 要复用这个connection
      if (!noNewStreams) {
        connection.successCount++;
      }
      
      // 核心代码
      releasedConnection = connection;
      socket = deallocate(noNewStreams, false, true);
      
      // deallocate会将connection关闭并置空,但是条件如下:
      // if (this.codec == null && (this.released || connection.noNewStreams)) 
      if (connection != null) releasedConnection = null;
      callEnd = this.released;
    }
    // 这里socket只有在connection里面的流空了才会返回,否则是空的
    closeQuietly(socket);
    if (releasedConnection != null) {
      // releasedConnection就是为了做下记录?
      eventListener.connectionReleased(call, releasedConnection);
    }
    
    //eventListener代码 。。。
  }

这个方法主要是用在HttpCodec里面(endOfInput),用来关闭流,下面是Http1Codec调用的地方:

    // 关闭缓存条目并使套接字可供重用。这应该在到达主体末尾时调用。
    protected final void endOfInput(boolean reuseConnection, IOException e) throws IOException {
      if (state == STATE_CLOSED) return;
      if (state != STATE_READING_RESPONSE_BODY) throw new IllegalStateException("state: " + state);

      detachTimeout(timeout);

      state = STATE_CLOSED;
      if (streamAllocation != null) {
        // noNewStreams=!reuseConnection,所以复用socket就不传noNewStreams
        streamAllocation.streamFinished(!reuseConnection, Http1Codec.this, bytesRead, e);
      }
    }
  }

在回到streamFinished里面,实际它就是调用了下上面的deallocate方法,这里有个releasedConnection让我很懵逼,一般来说deallocate方法会让connection置空:

      if (this.codec == null && (this.released || connection.noNewStreams)) {
        release(connection);
        if (connection.allocations.isEmpty()) {
          connection.idleAtNanos = System.nanoTime();
          if (Internal.instance.connectionBecameIdle(connectionPool, connection)) {
            socket = connection.socket();
          }
        }
        connection = null;
      }

如果connection!=null那就是这里条件没符合,但是最后releasedConnection只是用eventListener记录了下,也就是connection==null记录二零关闭的那个connection,connection!=null就不记录,所以就是指上面的条件没关闭connection吗?好吧,这里就是为了记录下吧。

release和noNewStreams方法

讲完streamFinished方法,下面还有两个方法类似的,都是和deallocate有关系,一并讲了吧,就是传入deallocate的参数不一样,

  public void release() {
    Socket socket;
    Connection releasedConnection;
    synchronized (connectionPool) {
      releasedConnection = connection;
      // 这里会不会触发对connection的清理,取决于this.codec是不是null
      socket = deallocate(false, true, false);
      if (connection != null) releasedConnection = null;
    }
    // 这里socket只有在connection里面的流空了才会返回,否则是空的
    closeQuietly(socket);
    if (releasedConnection != null) {
      Internal.instance.timeoutExit(call, null);
      eventListener.connectionReleased(call, releasedConnection);
      eventListener.callEnd(call);
    }
  }

这里deallocate传进去的参数不会触发上一小节说的清理,还是要看release前面会不会对this.codec置空,看了下用的地方都是在RetryAndFollowUpInterceptor里面,那就只是关闭了当前streamAllocation,并没有关闭这个连接,还是可以复用的。

  /** Forbid new streams from being created on the connection that hosts this allocation. */
  public void noNewStreams() {
    Socket socket;
    Connection releasedConnection;
    synchronized (connectionPool) {
      releasedConnection = connection;
      socket = deallocate(true, false, false);
      if (connection != null) releasedConnection = null;
    }
    // 这里socket只有在connection里面的流空了才会返回,否则是空的
    closeQuietly(socket);
    if (releasedConnection != null) {
      eventListener.connectionReleased(call, releasedConnection);
    }
  }

而noNewStreams传进去的参数,也是不能保证connection被置空,它会让connection变为noNewStreams,估计是让线程池的cleanup线程自行处理吧。

这里还要注意下这个socket的关闭,在deallocate里面清理了connection的流,还需要connection的流空了才会返回这个socket,还要复用呢,哪能随便关掉!

streamFailed方法

下面又是一个特别长的方法,streamFailed一听名字应该就是知道是对创建流失败后的操作,用在RetryAndFollowUpInterceptor里面,对releaseConnection的情况进行处理:

public void streamFailed(IOException e) {
    Socket socket;
    Connection releasedConnection;
    boolean noNewStreams = false;

    // 锁住连接池?为什么不锁住connection呢?不让其他地方get、put这个connection?
    synchronized (connectionPool) {
      if (e instanceof StreamResetException) {
        ErrorCode errorCode = ((StreamResetException) e).errorCode;
        // 重试一次
        if (errorCode == ErrorCode.REFUSED_STREAM) {
          // Retry REFUSED_STREAM errors once on the same connection.
          refusedStreamCount++;
          if (refusedStreamCount > 1) {
            noNewStreams = true;
            route = null;
          }
          
          // 不让再创建流
        } else if (errorCode != ErrorCode.CANCEL) {
          // Keep the connection for CANCEL errors. Everything else wants a fresh connection.
          noNewStreams = true;
          route = null;
        }
        
        // connection可以了,但是HTTP或者连接关闭异常?
      } else if (connection != null
          && (!connection.isMultiplexed() || e instanceof ConnectionShutdownException)) {
        // 不要忽略了这句,不再创建流  
        noNewStreams = true;

        // 是route就连接失败了吗?connection.successCount在上面的streamFinished增加的,就是没成功过吧
        // If this route hasn't completed a call, avoid it for new connections.
        if (connection.successCount == 0) {
          if (route != null && e != null) {
            routeSelector.connectFailed(route, e);
          }
          route = null;
        }
      }
      
      // 又来一遍deallocate,注意传入了streamFinished,会置空codec,可能触发对connection里面流的关闭
      releasedConnection = connection;
      socket = deallocate(noNewStreams, false, true);
      if (connection != null || !reportedAcquired) releasedConnection = null;
    }

    // 这里socket只有在connection里面的流空了才会返回,否则是空的
    closeQuietly(socket);
    if (releasedConnection != null) {
      eventListener.connectionReleased(call, releasedConnection);
    }
  }

大致整理了下,就是操作了下noNewStreams和route,调用了一下deallocate,传入了streamFinished,会置空codec,可能触发对connection里面流的关闭。

为什么是可能,因为还要看noNewStreams啊,noNewStreams和released都传false就不会触发,第一个ErrorCode.REFUSED_STREAM重试的时候就不会触发。

releaseAndAcquire方法

看到这我也头疼了,还好这里是StreamAllocation最后一个重要方法了,前面已经讲到release方法了,是对当前StreamAllocation从对应connection中移除的功能,而下面这个这方法则是让StreamAllocation释放旧connection,更换新的connection:

  // 释放此连接持有的连接并改为获取 newConnection。只有在保持的连接是新连接但被 newConnection 复制的情况下调用它才是安全的。这通常发生在同时连接到 HTTP/2 网络服务器时。
  public Socket releaseAndAcquire(RealConnection newConnection) {
    assert (Thread.holdsLock(connectionPool));
    // 旧的connection里面只允许有一个allocation元素,且没有在使用流了,多个allocation元素也没法这样换啊!这里会release的。
    if (codec != null || connection.allocations.size() != 1) throw new IllegalStateException();

    // 直接把旧连接里面的StreamAllocation弱引用放到新的connection里面就行了
    // Release the old connection.
    Reference<StreamAllocation> onlyAllocation = connection.allocations.get(0);
    
    // 这里就传了noNewStreams=true,需要注意这里codec==null了,里面会触发对旧connection的清理,清理了就会得到socket
    Socket socket = deallocate(true, false, false);

    // Acquire the new connection.
    this.connection = newConnection;
    newConnection.allocations.add(onlyAllocation);

    return socket;
  }

这里就是deallocate最后一个调用地方了,和上面五个类似,上面注释写的很清楚了,deallocate应该能理解了吧。至于release和互换的逻辑比较简单。

这个方法唯一用的地方是在ConnectionPool的deduplicate方法,上面也有讲,再提一下,估计就好理解了,就是异步可能创建了相同的connection,调整一下。

    // 如果可能,将 streamAllocation 持有的连接替换为共享连接。当同时创建多个多路复用连接时,这会恢复
    // deduplicate在Internal.instance调用,最终用在StreamAllocation的findConnection中,防止异步创建两个相同地址的connection
    @Nullable
    Socket deduplicate(Address address, StreamAllocation streamAllocation) {
        assert (Thread.holdsLock(this));
        for (RealConnection connection : connections) {
            // isMultiplexed返回http2Connection!=null,只有HTTP2才复用连接
            if (connection.isEligible(address, null)
                    && connection.isMultiplexed()
                    && connection != streamAllocation.connection()) {
                return streamAllocation.releaseAndAcquire(connection);
            }
        }
        return null;
    }

小结

这篇文章写的又有点长了,写了ConnectionPool和StreamAllocation比较多的内容,也附带讲了下Internal,看完ConnectionPool回过头来再看StreamAllocation真就理解通畅了好多。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值