Skip to content

Commit 3464ef3

Browse files
authored
Merge pull request #5431 from square/jwilson.0907.ack_apply_atomically
Acknowledge and apply inbound settings atomically
2 parents 3490c7e + bd6a97a commit 3464ef3

File tree

2 files changed

+36
-27
lines changed

2 files changed

+36
-27
lines changed

okhttp/src/main/java/okhttp3/internal/http2/Http2Connection.kt

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,6 @@ class Http2Connection internal constructor(builder: Builder) : Closeable {
138138
var writeBytesMaximum: Long = peerSettings.initialWindowSize.toLong()
139139
private set
140140

141-
internal var receivedInitialPeerSettings = false
142141
internal val socket: Socket = builder.socket
143142
val writer = Http2Writer(builder.sink, client)
144143

@@ -660,43 +659,53 @@ class Http2Connection internal constructor(builder: Builder) : Closeable {
660659
}
661660

662661
override fun settings(clearPrevious: Boolean, settings: Settings) {
662+
writerExecutor.tryExecute("OkHttp $connectionName ACK Settings") {
663+
applyAndAckSettings(clearPrevious, settings)
664+
}
665+
}
666+
667+
/**
668+
* Apply inbound settings and send an acknowledgement to the peer that provided them.
669+
*
670+
* We need to apply the settings and ack them atomically. This is because some HTTP/2
671+
* implementations (nghttp2) forbid peers from taking advantage of settings before they have
672+
* acknowledged! In particular, we shouldn't send frames that assume a new `initialWindowSize`
673+
* until we send the frame that acknowledges this new size.
674+
*
675+
* Since we can't ACK settings on the current reader thread (the reader thread can't write) we
676+
* execute all peer settings logic on the writer thread. This relies on the fact that the
677+
* writer executor won't reorder tasks; otherwise settings could be applied in the opposite
678+
* order than received.
679+
*/
680+
fun applyAndAckSettings(clearPrevious: Boolean, settings: Settings) {
663681
var delta = 0L
664682
var streamsToNotify: Array<Http2Stream>? = null
665-
synchronized(this@Http2Connection) {
666-
val priorWriteWindowSize = peerSettings.initialWindowSize
667-
if (clearPrevious) peerSettings.clear()
668-
peerSettings.merge(settings)
669-
applyAndAckSettings(settings)
670-
val peerInitialWindowSize = peerSettings.initialWindowSize
671-
if (peerInitialWindowSize != -1 && peerInitialWindowSize != priorWriteWindowSize) {
672-
delta = (peerInitialWindowSize - priorWriteWindowSize).toLong()
673-
if (!receivedInitialPeerSettings) {
674-
receivedInitialPeerSettings = true
675-
}
676-
if (streams.isNotEmpty()) {
677-
streamsToNotify = streams.values.toTypedArray()
683+
synchronized(writer) {
684+
synchronized(this@Http2Connection) {
685+
val priorWriteWindowSize = peerSettings.initialWindowSize
686+
if (clearPrevious) peerSettings.clear()
687+
peerSettings.merge(settings)
688+
val peerInitialWindowSize = peerSettings.initialWindowSize
689+
if (peerInitialWindowSize != -1 && peerInitialWindowSize != priorWriteWindowSize) {
690+
delta = (peerInitialWindowSize - priorWriteWindowSize).toLong()
691+
streamsToNotify = if (streams.isNotEmpty()) streams.values.toTypedArray() else null
678692
}
679693
}
680-
listenerExecutor.execute("OkHttp $connectionName settings") {
681-
listener.onSettings(this@Http2Connection)
694+
try {
695+
writer.applyAndAckSettings(peerSettings)
696+
} catch (e: IOException) {
697+
failConnection(e)
682698
}
683699
}
684-
if (streamsToNotify != null && delta != 0L) {
700+
if (streamsToNotify != null) {
685701
for (stream in streamsToNotify!!) {
686702
synchronized(stream) {
687703
stream.addBytesToWriteWindow(delta)
688704
}
689705
}
690706
}
691-
}
692-
693-
private fun applyAndAckSettings(peerSettings: Settings) {
694-
writerExecutor.tryExecute("OkHttp $connectionName ACK Settings") {
695-
try {
696-
writer.applyAndAckSettings(peerSettings)
697-
} catch (e: IOException) {
698-
failConnection(e)
699-
}
707+
listenerExecutor.execute("OkHttp $connectionName settings") {
708+
listener.onSettings(this@Http2Connection)
700709
}
701710
}
702711

okhttp/src/test/java/okhttp3/internal/http2/Http2ConnectionTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -944,7 +944,7 @@ public final class Http2ConnectionTest {
944944
// fake a settings frame with clear flag set.
945945
Settings settings2 = new Settings();
946946
settings2.set(MAX_CONCURRENT_STREAMS, 60000);
947-
connection.getReaderRunnable().settings(true, settings2);
947+
connection.getReaderRunnable().applyAndAckSettings(true, settings2);
948948

949949
synchronized (connection) {
950950
assertThat(connection.getPeerSettings().getHeaderTableSize()).isEqualTo(-1);

0 commit comments

Comments
 (0)