Skip to content

Commit b74d68f

Browse files
committed
Reduce number of window update frames sent.
1 parent 5f5dae1 commit b74d68f

File tree

5 files changed

+63
-11
lines changed

5 files changed

+63
-11
lines changed

lib/protocol/http2/connection.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def initialize(framer, local_stream_id)
4141
@decoder = HPACK::Context.new
4242
@encoder = HPACK::Context.new
4343

44-
@local_window = LocalWindow.new()
44+
@local_window = LocalWindow.new
4545
@remote_window = Window.new
4646
end
4747

lib/protocol/http2/flow_controlled.rb

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,11 @@ def send_window_update(window_increment)
7070
def receive_window_update(frame)
7171
amount = frame.unpack
7272

73-
# Async.logger.info(self) {"expanding remote_window=#{@remote_window} by #{amount}"}
74-
7573
if amount != 0
7674
@remote_window.expand(amount)
7775
else
7876
raise ProtocolError, "Invalid window size increment: #{amount}!"
7977
end
80-
81-
# puts "expanded remote_window=#{@remote_window} by #{amount}"
8278
end
8379

8480
# The window has been expanded by the given amount.

lib/protocol/http2/window.rb

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@
66
module Protocol
77
module HTTP2
88
class Window
9+
# When an HTTP/2 connection is first established, new streams are created with an initial flow-control window size of 65,535 octets. The connection flow-control window is also 65,535 octets.
10+
DEFAULT_CAPACITY = 0xFFFF
11+
912
# @param capacity [Integer] The initial window size, typically from the settings.
10-
def initialize(capacity = 0xFFFF)
13+
def initialize(capacity = DEFAULT_CAPACITY)
1114
# This is the main field required:
1215
@available = capacity
1316

@@ -75,30 +78,38 @@ def inspect
7578

7679
# This is a window which efficiently maintains a desired capacity.
7780
class LocalWindow < Window
78-
def initialize(capacity = 0xFFFF, desired: nil)
81+
def initialize(capacity = DEFAULT_CAPACITY, desired: nil)
7982
super(capacity)
8083

84+
# The desired capacity of the window, may be bigger than the initial capacity.
85+
# If that is the case, we will likely send a window update to the remote end to increase the capacity.
8186
@desired = desired
8287
end
8388

89+
# The desired capacity of the window.
8490
attr_accessor :desired
8591

8692
def wanted
8793
if @desired
8894
# We must send an update which allows at least @desired bytes to be sent.
8995
(@desired - @capacity) + @used
9096
else
91-
@used
97+
super
9298
end
9399
end
94100

95101
def limited?
96102
if @desired
97-
@available < @desired
103+
# Do not send window updates until we are less than half the desired capacity:
104+
@available < (@desired / 2)
98105
else
99106
super
100107
end
101108
end
109+
110+
def inspect
111+
"\#<#{self.class} used=#{@used} available=#{@available} capacity=#{@capacity} desired=#{@desired}>"
112+
end
102113
end
103114
end
104115
end

test/protocol/http2/window.rb

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
# Copyright, 2019-2024, by Samuel Williams.
55

66
require "protocol/http2/connection_context"
7+
require "json"
78

89
describe Protocol::HTTP2::Window do
910
let(:window) {subject.new}
@@ -54,5 +55,30 @@
5455
window.consume(window.available)
5556
expect(window.wanted).to be == 200
5657
end
58+
59+
it "is not limited if the half the desired capacity is available" do
60+
expect(window).not.to be(:limited?)
61+
62+
# Consume the entire window:
63+
window.consume(window.available)
64+
65+
expect(window).to be(:limited?)
66+
67+
# Expand the window by at least half the desired capacity:
68+
window.expand(window.desired / 2)
69+
70+
expect(window).not.to be(:limited?)
71+
end
72+
end
73+
74+
with "#limited?" do
75+
it "becomes limited after half the capacity is consumed" do
76+
expect(window).not.to be(:limited?)
77+
78+
# Consume a little more than half:
79+
window.consume(window.capacity / 2 + 2)
80+
81+
expect(window).to be(:limited?)
82+
end
5783
end
5884
end

test/protocol/http2/window_update_frame.rb

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,8 +203,12 @@ def before
203203
expect(frame).to be_a Protocol::HTTP2::WindowUpdateFrame
204204
end
205205

206-
expect(client).to receive(:receive_window_update)
206+
expect(client).to receive(:receive_window_update).twice
207207

208+
stream.send_data("*" * client.available_size)
209+
expect(server.read_frame).to be_a Protocol::HTTP2::DataFrame
210+
211+
frame = client.read_frame
208212
expect(frame).to be_a(Protocol::HTTP2::WindowUpdateFrame)
209213
expect(frame).to be(:connection?) # stream_id = 0
210214

@@ -237,5 +241,20 @@ def before
237241
end.to raise_exception(Protocol::HTTP2::ProtocolError, message: be =~ /Cannot update window of idle stream/)
238242
end
239243
end
240-
end
244+
245+
with "desired capacity" do
246+
it "should send window updates only as needed" do
247+
expect(client.local_window.desired).to be == 0xFFFF
248+
249+
server_stream = server[stream.id]
250+
251+
# Send a data frame that will consume less than half of the desired capacity:
252+
server_stream.send_data("*" * 0xFF)
253+
254+
expect(client.read_frame).to be_a Protocol::HTTP2::DataFrame
255+
expect(client.local_window.used).to be == 0xFF
256+
expect(client.local_window).not.to be(:limited?)
257+
end
258+
end
259+
end
241260
end

0 commit comments

Comments
 (0)