All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.squareup.okhttp.internal.allocations.Connection Maven / Gradle / Ivy

There is a newer version: 2.7.5
Show newest version
/*
 * Copyright (C) 2015 Square, Inc.
 *
 * Licensed 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 com.squareup.okhttp.internal.allocations;

import com.squareup.okhttp.ConnectionPool;
import com.squareup.okhttp.internal.Internal;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * Each connection can carry a varying number streams, depending on the underlying protocol being
 * used. HTTP/1.x connections can carry either zero or one streams. HTTP/2 connections can carry any
 * number of streams, dynamically configured with {@code SETTINGS_MAX_CONCURRENT_STREAMS}. A
 * connection currently carrying zero streams is an idle stream. We keep it alive because reusing an
 * existing connection is typically faster than establishing a new one.
 *
 * 

When a single logical call requires multiple streams due to redirects or authorization * challenges, we prefer to use the same physical connection for all streams in the sequence. There * are potential performance and behavior consequences to this preference. To support this feature, * this class separates allocations from streams. An allocation is created by a call, * used for one or more streams, and then released. An allocated connection won't be stolen by * other calls while a redirect or authorization challenge is being handled. * *

When the maximum concurrent streams limit is reduced, some allocations will be rescinded. * Attempting to create new streams on these allocations will fail. * *

Note that an allocation may be released before its stream is completed. This is intended to * make bookkeeping easier for the caller: releasing the allocation as soon as the terminal stream * has been found. But only complete the stream once its data stream has been exhausted. */ public final class Connection { private final ConnectionPool pool; private final List allocations = new ArrayList<>(); private int allocationLimit = 1; private boolean noNewAllocations; /** Nanotime that this connection most recently became idle. */ long idleAt = Long.MAX_VALUE; public Connection(ConnectionPool pool) { this.pool = pool; } /** * Attempts to reserves an allocation on this connection for a call. Returns null if no * allocation is available. */ public StreamAllocation reserve(String name) { synchronized (pool) { if (noNewAllocations || allocations.size() >= allocationLimit) return null; StreamAllocation result = new StreamAllocation(); allocations.add(new StreamAllocationReference(result, name)); return result; } } /** * Release the reservation on {@code streamAllocation}. If a stream is currently active it may * continue to use this connection until it is complete. */ public void release(StreamAllocation streamAllocation) { synchronized (pool) { if (streamAllocation.released) throw new IllegalStateException("already released"); streamAllocation.released = true; if (streamAllocation.stream == null) { remove(streamAllocation); } } } private void remove(StreamAllocation streamAllocation) { for (int i = 0, size = allocations.size(); i < size; i++) { StreamAllocationReference weakReference = allocations.get(i); if (weakReference.get() == streamAllocation) { allocations.remove(i); if (allocations.isEmpty()) { idleAt = System.nanoTime(); // TODO(jwilson): schedule a cleanup thread if allocationLimit == 0. } return; } } throw new IllegalArgumentException("unexpected allocation: " + streamAllocation); } /** * Prevents new streams from being created on this connection. This is similar to setting the * allocation limit to 0, except that this call is permanent. */ public void noNewStreams() { synchronized (pool) { noNewAllocations = true; for (int i = 0; i < allocations.size(); i++) { allocations.get(i).rescind(); } } } /** * Sets the maximum number of streams that this connection will carry. Existing streams will not * be interrupted, but existing allocations may be prevented from creating new streams. */ public void setAllocationLimit(int allocationLimit) { synchronized (pool) { if (allocationLimit < 0) throw new IllegalArgumentException(); this.allocationLimit = allocationLimit; for (int i = allocationLimit; i < allocations.size(); i++) { allocations.get(i).rescind(); } } } /** * Look through the allocations held by this connection and confirm that they're all still * alive. If they aren't, we have a leaked allocation. In which case we prevent this connection * from taking new allocations so that it may gracefully shut down. */ public void pruneLeakedAllocations() { synchronized (pool) { for (Iterator i = allocations.iterator(); i.hasNext(); ) { StreamAllocationReference reference = i.next(); if (reference.get() == null) { Internal.logger.warning("Call " + reference.name + " leaked a connection. Did you forget to close a response body?"); noNewAllocations = true; i.remove(); if (allocations.isEmpty()) { idleAt = System.nanoTime(); // TODO(jwilson): schedule a cleanup thread if allocationLimit == 0. } } } } } /** Returns the number of allocations currently held. */ int size() { synchronized (pool) { return allocations.size(); } } /** Links a stream to a connection. */ public final class StreamAllocation { /** True if the call is done with this allocation. */ private boolean released; /** * Non-null if a stream is using this allocation. This may be non-null even after the * allocation has been released, because the application may continue to read the response body * long after redirects and authorization challenges have all been handled. */ private Stream stream; /** * True if this allocation has been taken away by the connection. The current stream may * proceed but further streams need new allocations. */ private boolean rescinded; private StreamAllocation() { } /** Returns a new stream, or null if this allocation has been rescinded. */ public Stream newStream(String name) { synchronized (pool) { if (this.stream != null || released) throw new IllegalStateException(); if (rescinded) return null; this.stream = new Stream(name); return this.stream; } } public void streamComplete(Stream stream) { synchronized (pool) { if (stream == null || stream != this.stream) throw new IllegalArgumentException(); this.stream = null; if (released) { remove(this); } } } } private static final class StreamAllocationReference extends WeakReference { private final String name; public StreamAllocationReference(StreamAllocation streamAllocation, String name) { super(streamAllocation); this.name = name; } public void rescind() { StreamAllocation streamAllocation = get(); if (streamAllocation != null) { streamAllocation.rescinded = true; } } } public static class Stream { public final String name; public Stream(String name) { this.name = name; } @Override public String toString() { return name; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy