window.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. # -*- coding: utf-8 -*-
  2. """
  3. hyper/http20/window
  4. ~~~~~~~~~~~~~~~~~~~
  5. Objects that understand flow control in hyper.
  6. HTTP/2 implements connection- and stream-level flow control. This flow
  7. control is mandatory. Unfortunately, it's difficult for hyper to be
  8. all that intelligent about how it manages flow control in a general case.
  9. This module defines an interface for pluggable flow-control managers. These
  10. managers will define a flow-control policy. This policy will determine when to
  11. send WINDOWUPDATE frames.
  12. """
  13. class BaseFlowControlManager(object):
  14. """
  15. The abstract base class for flow control managers.
  16. This class defines the interface for pluggable flow-control managers. A
  17. flow-control manager defines a flow-control policy, which basically boils
  18. down to deciding when to increase the flow control window.
  19. This decision can be based on a number of factors:
  20. - the initial window size,
  21. - the size of the document being retrieved,
  22. - the size of the received data frames,
  23. - any other information the manager can obtain
  24. A flow-control manager may be defined at the connection level or at the
  25. stream level. If no stream-level flow-control manager is defined, an
  26. instance of the connection-level flow control manager is used.
  27. A class that inherits from this one must not adjust the member variables
  28. defined in this class. They are updated and set by methods on this class.
  29. """
  30. def __init__(self, initial_window_size, document_size=None):
  31. #: The initial size of the connection window in bytes. This is set at
  32. #: creation time.
  33. self.initial_window_size = initial_window_size
  34. #: The current size of the connection window. Any methods overridden
  35. #: by the user must not adjust this value.
  36. self.window_size = initial_window_size
  37. #: The size of the document being retrieved, in bytes. This is
  38. #: retrieved from the Content-Length header, if provided. Note that
  39. #: the total number of bytes that will be received may be larger than
  40. #: this value due to HTTP/2 padding. It should not be assumed that
  41. #: simply because the the document size is smaller than the initial
  42. #: window size that there will never be a need to increase the window
  43. #: size.
  44. self.document_size = document_size
  45. def increase_window_size(self, frame_size):
  46. """
  47. Determine whether or not to emit a WINDOWUPDATE frame.
  48. This method should be overridden to determine, based on the state of
  49. the system and the size of the received frame, whether or not a
  50. WindowUpdate frame should be sent for the stream.
  51. This method should *not* adjust any of the member variables of this
  52. class.
  53. Note that this method is called before the window size is decremented
  54. as a result of the frame being handled.
  55. :param frame_size: The size of the received frame. Note that this *may*
  56. be zero. When this parameter is zero, it's possible that a
  57. WINDOWUPDATE frame may want to be emitted anyway. A zero-length frame
  58. size is usually associated with a change in the size of the receive
  59. window due to a SETTINGS frame.
  60. :returns: The amount to increase the receive window by. Return zero if
  61. the window should not be increased.
  62. """
  63. raise NotImplementedError(
  64. "FlowControlManager is an abstract base class"
  65. )
  66. def blocked(self):
  67. """
  68. Called whenever the remote endpoint reports that it is blocked behind
  69. the flow control window.
  70. When this method is called the remote endpoint is signaling that it
  71. has more data to send and that the transport layer is capable of
  72. transmitting it, but that the HTTP/2 flow control window prevents it
  73. being sent.
  74. This method should return the size by which the window should be
  75. incremented, which may be zero. This method should *not* adjust any
  76. of the member variables of this class.
  77. :returns: The amount to increase the receive window by. Return zero if
  78. the window should not be increased.
  79. """
  80. # TODO: Is this method necessary?
  81. raise NotImplementedError(
  82. "FlowControlManager is an abstract base class"
  83. )
  84. def _handle_frame(self, frame_size):
  85. """
  86. This internal method is called by the connection or stream that owns
  87. the flow control manager. It handles the generic behaviour of flow
  88. control managers: namely, keeping track of the window size.
  89. """
  90. rc = self.increase_window_size(frame_size)
  91. self.window_size -= frame_size
  92. self.window_size += rc
  93. return rc
  94. def _blocked(self):
  95. """
  96. This internal method is called by the connection or stream that owns
  97. the flow control manager. It handles the generic behaviour of receiving
  98. BLOCKED frames.
  99. """
  100. rc = self.blocked()
  101. self.window_size += rc
  102. return rc
  103. class FlowControlManager(BaseFlowControlManager):
  104. """
  105. ``hyper``'s default flow control manager.
  106. This implements hyper's flow control algorithms. This algorithm attempts to
  107. reduce the number of WINDOWUPDATE frames we send without blocking the
  108. remote endpoint behind the flow control window.
  109. This algorithm will become more complicated over time. In the current form,
  110. the algorithm is very simple:
  111. - When the flow control window gets less than 1/4 of the maximum size,
  112. increment back to the maximum.
  113. - Otherwise, if the flow control window gets to less than 1kB, increment
  114. back to the maximum.
  115. """
  116. def increase_window_size(self, frame_size):
  117. future_window_size = self.window_size - frame_size
  118. if ((future_window_size < (self.initial_window_size / 4)) or
  119. (future_window_size < 1000)):
  120. return self.initial_window_size - future_window_size
  121. return 0
  122. def blocked(self):
  123. return self.initial_window_size - self.window_size