1
0
mirror of https://github.com/square/okhttp.git synced 2025-11-26 06:43:09 +03:00

Add a canceled event to EventListener

This commit is contained in:
Jesse Wilson
2020-02-16 18:20:49 -05:00
parent 4bb66bcf5e
commit 08e23fcd02
10 changed files with 87 additions and 1 deletions

View File

@@ -151,6 +151,10 @@ class LoggingEventListener private constructor(
logWithTime("callFailed: $ioe") logWithTime("callFailed: $ioe")
} }
override fun canceled(call: Call) {
logWithTime("canceled")
}
private fun logWithTime(message: String) { private fun logWithTime(message: String) {
val timeMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs) val timeMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs)
logger.log("[$timeMs ms] $message") logger.log("[$timeMs ms] $message")

View File

@@ -134,6 +134,11 @@ sealed class CallEvent {
val ioe: IOException val ioe: IOException
) : CallEvent() ) : CallEvent()
data class Canceled(
override val timestampNs: Long,
override val call: Call
) : CallEvent()
data class RequestHeadersStart( data class RequestHeadersStart(
override val timestampNs: Long, override val timestampNs: Long,
override val call: Call override val call: Call

View File

@@ -184,6 +184,12 @@ class ClientRuleEventListener(val delegate: EventListener = NONE, var logger: (S
delegate.callFailed(call, ioe) delegate.callFailed(call, ioe)
} }
override fun canceled(call: Call) {
logWithTime("canceled")
delegate.canceled(call)
}
private fun logWithTime(message: String) { private fun logWithTime(message: String) {
val timeMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs) val timeMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs)
logger.invoke("[$timeMs ms] $message") logger.invoke("[$timeMs ms] $message")

View File

@@ -25,6 +25,7 @@ import java.util.concurrent.TimeUnit
import okhttp3.CallEvent.CallEnd import okhttp3.CallEvent.CallEnd
import okhttp3.CallEvent.CallFailed import okhttp3.CallEvent.CallFailed
import okhttp3.CallEvent.CallStart import okhttp3.CallEvent.CallStart
import okhttp3.CallEvent.Canceled
import okhttp3.CallEvent.ConnectEnd import okhttp3.CallEvent.ConnectEnd
import okhttp3.CallEvent.ConnectFailed import okhttp3.CallEvent.ConnectFailed
import okhttp3.CallEvent.ConnectStart import okhttp3.CallEvent.ConnectStart
@@ -249,4 +250,8 @@ open class RecordingEventListener : EventListener() {
call: Call, call: Call,
ioe: IOException ioe: IOException
) = logEvent(CallFailed(System.nanoTime(), call, ioe)) ) = logEvent(CallFailed(System.nanoTime(), call, ioe))
override fun canceled(
call: Call
) = logEvent(Canceled(System.nanoTime(), call))
} }

View File

@@ -401,6 +401,27 @@ abstract class EventListener {
) { ) {
} }
/**
* Invoked when a call is canceled.
*
* Like all methods in this interface, this is invoked on the thread that triggered the event. But
* while other events occur sequentially; cancels may occur concurrently with other events. For
* example, thread A may be executing [responseBodyStart] while thread B executes [canceled].
* Implementations must support such concurrent calls.
*
* Note that cancellation is best-effort and that a call may proceed normally after it has been
* canceled. For example, happy-path events like [requestHeadersStart] and [requestHeadersEnd] may
* occur after a call is canceled. Typically cancellation takes effect when an expensive I/O
* operation is required.
*
* This is invoked at most once, even if [Call.cancel] is invoked multiple times. It may be
* invoked at any point in a call's life, including before [callStart] and after [callEnd].
*/
open fun canceled(
call: Call
) {
}
interface Factory { interface Factory {
/** /**
* Creates an instance of the [EventListener] for a particular [Call]. The returned * Creates an instance of the [EventListener] for a particular [Call]. The returned

View File

@@ -121,11 +121,13 @@ class RealCall(
val exchangeToCancel: Exchange? val exchangeToCancel: Exchange?
val connectionToCancel: RealConnection? val connectionToCancel: RealConnection?
synchronized(connectionPool) { synchronized(connectionPool) {
if (canceled) return // Already canceled.
canceled = true canceled = true
exchangeToCancel = exchange exchangeToCancel = exchange
connectionToCancel = exchangeFinder?.connectingConnection() ?: connection connectionToCancel = exchangeFinder?.connectingConnection() ?: connection
} }
exchangeToCancel?.cancel() ?: connectionToCancel?.cancel() exchangeToCancel?.cancel() ?: connectionToCancel?.cancel()
eventListener.canceled(this)
} }
override fun isCanceled(): Boolean { override fun isCanceled(): Boolean {

View File

@@ -226,7 +226,41 @@ public final class EventListenerTest {
assertThat(expected.getMessage()).isEqualTo("Canceled"); assertThat(expected.getMessage()).isEqualTo("Canceled");
} }
assertThat(listener.recordedEventTypes()).containsExactly("CallStart", "CallFailed"); assertThat(listener.recordedEventTypes()).containsExactly(
"Canceled", "CallStart", "CallFailed");
}
@Test public void cancelAsyncCall() throws IOException {
server.enqueue(new MockResponse()
.setBody("abc"));
Call call = client.newCall(new Request.Builder()
.url(server.url("/"))
.build());
call.enqueue(new Callback() {
@Override public void onFailure(Call call, IOException e) {
}
@Override public void onResponse(Call call, Response response) throws IOException {
response.close();
}
});
call.cancel();
assertThat(listener.recordedEventTypes()).contains("Canceled");
}
@Test public void multipleCancelsEmitsOnlyOneEvent() throws IOException {
server.enqueue(new MockResponse()
.setBody("abc"));
Call call = client.newCall(new Request.Builder()
.url(server.url("/"))
.build());
call.cancel();
call.cancel();
assertThat(listener.recordedEventTypes()).containsExactly("Canceled");
} }
private void assertSuccessfulEventOrder(Matcher<Response> responseMatcher) throws IOException { private void assertSuccessfulEventOrder(Matcher<Response> responseMatcher) throws IOException {

View File

@@ -387,6 +387,7 @@ class KotlinSourceModernTest {
override fun responseFailed(call: Call, ioe: IOException) = TODO() override fun responseFailed(call: Call, ioe: IOException) = TODO()
override fun callEnd(call: Call) = TODO() override fun callEnd(call: Call) = TODO()
override fun callFailed(call: Call, ioe: IOException) = TODO() override fun callFailed(call: Call, ioe: IOException) = TODO()
override fun canceled(call: Call) = TODO()
} }
val none: EventListener = EventListener.NONE val none: EventListener = EventListener.NONE
} }

View File

@@ -196,5 +196,9 @@ public final class PrintEvents {
@Override public void callFailed(Call call, IOException ioe) { @Override public void callFailed(Call call, IOException ioe) {
printEvent("callFailed"); printEvent("callFailed");
} }
@Override public void canceled(Call call) {
printEvent("canceled");
}
} }
} }

View File

@@ -171,5 +171,9 @@ public final class PrintEventsNonConcurrent {
@Override public void callFailed(Call call, IOException ioe) { @Override public void callFailed(Call call, IOException ioe) {
printEvent("callFailed"); printEvent("callFailed");
} }
@Override public void canceled(Call call) {
printEvent("canceled");
}
} }
} }