Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions okhttp/api/android/okhttp.api
Original file line number Diff line number Diff line change
Expand Up @@ -343,10 +343,15 @@ public abstract interface class okhttp3/CompressionInterceptor$DecompressionAlgo
}

public abstract interface class okhttp3/Connection {
public abstract fun callCount ()I
public abstract fun connectAtMillis ()J
public abstract fun handshake ()Lokhttp3/Handshake;
public abstract fun idleAtMillis ()Ljava/lang/Long;
public abstract fun noNewExchanges ()Z
public abstract fun protocol ()Lokhttp3/Protocol;
public abstract fun route ()Lokhttp3/Route;
public abstract fun socket ()Ljava/net/Socket;
public abstract fun successCount ()I
}

public final class okhttp3/ConnectionPool {
Expand Down
5 changes: 5 additions & 0 deletions okhttp/api/jvm/okhttp.api
Original file line number Diff line number Diff line change
Expand Up @@ -343,10 +343,15 @@ public abstract interface class okhttp3/CompressionInterceptor$DecompressionAlgo
}

public abstract interface class okhttp3/Connection {
public abstract fun callCount ()I
public abstract fun connectAtMillis ()J
public abstract fun handshake ()Lokhttp3/Handshake;
public abstract fun idleAtMillis ()Ljava/lang/Long;
public abstract fun noNewExchanges ()Z
public abstract fun protocol ()Lokhttp3/Protocol;
public abstract fun route ()Lokhttp3/Route;
public abstract fun socket ()Ljava/net/Socket;
public abstract fun successCount ()I
}

public final class okhttp3/ConnectionPool {
Expand Down
30 changes: 30 additions & 0 deletions okhttp/src/commonJvmAndroid/kotlin/okhttp3/Connection.kt
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,34 @@ interface Connection {
* [Protocol.HTTP_1_0].
*/
fun protocol(): Protocol

/**
* Returns the wall-clock time (epoch millis) when this connection was created. This is the time
* the TCP and TLS handshakes completed.
*/
fun connectAtMillis(): Long

/**
* Returns the wall-clock time (epoch millis) when this connection became idle, or null if it is
* currently active (carrying one or more calls).
*/
fun idleAtMillis(): Long?

/**
* Returns the number of exchanges successfully completed on this connection. Each completed
* request/response pair increments this count.
*/
fun successCount(): Int

/**
* Returns the number of calls currently carried by this connection. This is 0 when the
* connection is idle, and may be greater than 1 for HTTP/2 connections.
*/
fun callCount(): Int

/**
* Returns true if this connection will not accept new exchanges. This may be because the
* connection has been marked for closure, or because an error has occurred on this connection.
*/
fun noNewExchanges(): Boolean
}
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,7 @@ class ConnectPlan internal constructor(
// Do nothing.
}

override fun noNewExchanges() {
override fun prohibitNewExchanges() {
// Do nothing.
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ class Exchange(
}

fun noNewExchangesOnConnection() {
codec.carrier.noNewExchanges()
codec.carrier.prohibitNewExchanges()
}

fun cancel() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ class RealCall(
check(this.connection == null)
this.connection = connection
connection.calls.add(CallReference(this, callStackTrace))
connection.idleAtEpochMillis = null
}

/**
Expand Down Expand Up @@ -452,6 +453,7 @@ class RealCall(

if (calls.isEmpty()) {
connection.idleAtNs = System.nanoTime()
connection.idleAtEpochMillis = System.currentTimeMillis()
if (connectionPool.connectionBecameIdle(connection)) {
return connection.socket()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,12 @@ class RealConnection internal constructor(
/** Timestamp when `allocations.size()` reached zero. Also assigned upon initial connection. */
var idleAtNs = Long.MAX_VALUE

/** Wall-clock time (epoch millis) when the connection was created. */
internal var connectAtEpochMillis: Long = 0L

/** Wall-clock time (epoch millis) when the connection became idle, or null if active. */
internal var idleAtEpochMillis: Long? = null

/**
* Returns true if this is an HTTP/2 connection. Such connections can be used in multiple HTTP
* requests simultaneously.
Expand All @@ -129,7 +135,7 @@ class RealConnection internal constructor(
get() = http2Connection != null

/** Prevent further exchanges from being created on this connection. */
override fun noNewExchanges() {
override fun prohibitNewExchanges() {
withLock {
noNewExchanges = true
}
Expand All @@ -152,6 +158,8 @@ class RealConnection internal constructor(
@Throws(IOException::class)
fun start() {
idleAtNs = System.nanoTime()
connectAtEpochMillis = System.currentTimeMillis()
idleAtEpochMillis = connectAtEpochMillis
if (protocol == Protocol.HTTP_2 || protocol == Protocol.H2_PRIOR_KNOWLEDGE) {
startHttp2()
}
Expand Down Expand Up @@ -284,7 +292,7 @@ class RealConnection internal constructor(

internal fun useAsSocket() {
javaNetSocket.soTimeout = 0
noNewExchanges()
prohibitNewExchanges()
}

override fun route(): Route = route
Expand Down Expand Up @@ -416,6 +424,16 @@ class RealConnection internal constructor(

override fun protocol(): Protocol = protocol

override fun connectAtMillis(): Long = connectAtEpochMillis

override fun idleAtMillis(): Long? = withLock { idleAtEpochMillis }

override fun successCount(): Int = withLock { successCount }

override fun callCount(): Int = withLock { calls.size }

override fun noNewExchanges(): Boolean = withLock { noNewExchanges }

override fun toString(): String =
"Connection{${route.address.url.host}:${route.address.url.port}," +
" proxy=${route.proxy}" +
Expand Down Expand Up @@ -456,6 +474,8 @@ class RealConnection internal constructor(
connectionListener = ConnectionListener.NONE,
)
result.idleAtNs = idleAtNs
result.connectAtEpochMillis = System.currentTimeMillis()
result.idleAtEpochMillis = result.connectAtEpochMillis
return result
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ class RealConnectionPool internal constructor(
// If this was the last allocation, the connection is eligible for immediate eviction.
if (references.isEmpty()) {
connection.idleAtNs = now - keepAliveDurationNs
connection.idleAtEpochMillis = System.currentTimeMillis()
return 0
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ interface ExchangeCodec {
e: IOException?,
)

fun noNewExchanges()
fun prohibitNewExchanges()

fun cancel()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ class Http1ExchangeCodec(
private fun newUnknownLengthSource(url: HttpUrl): Source {
check(state == STATE_OPEN_RESPONSE_BODY) { "state: $state" }
state = STATE_READING_RESPONSE_BODY
carrier.noNewExchanges()
carrier.prohibitNewExchanges()
return UnknownLengthSource(url)
}

Expand Down Expand Up @@ -396,7 +396,7 @@ class Http1ExchangeCodec(
try {
socket.source.read(sink, byteCount)
} catch (e: IOException) {
carrier.noNewExchanges()
carrier.prohibitNewExchanges()
responseBodyComplete(TRAILERS_RESPONSE_BODY_TRUNCATED)
throw e
}
Expand Down Expand Up @@ -440,7 +440,7 @@ class Http1ExchangeCodec(

val read = super.read(sink, minOf(bytesRemaining, byteCount))
if (read == -1L) {
carrier.noNewExchanges() // The server didn't supply the promised content length.
carrier.prohibitNewExchanges() // The server didn't supply the promised content length.
val e = ProtocolException("unexpected end of stream")
responseBodyComplete(TRAILERS_RESPONSE_BODY_TRUNCATED)
throw e
Expand All @@ -459,7 +459,7 @@ class Http1ExchangeCodec(
if (bytesRemaining != 0L &&
!discard(ExchangeCodec.DISCARD_STREAM_TIMEOUT_MILLIS, MILLISECONDS)
) {
carrier.noNewExchanges() // Unread bytes remain on the stream.
carrier.prohibitNewExchanges() // Unread bytes remain on the stream.
responseBodyComplete(TRAILERS_RESPONSE_BODY_TRUNCATED)
}

Expand Down Expand Up @@ -489,7 +489,7 @@ class Http1ExchangeCodec(

val read = super.read(sink, minOf(byteCount, bytesRemainingInChunk))
if (read == -1L) {
carrier.noNewExchanges() // The server didn't supply the promised chunk length.
carrier.prohibitNewExchanges() // The server didn't supply the promised chunk length.
val e = ProtocolException("unexpected end of stream")
responseBodyComplete(TRAILERS_RESPONSE_BODY_TRUNCATED)
throw e
Expand Down Expand Up @@ -528,7 +528,7 @@ class Http1ExchangeCodec(
if (hasMoreChunks &&
!discard(ExchangeCodec.DISCARD_STREAM_TIMEOUT_MILLIS, MILLISECONDS)
) {
carrier.noNewExchanges() // Unread bytes remain on the stream.
carrier.prohibitNewExchanges() // Unread bytes remain on the stream.
responseBodyComplete(TRAILERS_RESPONSE_BODY_TRUNCATED)
}
closed = true
Expand Down
26 changes: 26 additions & 0 deletions okhttp/src/jvmTest/kotlin/okhttp3/ConnectionListenerTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ import assertk.assertThat
import assertk.assertions.containsExactly
import assertk.assertions.hasMessage
import assertk.assertions.isEqualTo
import assertk.assertions.isFalse
import assertk.assertions.isGreaterThan
import assertk.assertions.isIn
import assertk.assertions.isNotNull
import java.io.IOException
import java.net.InetSocketAddress
import java.net.UnknownHostException
Expand Down Expand Up @@ -340,6 +343,29 @@ open class ConnectionListenerTest {
assertThat(event.connection.route().proxy).isEqualTo(proxy)
}

@Test
@Throws(IOException::class)
fun connectionMetrics() {
server.enqueue(MockResponse())
val call =
client.newCall(
Request
.Builder()
.url(server.url("/"))
.build(),
)
val response = call.execute()
assertThat(response.code).isEqualTo(200)
response.body.close()
val event = listener.removeUpToEvent(ConnectionEvent.ConnectEnd::class.java)
val connection = event.connection
assertThat(connection.connectAtMillis()).isGreaterThan(0L)
assertThat(connection.successCount()).isEqualTo(1)
assertThat(connection.callCount()).isEqualTo(0)
assertThat(connection.idleAtMillis()).isNotNull()
assertThat(connection.noNewExchanges()).isFalse()
}

private fun enableTls() {
client =
client
Expand Down
10 changes: 10 additions & 0 deletions okhttp/src/jvmTest/kotlin/okhttp3/KotlinSourceModernTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,16 @@ class KotlinSourceModernTest {
override fun handshake(): Handshake? = TODO()

override fun protocol(): Protocol = TODO()

override fun connectAtMillis(): Long = TODO()

override fun idleAtMillis(): Long? = TODO()

override fun successCount(): Int = TODO()

override fun callCount(): Int = TODO()

override fun noNewExchanges(): Boolean = TODO()
}
}

Expand Down