Skip to content

Commit d41a755

Browse files
authored
Start publishing dispatcher queue events (#9111)
* Start publishing dispatcher queue events * Update okhttp/src/commonJvmAndroid/kotlin/okhttp3/Dispatcher.kt --------- Co-authored-by: Jesse Wilson <jwilson@squareup.com>
1 parent c06ff31 commit d41a755

3 files changed

Lines changed: 81 additions & 14 deletions

File tree

okhttp-testing-support/src/main/kotlin/okhttp3/RecordingEventListener.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,12 @@ open class RecordingEventListener(
6969
*/
7070
private val enforceOrder: Boolean = true,
7171
) : EventListener() {
72+
/** Events that haven't yet been removed. */
7273
val eventSequence: Deque<CallEvent> = ConcurrentLinkedDeque()
7374

75+
/** The full set of events, used to match starts with ends. */
76+
private val eventsForMatching = ConcurrentLinkedDeque<CallEvent>()
77+
7478
private val forbiddenLocks = mutableListOf<Any>()
7579

7680
/** The timestamp of the last taken event, used to measure elapsed time between events. */
@@ -151,14 +155,15 @@ open class RecordingEventListener(
151155
checkForStartEvent(e)
152156
}
153157

158+
eventsForMatching.offer(e)
154159
eventSequence.offer(e)
155160
}
156161

157162
private fun checkForStartEvent(e: CallEvent) {
158-
if (eventSequence.isEmpty()) {
163+
if (eventsForMatching.isEmpty()) {
159164
assertThat(e).matchesPredicate { it is CallStart || it is Canceled }
160165
} else {
161-
eventSequence.forEach loop@{
166+
eventsForMatching.forEach loop@{
162167
when (e.closes(it)) {
163168
null -> return // no open event
164169
true -> return // found open event

okhttp/src/commonJvmAndroid/kotlin/okhttp3/Dispatcher.kt

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -170,27 +170,21 @@ class Dispatcher() {
170170

171171
// Actions to take outside the synchronized block.
172172
class Effects(
173-
val callsToExecute: List<AsyncCall> = listOf(),
174-
val callsToReject: List<AsyncCall> = listOf(),
173+
val callsToExecute: List<AsyncCall>,
175174
val idleCallbackToRun: Runnable?,
176175
)
177176

178177
val effects =
179178
synchronized(this) {
180-
var becameIdle = false
181179
if (finishedCall != null) {
182180
check(runningSyncCalls.remove(finishedCall)) { "Call wasn't in-flight!" }
183-
becameIdle = runningSyncCalls.isEmpty()
184181
}
185182

186183
if (finishedAsyncCall != null) {
187184
finishedAsyncCall.callsPerHost.decrementAndGet()
188185
check(runningAsyncCalls.remove(finishedAsyncCall)) { "Call wasn't in-flight!" }
189-
becameIdle = runningAsyncCalls.isEmpty()
190186
}
191187

192-
val idleCallbackToRun = if (becameIdle) idleCallback else null
193-
194188
if (enqueuedCall != null) {
195189
readyAsyncCalls.add(enqueuedCall)
196190

@@ -202,9 +196,15 @@ class Dispatcher() {
202196
}
203197
}
204198

199+
val becameIdle =
200+
(finishedCall != null || finishedAsyncCall != null) &&
201+
(executorIsShutdown || runningAsyncCalls.isEmpty()) &&
202+
runningSyncCalls.isEmpty()
203+
val idleCallbackToRun = if (becameIdle) idleCallback else null
204+
205205
if (executorIsShutdown) {
206206
return@synchronized Effects(
207-
callsToReject =
207+
callsToExecute =
208208
readyAsyncCalls
209209
.toList()
210210
.also { readyAsyncCalls.clear() },
@@ -233,12 +233,28 @@ class Dispatcher() {
233233
)
234234
}
235235

236-
for (i in 0 until effects.callsToReject.size) {
237-
effects.callsToReject[i].failRejected()
238-
}
236+
var callDispatcherQueueStart = true
239237

240238
for (i in 0 until effects.callsToExecute.size) {
241-
effects.callsToExecute[i].executeOn(executorService)
239+
val call = effects.callsToExecute[i]
240+
241+
// If the newly-enqueued call is already out, skip its dispatcher queue events. We only
242+
// publish those events for calls that have to wait.
243+
if (call === enqueuedCall) {
244+
callDispatcherQueueStart = false
245+
} else {
246+
call.call.eventListener.dispatcherQueueEnd(call.call, this)
247+
}
248+
249+
if (executorIsShutdown) {
250+
call.failRejected()
251+
} else {
252+
call.executeOn(executorService)
253+
}
254+
}
255+
256+
if (callDispatcherQueueStart && enqueuedCall != null) {
257+
enqueuedCall.call.eventListener.dispatcherQueueStart(enqueuedCall.call, this)
242258
}
243259

244260
effects.idleCallbackToRun?.run()

okhttp/src/jvmTest/kotlin/okhttp3/DispatcherTest.kt

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,19 @@ import assertk.assertions.containsExactlyInAnyOrder
2121
import assertk.assertions.isEmpty
2222
import assertk.assertions.isEqualTo
2323
import assertk.assertions.isFalse
24+
import assertk.assertions.isInstanceOf
2425
import assertk.assertions.isTrue
26+
import assertk.assertions.none
2527
import java.io.IOException
2628
import java.io.InterruptedIOException
2729
import java.net.UnknownHostException
2830
import java.util.concurrent.CountDownLatch
2931
import java.util.concurrent.TimeUnit
3032
import java.util.concurrent.atomic.AtomicBoolean
3133
import kotlin.test.assertFailsWith
34+
import okhttp3.CallEvent.DispatcherQueueEnd
35+
import okhttp3.CallEvent.DispatcherQueueStart
36+
import okhttp3.HttpUrl.Companion.toHttpUrl
3237
import org.junit.jupiter.api.BeforeEach
3338
import org.junit.jupiter.api.Tag
3439
import org.junit.jupiter.api.Test
@@ -78,6 +83,9 @@ class DispatcherTest {
7883
fun enqueuedJobsRunImmediately() {
7984
client.newCall(newRequest("http://a/1")).enqueue(callback)
8085
executor.assertJobs("http://a/1")
86+
87+
assertThat(listener.eventSequence).none { it.isInstanceOf<DispatcherQueueStart>() }
88+
assertThat(listener.eventSequence).none { it.isInstanceOf<DispatcherQueueEnd>() }
8189
}
8290

8391
@Test
@@ -88,6 +96,10 @@ class DispatcherTest {
8896
client.newCall(newRequest("http://b/1")).enqueue(callback)
8997
client.newCall(newRequest("http://b/2")).enqueue(callback)
9098
executor.assertJobs("http://a/1", "http://a/2", "http://b/1")
99+
100+
val dispatcherQueueStart = listener.removeUpToEvent<DispatcherQueueStart>()
101+
assertThat(dispatcherQueueStart.call.request().url).isEqualTo("http://b/2".toHttpUrl())
102+
assertThat(listener.eventSequence).none { it.isInstanceOf<DispatcherQueueEnd>() }
91103
}
92104

93105
@Test
@@ -97,6 +109,10 @@ class DispatcherTest {
97109
client.newCall(newRequest("http://a/2")).enqueue(callback)
98110
client.newCall(newRequest("http://a/3")).enqueue(callback)
99111
executor.assertJobs("http://a/1", "http://a/2")
112+
113+
val dispatcherQueueStart = listener.removeUpToEvent<DispatcherQueueStart>()
114+
assertThat(dispatcherQueueStart.call.request().url).isEqualTo("http://a/3".toHttpUrl())
115+
assertThat(listener.eventSequence).none { it.isInstanceOf<DispatcherQueueEnd>() }
100116
}
101117

102118
@Test
@@ -116,8 +132,23 @@ class DispatcherTest {
116132
client.newCall(newRequest("http://c/1")).enqueue(callback)
117133
client.newCall(newRequest("http://a/2")).enqueue(callback)
118134
client.newCall(newRequest("http://b/2")).enqueue(callback)
135+
136+
val dispatcherQueueStartC1 = listener.removeUpToEvent<DispatcherQueueStart>()
137+
assertThat(dispatcherQueueStartC1.call.request().url).isEqualTo("http://c/1".toHttpUrl())
138+
val dispatcherQueueStartA2 = listener.removeUpToEvent<DispatcherQueueStart>()
139+
assertThat(dispatcherQueueStartA2.call.request().url).isEqualTo("http://a/2".toHttpUrl())
140+
val dispatcherQueueStartB2 = listener.removeUpToEvent<DispatcherQueueStart>()
141+
assertThat(dispatcherQueueStartB2.call.request().url).isEqualTo("http://b/2".toHttpUrl())
142+
assertThat(listener.eventSequence).none { it.isInstanceOf<DispatcherQueueEnd>() }
143+
119144
dispatcher.maxRequests = 4
120145
executor.assertJobs("http://a/1", "http://b/1", "http://c/1", "http://a/2")
146+
147+
val dispatcherQueueEndC1 = listener.removeUpToEvent<DispatcherQueueEnd>()
148+
assertThat(dispatcherQueueEndC1.call.request().url).isEqualTo("http://c/1".toHttpUrl())
149+
val dispatcherQueueEndA2 = listener.removeUpToEvent<DispatcherQueueEnd>()
150+
assertThat(dispatcherQueueEndA2.call.request().url).isEqualTo("http://a/2".toHttpUrl())
151+
assertThat(listener.eventSequence).none { it.isInstanceOf<DispatcherQueueEnd>() }
121152
}
122153

123154
@Test
@@ -128,8 +159,23 @@ class DispatcherTest {
128159
client.newCall(newRequest("http://a/3")).enqueue(callback)
129160
client.newCall(newRequest("http://a/4")).enqueue(callback)
130161
client.newCall(newRequest("http://a/5")).enqueue(callback)
162+
163+
val dispatcherQueueStartA3 = listener.removeUpToEvent<DispatcherQueueStart>()
164+
assertThat(dispatcherQueueStartA3.call.request().url).isEqualTo("http://a/3".toHttpUrl())
165+
val dispatcherQueueStartA4 = listener.removeUpToEvent<DispatcherQueueStart>()
166+
assertThat(dispatcherQueueStartA4.call.request().url).isEqualTo("http://a/4".toHttpUrl())
167+
val dispatcherQueueStartA5 = listener.removeUpToEvent<DispatcherQueueStart>()
168+
assertThat(dispatcherQueueStartA5.call.request().url).isEqualTo("http://a/5".toHttpUrl())
169+
assertThat(listener.eventSequence).none { it.isInstanceOf<DispatcherQueueEnd>() }
170+
131171
dispatcher.maxRequestsPerHost = 4
132172
executor.assertJobs("http://a/1", "http://a/2", "http://a/3", "http://a/4")
173+
174+
val dispatcherQueueEndA3 = listener.removeUpToEvent<DispatcherQueueEnd>()
175+
assertThat(dispatcherQueueEndA3.call.request().url).isEqualTo("http://a/3".toHttpUrl())
176+
val dispatcherQueueEndA4 = listener.removeUpToEvent<DispatcherQueueEnd>()
177+
assertThat(dispatcherQueueEndA4.call.request().url).isEqualTo("http://a/4".toHttpUrl())
178+
assertThat(listener.eventSequence).none { it.isInstanceOf<DispatcherQueueEnd>() }
133179
}
134180

135181
@Test

0 commit comments

Comments
 (0)