1
0
mirror of https://github.com/square/okhttp.git synced 2025-08-08 23:42:08 +03:00

Check for matching ConnectionEvent (#7612)

* Check for matching ConnectionEvent

* Fixes for API file

* Fix tests

* Fix cancel event logic
This commit is contained in:
Yuri Schimke
2023-01-03 02:24:01 +10:00
committed by GitHub
parent 847b5af240
commit 13ac56ca0a
9 changed files with 137 additions and 52 deletions

View File

@@ -30,8 +30,8 @@ sealed class CallEvent {
val name: String
get() = javaClass.simpleName
/** Returns the open event that this close event closes, or null if this is not a close event. */
open fun closes(timestampNs: Long): CallEvent? = null
/** Returns if the event closes this event, or null if this is no open event. */
open fun closes(event: CallEvent): Boolean? = null
data class ProxySelectStart(
override val timestampNs: Long,
@@ -44,7 +44,10 @@ sealed class CallEvent {
override val call: Call,
val url: HttpUrl,
val proxies: List<Proxy>?
) : CallEvent()
) : CallEvent() {
override fun closes(event: CallEvent): Boolean =
event is ProxySelectStart && call == event.call && url == event.url
}
data class DnsStart(
override val timestampNs: Long,
@@ -58,7 +61,8 @@ sealed class CallEvent {
val domainName: String,
val inetAddressList: List<InetAddress>
) : CallEvent() {
override fun closes(timestampNs: Long): CallEvent = DnsStart(timestampNs, call, domainName)
override fun closes(event: CallEvent): Boolean =
event is DnsStart && call == event.call && domainName == event.domainName
}
data class ConnectStart(
@@ -75,8 +79,8 @@ sealed class CallEvent {
val proxy: Proxy?,
val protocol: Protocol?
) : CallEvent() {
override fun closes(timestampNs: Long): CallEvent =
ConnectStart(timestampNs, call, inetSocketAddress, proxy)
override fun closes(event: CallEvent): Boolean =
event is ConnectStart && call == event.call && inetSocketAddress == event.inetSocketAddress && proxy == event.proxy
}
data class ConnectFailed(
@@ -87,8 +91,8 @@ sealed class CallEvent {
val protocol: Protocol?,
val ioe: IOException
) : CallEvent() {
override fun closes(timestampNs: Long): CallEvent =
ConnectStart(timestampNs, call, inetSocketAddress, proxy)
override fun closes(event: CallEvent): Boolean =
event is ConnectStart && call == event.call && inetSocketAddress == event.inetSocketAddress && proxy == event.proxy
}
data class SecureConnectStart(
@@ -101,7 +105,8 @@ sealed class CallEvent {
override val call: Call,
val handshake: Handshake?
) : CallEvent() {
override fun closes(timestampNs: Long): CallEvent = SecureConnectStart(timestampNs, call)
override fun closes(event: CallEvent): Boolean =
event is SecureConnectStart && call == event.call
}
data class ConnectionAcquired(
@@ -115,7 +120,8 @@ sealed class CallEvent {
override val call: Call,
val connection: Connection
) : CallEvent() {
override fun closes(timestampNs: Long): CallEvent = ConnectionAcquired(timestampNs, call, connection)
override fun closes(event: CallEvent): Boolean =
event is ConnectionAcquired && call == event.call && connection == event.connection
}
data class CallStart(
@@ -127,14 +133,18 @@ sealed class CallEvent {
override val timestampNs: Long,
override val call: Call
) : CallEvent() {
override fun closes(timestampNs: Long): CallEvent = CallStart(timestampNs, call)
override fun closes(event: CallEvent): Boolean =
event is CallStart && call == event.call
}
data class CallFailed(
override val timestampNs: Long,
override val call: Call,
val ioe: IOException
) : CallEvent()
) : CallEvent() {
override fun closes(event: CallEvent): Boolean =
event is CallStart && call == event.call
}
data class Canceled(
override val timestampNs: Long,
@@ -151,7 +161,8 @@ sealed class CallEvent {
override val call: Call,
val headerLength: Long
) : CallEvent() {
override fun closes(timestampNs: Long): CallEvent = RequestHeadersStart(timestampNs, call)
override fun closes(event: CallEvent): Boolean =
event is RequestHeadersStart && call == event.call
}
data class RequestBodyStart(
@@ -164,14 +175,19 @@ sealed class CallEvent {
override val call: Call,
val bytesWritten: Long
) : CallEvent() {
override fun closes(timestampNs: Long): CallEvent = RequestBodyStart(timestampNs, call)
override fun closes(event: CallEvent): Boolean =
event is RequestBodyStart && call == event.call
}
data class RequestFailed(
override val timestampNs: Long,
override val call: Call,
val ioe: IOException
) : CallEvent()
) : CallEvent() {
override fun closes(event: CallEvent): Boolean =
event is RequestHeadersStart && call == event.call
}
data class ResponseHeadersStart(
override val timestampNs: Long,
@@ -183,7 +199,8 @@ sealed class CallEvent {
override val call: Call,
val headerLength: Long
) : CallEvent() {
override fun closes(timestampNs: Long): CallEvent = RequestHeadersStart(timestampNs, call)
override fun closes(event: CallEvent): Boolean =
event is ResponseHeadersStart && call == event.call
}
data class ResponseBodyStart(
@@ -196,7 +213,8 @@ sealed class CallEvent {
override val call: Call,
val bytesRead: Long
) : CallEvent() {
override fun closes(timestampNs: Long): CallEvent = ResponseBodyStart(timestampNs, call)
override fun closes(event: CallEvent): Boolean =
event is ResponseBodyStart && call == event.call
}
data class ResponseFailed(

View File

@@ -16,16 +16,17 @@
package okhttp3
import java.io.IOException
import java.net.InetAddress
import java.net.InetSocketAddress
import java.net.Proxy
import okhttp3.internal.SuppressSignatureCheck
/** Data classes that correspond to each of the methods of [ConnectionListener]. */
@SuppressSignatureCheck
sealed class ConnectionEvent {
abstract val timestampNs: Long
open val connection: Connection? = null
open val connection: Connection?
get() = null
/** Returns if the event closes this event, or null if this is no open event. */
open fun closes(event: ConnectionEvent): Boolean? = null
val name: String
get() = javaClass.simpleName
@@ -41,12 +42,20 @@ sealed class ConnectionEvent {
val route: Route,
val call: Call,
val exception: IOException
) : ConnectionEvent()
) : ConnectionEvent() {
override fun closes(event: ConnectionEvent): Boolean =
event is ConnectStart && call == event.call && route == event.route
}
data class ConnectEnd(
override val timestampNs: Long,
override val connection: Connection,
) : ConnectionEvent()
val route: Route,
val call: Call,
) : ConnectionEvent() {
override fun closes(event: ConnectionEvent): Boolean =
event is ConnectStart && call == event.call && route == event.route
}
data class ConnectionClosed(
override val timestampNs: Long,
@@ -63,7 +72,11 @@ sealed class ConnectionEvent {
override val timestampNs: Long,
override val connection: Connection,
val call: Call
) : ConnectionEvent()
) : ConnectionEvent() {
override fun closes(event: ConnectionEvent): Boolean =
event is ConnectionAcquired && connection == event.connection && call == event.call
}
data class NoNewExchanges(
override val timestampNs: Long,

View File

@@ -173,7 +173,9 @@ class OkHttpClientTestRule : BeforeEachCallback, AfterEachCallback {
// a test timeout failure.
val waitTime = (entryTime + 1_000_000_000L - System.nanoTime())
if (!queue.idleLatch().await(waitTime, TimeUnit.NANOSECONDS)) {
TaskRunner.INSTANCE.cancelAll()
synchronized (TaskRunner.INSTANCE) {
TaskRunner.INSTANCE.cancelAll()
}
fail<Unit>("Queue still active after 1000 ms")
}
}

View File

@@ -19,12 +19,20 @@ import java.util.Deque
import java.util.concurrent.ConcurrentLinkedDeque
import java.util.concurrent.TimeUnit
import okhttp3.ConnectionEvent.NoNewExchanges
import okhttp3.internal.connection.RealCall
import okhttp3.internal.connection.RealConnection
import okio.IOException
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.data.Offset
import org.junit.jupiter.api.Assertions
open class RecordingConnectionListener : ConnectionListener() {
open class RecordingConnectionListener(
/**
* An override to ignore the normal order that is enforced.
* EventListeners added by Interceptors will not see all events.
*/
private val enforceOrder: Boolean = true
) : ConnectionListener() {
val eventSequence: Deque<ConnectionEvent> = ConcurrentLinkedDeque()
private val forbiddenLocks = mutableSetOf<Any>()
@@ -41,7 +49,7 @@ open class RecordingConnectionListener : ConnectionListener() {
* Removes recorded events up to (and including) an event is found whose class equals [eventClass]
* and returns it.
*/
fun <T> removeUpToEvent(eventClass: Class<T>): T {
fun <T: ConnectionEvent> removeUpToEvent(eventClass: Class<T>): T {
val fullEventSequence = eventSequence.toList()
try {
while (true) {
@@ -106,15 +114,34 @@ open class RecordingConnectionListener : ConnectionListener() {
.isFalse()
}
if (enforceOrder) {
checkForStartEvent(e)
}
eventSequence.offer(e)
}
private fun checkForStartEvent(e: ConnectionEvent) {
if (eventSequence.isEmpty()) {
assertThat(e).isInstanceOf(ConnectionEvent.ConnectStart::class.java)
} else {
eventSequence.forEach loop@ {
when (e.closes(it)) {
null -> return // no open event
true -> return // found open event
false -> return@loop // this is not the open event so continue
}
}
Assertions.fail<Any>("event $e without matching start event")
}
}
override fun connectStart(route: Route, call: Call) = logEvent(ConnectionEvent.ConnectStart(System.nanoTime(), route, call))
override fun connectFailed(route: Route, call: Call, failure: IOException) = logEvent(ConnectionEvent.ConnectFailed(System.nanoTime(), route, call, failure))
override fun connectEnd(connection: Connection) {
logEvent(ConnectionEvent.ConnectEnd(System.nanoTime(), connection))
override fun connectEnd(connection: Connection, route: Route, call: RealCall) {
logEvent(ConnectionEvent.ConnectEnd(System.nanoTime(), connection, route, call))
}
override fun connectionClosed(connection: Connection) = logEvent(ConnectionEvent.ConnectionClosed(System.nanoTime(), connection))

View File

@@ -54,8 +54,16 @@ import okhttp3.CallEvent.SecureConnectStart
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.data.Offset
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Assertions.fail
import org.junit.jupiter.api.fail
open class RecordingEventListener : EventListener() {
open class RecordingEventListener(
/**
* An override to ignore the normal order that is enforced.
* EventListeners added by Interceptors will not see all events.
*/
private val enforceOrder: Boolean = true
) : EventListener() {
val eventSequence: Deque<CallEvent> = ConcurrentLinkedDeque()
private val forbiddenLocks = mutableListOf<Any>()
@@ -72,7 +80,7 @@ open class RecordingEventListener : EventListener() {
* Removes recorded events up to (and including) an event is found whose class equals [eventClass]
* and returns it.
*/
fun <T> removeUpToEvent(eventClass: Class<T>): T {
fun <T : CallEvent> removeUpToEvent(eventClass: Class<T>): T {
val fullEventSequence = eventSequence.toList()
try {
while (true) {
@@ -132,14 +140,28 @@ open class RecordingEventListener : EventListener() {
.isFalse()
}
val startEvent = e.closes(-1L)
if (startEvent != null) {
assertTrue(eventSequence.any { it == e.closes(it.timestampNs) })
if (enforceOrder) {
checkForStartEvent(e)
}
eventSequence.offer(e)
}
private fun checkForStartEvent(e: CallEvent) {
if (eventSequence.isEmpty()) {
assertThat(e).isInstanceOfAny(CallStart::class.java, Canceled::class.java)
} else {
eventSequence.forEach loop@ {
when (e.closes(it)) {
null -> return // no open event
true -> return // found open event
false -> return@loop // this is not the open event so continue
}
}
fail<Any>("event $e without matching start event")
}
}
override fun proxySelectStart(
call: Call,
url: HttpUrl