windows.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. # -*- coding: utf-8 -*-
  2. """
  3. h2/windows
  4. ~~~~~~~~~~
  5. Defines tools for managing HTTP/2 flow control windows.
  6. The objects defined in this module are used to automatically manage HTTP/2
  7. flow control windows. Specifically, they keep track of what the size of the
  8. window is, how much data has been consumed from that window, and how much data
  9. the user has already used. It then implements a basic algorithm that attempts
  10. to manage the flow control window without user input, trying to ensure that it
  11. does not emit too many WINDOW_UPDATE frames.
  12. """
  13. from __future__ import division
  14. from .exceptions import FlowControlError
  15. # The largest acceptable value for a HTTP/2 flow control window.
  16. LARGEST_FLOW_CONTROL_WINDOW = 2**31 - 1
  17. class WindowManager(object):
  18. """
  19. A basic HTTP/2 window manager.
  20. :param max_window_size: The maximum size of the flow control window.
  21. :type max_window_size: ``int``
  22. """
  23. def __init__(self, max_window_size):
  24. assert max_window_size <= LARGEST_FLOW_CONTROL_WINDOW
  25. self.max_window_size = max_window_size
  26. self.current_window_size = max_window_size
  27. self._bytes_processed = 0
  28. def window_consumed(self, size):
  29. """
  30. We have received a certain number of bytes from the remote peer. This
  31. necessarily shrinks the flow control window!
  32. :param size: The number of flow controlled bytes we received from the
  33. remote peer.
  34. :type size: ``int``
  35. :returns: Nothing.
  36. :rtype: ``None``
  37. """
  38. self.current_window_size -= size
  39. if self.current_window_size < 0:
  40. raise FlowControlError("Flow control window shrunk below 0")
  41. def window_opened(self, size):
  42. """
  43. The flow control window has been incremented, either because of manual
  44. flow control management or because of the user changing the flow
  45. control settings. This can have the effect of increasing what we
  46. consider to be the "maximum" flow control window size.
  47. This does not increase our view of how many bytes have been processed,
  48. only of how much space is in the window.
  49. :param size: The increment to the flow control window we received.
  50. :type size: ``int``
  51. :returns: Nothing
  52. :rtype: ``None``
  53. """
  54. self.current_window_size += size
  55. if self.current_window_size > LARGEST_FLOW_CONTROL_WINDOW:
  56. raise FlowControlError(
  57. "Flow control window mustn't exceed %d" %
  58. LARGEST_FLOW_CONTROL_WINDOW
  59. )
  60. if self.current_window_size > self.max_window_size:
  61. self.max_window_size = self.current_window_size
  62. def process_bytes(self, size):
  63. """
  64. The application has informed us that it has processed a certain number
  65. of bytes. This may cause us to want to emit a window update frame. If
  66. we do want to emit a window update frame, this method will return the
  67. number of bytes that we should increment the window by.
  68. :param size: The number of flow controlled bytes that the application
  69. has processed.
  70. :type size: ``int``
  71. :returns: The number of bytes to increment the flow control window by,
  72. or ``None``.
  73. :rtype: ``int`` or ``None``
  74. """
  75. self._bytes_processed += size
  76. return self._maybe_update_window()
  77. def _maybe_update_window(self):
  78. """
  79. Run the algorithm.
  80. Our current algorithm can be described like this.
  81. 1. If no bytes have been processed, we immediately return 0. There is
  82. no meaningful way for us to hand space in the window back to the
  83. remote peer, so let's not even try.
  84. 2. If there is no space in the flow control window, and we have
  85. processed at least 1024 bytes (or 1/4 of the window, if the window
  86. is smaller), we will emit a window update frame. This is to avoid
  87. the risk of blocking a stream altogether.
  88. 3. If there is space in the flow control window, and we have processed
  89. at least 1/2 of the window worth of bytes, we will emit a window
  90. update frame. This is to minimise the number of window update frames
  91. we have to emit.
  92. In a healthy system with large flow control windows, this will
  93. irregularly emit WINDOW_UPDATE frames. This prevents us starving the
  94. connection by emitting eleventy bajillion WINDOW_UPDATE frames,
  95. especially in situations where the remote peer is sending a lot of very
  96. small DATA frames.
  97. """
  98. # TODO: Can the window be smaller than 1024 bytes? If not, we can
  99. # streamline this algorithm.
  100. if not self._bytes_processed:
  101. return None
  102. max_increment = (self.max_window_size - self.current_window_size)
  103. increment = 0
  104. # Note that, even though we may increment less than _bytes_processed,
  105. # we still want to set it to zero whenever we emit an increment. This
  106. # is because we'll always increment up to the maximum we can.
  107. if (self.current_window_size == 0) and (
  108. self._bytes_processed > min(1024, self.max_window_size // 4)):
  109. increment = min(self._bytes_processed, max_increment)
  110. self._bytes_processed = 0
  111. elif self._bytes_processed >= (self.max_window_size // 2):
  112. increment = min(self._bytes_processed, max_increment)
  113. self._bytes_processed = 0
  114. self.current_window_size += increment
  115. return increment