Repository: square/okhttp
File: okhttp/src/jvmMain/kotlin/okhttp3/internal/http2/Http2Stream.kt (Kotlin)

Code before:
          val doReadTimeout = doReadTimeout()
          if (doReadTimeout) {
            readTimeout.enter()
          }
          try {
            if (errorCode != null && !finished) {
              // Prepare to deliver an error.
              errorExceptionToDeliver = errorException ?: StreamResetException(errorCode!!)
            }

            if (closed) {
              throw IOException("stream closed")
            } else if (readBuffer.size > 0L) {
              // Prepare to read bytes. Start by moving them to the caller's buffer.
              readBytesDelivered = readBuffer.read(sink, minOf(byteCount, readBuffer.size))
              readBytes.increase(total = readBytesDelivered)

              val unacknowledgedBytesRead = readBytes.unacknowledged
              if (errorExceptionToDeliver == null &&
                  unacknowledgedBytesRead >= connection.okHttpSettings.initialWindowSize / 2) {
                // Flow control: notify the peer that we're ready for more data! Only send a
                // WINDOW_UPDATE if the stream isn't in error.
                connection.writeWindowUpdateLater(id, unacknowledgedBytesRead)
                readBytes.increase(acknowledged = unacknowledgedBytesRead)
              }
              connection.flowControlListener.receivingStreamWindowChanged(id, readBytes, readBuffer.size)
            } else if (!finished && errorExceptionToDeliver == null) {
              // Nothing to do. Wait until that changes then try again.
              waitForIo()
              tryAgain = true
            }
          } finally {
            if (doReadTimeout) {
              readTimeout.exitAndThrowIfTimedOut()
            }
          }
        }

        // 2. Do it outside of the synchronized block and timeout.

        if (tryAgain) {
          continue
        }

        if (readBytesDelivered != -1L) {
          // Update connection.unacknowledgedBytesRead outside the synchronized block.
          updateConnectionFlowControl(readBytesDelivered)
          return readBytesDelivered
        }

        if (errorExceptionToDeliver != null) {

Review comment (suggestion):
Anything where we call a user-owned listener while holding a lock is bad news. Something to keep in mind if ever we wanted to make a public API.

Code after:
          val doReadTimeout = doReadTimeout()
          if (doReadTimeout) {
            readTimeout.enter()
          }
          try {
            if (errorCode != null && !finished) {
              // Prepare to deliver an error.
              errorExceptionToDeliver = errorException ?: StreamResetException(errorCode!!)
            }

            if (closed) {
              throw IOException("stream closed")
            } else if (readBuffer.size > 0L) {
              // Prepare to read bytes. Start by moving them to the caller's buffer.
              readBytesDelivered = readBuffer.read(sink, minOf(byteCount, readBuffer.size))
              readBytes.update(total = readBytesDelivered)

              val unacknowledgedBytesRead = readBytes.unacknowledged
              if (errorExceptionToDeliver == null &&
                  unacknowledgedBytesRead >= connection.okHttpSettings.initialWindowSize / 2) {
                // Flow control: notify the peer that we're ready for more data! Only send a
                // WINDOW_UPDATE if the stream isn't in error.
                connection.writeWindowUpdateLater(id, unacknowledgedBytesRead)
                readBytes.update(acknowledged = unacknowledgedBytesRead)
              }
            } else if (!finished && errorExceptionToDeliver == null) {
              // Nothing to do. Wait until that changes then try again.
              waitForIo()
              tryAgain = true
            }
          } finally {
            if (doReadTimeout) {
              readTimeout.exitAndThrowIfTimedOut()
            }
          }
        }
        connection.flowControlListener.receivingStreamWindowChanged(id, readBytes, readBuffer.size)

        // 2. Do it outside of the synchronized block and timeout.

        if (tryAgain) {
          continue
        }

        if (readBytesDelivered != -1L) {
          // Update connection.unacknowledgedBytesRead outside the synchronized block.
          updateConnectionFlowControl(readBytesDelivered)
          return readBytesDelivered
        }

        if (errorExceptionToDeliver != null) {