@@ -138,7 +138,6 @@ class Http2Connection internal constructor(builder: Builder) : Closeable {
138
138
var writeBytesMaximum: Long = peerSettings.initialWindowSize.toLong()
139
139
private set
140
140
141
- internal var receivedInitialPeerSettings = false
142
141
internal val socket: Socket = builder.socket
143
142
val writer = Http2Writer (builder.sink, client)
144
143
@@ -660,43 +659,53 @@ class Http2Connection internal constructor(builder: Builder) : Closeable {
660
659
}
661
660
662
661
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 ) {
663
681
var delta = 0L
664
682
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
678
692
}
679
693
}
680
- listenerExecutor.execute(" OkHttp $connectionName settings" ) {
681
- listener.onSettings(this @Http2Connection)
694
+ try {
695
+ writer.applyAndAckSettings(peerSettings)
696
+ } catch (e: IOException ) {
697
+ failConnection(e)
682
698
}
683
699
}
684
- if (streamsToNotify != null && delta != 0L ) {
700
+ if (streamsToNotify != null ) {
685
701
for (stream in streamsToNotify!! ) {
686
702
synchronized(stream) {
687
703
stream.addBytesToWriteWindow(delta)
688
704
}
689
705
}
690
706
}
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)
700
709
}
701
710
}
702
711
0 commit comments