155 lines
6.0 KiB
Python
155 lines
6.0 KiB
Python
|
# -*- coding: utf-8 -*-
|
||
|
"""
|
||
|
hyper/http20/window
|
||
|
~~~~~~~~~~~~~~~~~~~
|
||
|
|
||
|
Objects that understand flow control in hyper.
|
||
|
|
||
|
HTTP/2 implements connection- and stream-level flow control. This flow
|
||
|
control is mandatory. Unfortunately, it's difficult for hyper to be
|
||
|
all that intelligent about how it manages flow control in a general case.
|
||
|
|
||
|
This module defines an interface for pluggable flow-control managers. These
|
||
|
managers will define a flow-control policy. This policy will determine when to
|
||
|
send WINDOWUPDATE frames.
|
||
|
"""
|
||
|
|
||
|
|
||
|
class BaseFlowControlManager(object):
|
||
|
"""
|
||
|
The abstract base class for flow control managers.
|
||
|
|
||
|
This class defines the interface for pluggable flow-control managers. A
|
||
|
flow-control manager defines a flow-control policy, which basically boils
|
||
|
down to deciding when to increase the flow control window.
|
||
|
|
||
|
This decision can be based on a number of factors:
|
||
|
|
||
|
- the initial window size,
|
||
|
- the size of the document being retrieved,
|
||
|
- the size of the received data frames,
|
||
|
- any other information the manager can obtain
|
||
|
|
||
|
A flow-control manager may be defined at the connection level or at the
|
||
|
stream level. If no stream-level flow-control manager is defined, an
|
||
|
instance of the connection-level flow control manager is used.
|
||
|
|
||
|
A class that inherits from this one must not adjust the member variables
|
||
|
defined in this class. They are updated and set by methods on this class.
|
||
|
"""
|
||
|
def __init__(self, initial_window_size, document_size=None):
|
||
|
#: The initial size of the connection window in bytes. This is set at
|
||
|
#: creation time.
|
||
|
self.initial_window_size = initial_window_size
|
||
|
|
||
|
#: The current size of the connection window. Any methods overridden
|
||
|
#: by the user must not adjust this value.
|
||
|
self.window_size = initial_window_size
|
||
|
|
||
|
#: The size of the document being retrieved, in bytes. This is
|
||
|
#: retrieved from the Content-Length header, if provided. Note that
|
||
|
#: the total number of bytes that will be received may be larger than
|
||
|
#: this value due to HTTP/2 padding. It should not be assumed that
|
||
|
#: simply because the the document size is smaller than the initial
|
||
|
#: window size that there will never be a need to increase the window
|
||
|
#: size.
|
||
|
self.document_size = document_size
|
||
|
|
||
|
def increase_window_size(self, frame_size):
|
||
|
"""
|
||
|
Determine whether or not to emit a WINDOWUPDATE frame.
|
||
|
|
||
|
This method should be overridden to determine, based on the state of
|
||
|
the system and the size of the received frame, whether or not a
|
||
|
WindowUpdate frame should be sent for the stream.
|
||
|
|
||
|
This method should *not* adjust any of the member variables of this
|
||
|
class.
|
||
|
|
||
|
Note that this method is called before the window size is decremented
|
||
|
as a result of the frame being handled.
|
||
|
|
||
|
:param frame_size: The size of the received frame. Note that this *may*
|
||
|
be zero. When this parameter is zero, it's possible that a
|
||
|
WINDOWUPDATE frame may want to be emitted anyway. A zero-length frame
|
||
|
size is usually associated with a change in the size of the receive
|
||
|
window due to a SETTINGS frame.
|
||
|
:returns: The amount to increase the receive window by. Return zero if
|
||
|
the window should not be increased.
|
||
|
"""
|
||
|
raise NotImplementedError(
|
||
|
"FlowControlManager is an abstract base class"
|
||
|
)
|
||
|
|
||
|
def blocked(self):
|
||
|
"""
|
||
|
Called whenever the remote endpoint reports that it is blocked behind
|
||
|
the flow control window.
|
||
|
|
||
|
When this method is called the remote endpoint is signaling that it
|
||
|
has more data to send and that the transport layer is capable of
|
||
|
transmitting it, but that the HTTP/2 flow control window prevents it
|
||
|
being sent.
|
||
|
|
||
|
This method should return the size by which the window should be
|
||
|
incremented, which may be zero. This method should *not* adjust any
|
||
|
of the member variables of this class.
|
||
|
|
||
|
:returns: The amount to increase the receive window by. Return zero if
|
||
|
the window should not be increased.
|
||
|
"""
|
||
|
# TODO: Is this method necessary?
|
||
|
raise NotImplementedError(
|
||
|
"FlowControlManager is an abstract base class"
|
||
|
)
|
||
|
|
||
|
def _handle_frame(self, frame_size):
|
||
|
"""
|
||
|
This internal method is called by the connection or stream that owns
|
||
|
the flow control manager. It handles the generic behaviour of flow
|
||
|
control managers: namely, keeping track of the window size.
|
||
|
"""
|
||
|
rc = self.increase_window_size(frame_size)
|
||
|
self.window_size -= frame_size
|
||
|
self.window_size += rc
|
||
|
return rc
|
||
|
|
||
|
def _blocked(self):
|
||
|
"""
|
||
|
This internal method is called by the connection or stream that owns
|
||
|
the flow control manager. It handles the generic behaviour of receiving
|
||
|
BLOCKED frames.
|
||
|
"""
|
||
|
rc = self.blocked()
|
||
|
self.window_size += rc
|
||
|
return rc
|
||
|
|
||
|
|
||
|
class FlowControlManager(BaseFlowControlManager):
|
||
|
"""
|
||
|
``hyper``'s default flow control manager.
|
||
|
|
||
|
This implements hyper's flow control algorithms. This algorithm attempts to
|
||
|
reduce the number of WINDOWUPDATE frames we send without blocking the
|
||
|
remote endpoint behind the flow control window.
|
||
|
|
||
|
This algorithm will become more complicated over time. In the current form,
|
||
|
the algorithm is very simple:
|
||
|
|
||
|
- When the flow control window gets less than 1/4 of the maximum size,
|
||
|
increment back to the maximum.
|
||
|
- Otherwise, if the flow control window gets to less than 1kB, increment
|
||
|
back to the maximum.
|
||
|
"""
|
||
|
def increase_window_size(self, frame_size):
|
||
|
future_window_size = self.window_size - frame_size
|
||
|
|
||
|
if ((future_window_size < (self.initial_window_size / 4)) or
|
||
|
(future_window_size < 1000)):
|
||
|
return self.initial_window_size - future_window_size
|
||
|
|
||
|
return 0
|
||
|
|
||
|
def blocked(self):
|
||
|
return self.initial_window_size - self.window_size
|