Skip to content

Commit df2c842

Browse files
authored
Merge pull request #1165 from hellysmile/limit_closed_streams
Limit amount of _closed_streams
2 parents 1df6a15 + 0affd9c commit df2c842

File tree

3 files changed

+75
-3
lines changed

3 files changed

+75
-3
lines changed

h2/connection.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
from .frame_buffer import FrameBuffer
3434
from .settings import Settings, SettingCodes
3535
from .stream import H2Stream, StreamClosedBy
36-
from .utilities import guard_increment_window
36+
from .utilities import SizeLimitDict, guard_increment_window
3737
from .windows import WindowManager
3838

3939

@@ -281,6 +281,9 @@ class H2Connection(object):
281281
# The initial default value of SETTINGS_MAX_HEADER_LIST_SIZE.
282282
DEFAULT_MAX_HEADER_LIST_SIZE = 2**16
283283

284+
# Keep in memory limited amount of results for streams closes
285+
MAX_CLOSED_STREAMS = 2**16
286+
284287
def __init__(self, config=None):
285288
self.state_machine = H2ConnectionStateMachine()
286289
self.streams = {}
@@ -355,7 +358,9 @@ def __init__(self, config=None):
355358
# Also used to determine whether we should consider a frame received
356359
# while a stream is closed as either a stream error or a connection
357360
# error.
358-
self._closed_streams = {}
361+
self._closed_streams = SizeLimitDict(
362+
size_limit=self.MAX_CLOSED_STREAMS
363+
)
359364

360365
# The flow control window manager for the connection.
361366
self._inbound_flow_control_window_manager = WindowManager(

h2/utilities.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -617,3 +617,22 @@ def validate_outbound_headers(headers, hdr_validation_flags):
617617
headers = _check_path_header(headers, hdr_validation_flags)
618618

619619
return headers
620+
621+
622+
class SizeLimitDict(collections.OrderedDict):
623+
624+
def __init__(self, *args, **kwargs):
625+
self._size_limit = kwargs.pop("size_limit", None)
626+
super(SizeLimitDict, self).__init__(*args, **kwargs)
627+
628+
self._check_size_limit()
629+
630+
def __setitem__(self, key, value):
631+
super(SizeLimitDict, self).__setitem__(key, value)
632+
633+
self._check_size_limit()
634+
635+
def _check_size_limit(self):
636+
if self._size_limit is not None:
637+
while len(self) > self._size_limit:
638+
self.popitem(last=False)

test/test_utility_functions.py

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import h2.errors
1313
import h2.events
1414
import h2.exceptions
15-
from h2.utilities import extract_method_header
15+
from h2.utilities import SizeLimitDict, extract_method_header
1616

1717
# These tests require a non-list-returning range function.
1818
try:
@@ -176,3 +176,51 @@ class TestExtractHeader(object):
176176
)
177177
def test_extract_header_method(self, headers):
178178
assert extract_method_header(headers) == b'GET'
179+
180+
181+
def test_size_limit_dict_limit():
182+
dct = SizeLimitDict(size_limit=2)
183+
184+
dct[1] = 1
185+
dct[2] = 2
186+
187+
assert len(dct) == 2
188+
assert dct[1] == 1
189+
assert dct[2] == 2
190+
191+
dct[3] = 3
192+
193+
assert len(dct) == 2
194+
assert dct[2] == 2
195+
assert dct[3] == 3
196+
assert 1 not in dct
197+
198+
199+
def test_size_limit_dict_limit_init():
200+
initial_dct = {
201+
1: 1,
202+
2: 2,
203+
3: 3,
204+
}
205+
206+
dct = SizeLimitDict(initial_dct, size_limit=2)
207+
208+
assert len(dct) == 2
209+
210+
211+
def test_size_limit_dict_no_limit():
212+
dct = SizeLimitDict(size_limit=None)
213+
214+
dct[1] = 1
215+
dct[2] = 2
216+
217+
assert len(dct) == 2
218+
assert dct[1] == 1
219+
assert dct[2] == 2
220+
221+
dct[3] = 3
222+
223+
assert len(dct) == 3
224+
assert dct[1] == 1
225+
assert dct[2] == 2
226+
assert dct[3] == 3

0 commit comments

Comments
 (0)