Module: HTTP2::FlowBuffer

Includes:
Error
Included in:
Connection, Stream
Defined in:
lib/http/2/flow_buffer.rb,
sig/flow_buffer.rbs

Overview

Implementation of stream and connection DATA flow control: frames may be split and / or may be buffered based on current flow control window.

Constant Summary collapse

MAX_WINDOW_SIZE =

Returns:

  • (Integer)
(2 << 30) - 1

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Error

#self?.types

Instance Attribute Details

#send_bufferFrameBuffer (readonly)

Returns the value of attribute send_buffer.

Returns:



10
11
12
# File 'lib/http/2/flow_buffer.rb', line 10

def send_buffer
  @send_buffer
end

Instance Method Details

#buffered_amountInteger

Amount of buffered data. Only DATA payloads are subject to flow stream and connection flow control.

Returns:

  • (Integer)


18
19
20
# File 'lib/http/2/flow_buffer.rb', line 18

def buffered_amount
  @send_buffer.bytesize
end

#calculate_window_update(window_max_size) ⇒ void

This method returns an undefined value.

Parameters:

  • (Integer)


34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/http/2/flow_buffer.rb', line 34

def calculate_window_update(window_max_size)
  # If DATA frame is received with length > 0 and
  # current received window size + delta length is strictly larger than
  # local window size, it throws a flow control error.
  #
  error(:flow_control_error) if @local_window < 0

  # Send WINDOW_UPDATE if the received window size goes over
  # the local window size / 2.
  #
  # The HTTP/2 spec mandates that every DATA frame received
  # generates a WINDOW_UPDATE to send. In some cases however,
  # (ex: DATA frames with short payloads),
  # the noise generated by flow control frames creates enough
  # congestion for this to be deemed very inefficient.
  #
  # This heuristic was inherited from nghttp, which delays the
  # WINDOW_UPDATE until at least half the window is exhausted.
  # This works because the sender doesn't need those increments
  # until the receiver window is exhausted, after which he'll be
  # waiting for the WINDOW_UPDATE frame.
  return unless @local_window <= (window_max_size / 2)

  window_update(window_max_size - @local_window)
end

#flushvoid

This method returns an undefined value.



22
23
24
# File 'lib/http/2/flow_buffer.rb', line 22

def flush
  send_data
end

#process_window_update(frame:, encode: false) ⇒ void

This method returns an undefined value.

Parameters:

  • frame: (window_update_frame)
  • encode: (Boolean) (defaults to: false)


104
105
106
107
108
109
110
111
112
113
114
# File 'lib/http/2/flow_buffer.rb', line 104

def process_window_update(frame:, encode: false)
  return if frame[:ignore]

  if (increment = frame[:increment])
    raise ProtocolError, "increment MUST be higher than zero" if increment.zero?

    @remote_window += increment
    error(:flow_control_error, msg: "window size too large") if @remote_window > MAX_WINDOW_SIZE
  end
  send_data(nil, encode)
end

#send_data(frame = nil, encode = false) ⇒ void

This method returns an undefined value.

Buffers outgoing DATA frames and applies flow control logic to split and emit DATA frames based on current flow control window. If the window is large enough, the data is sent immediately. Otherwise, the data is buffered until the flow control window is updated.

Buffered DATA frames are emitted in FIFO order.

Parameters:

  • frame (Hash) (defaults to: nil)
  • encode (Boolean) (defaults to: false)

    set to true by connection



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/http/2/flow_buffer.rb', line 69

def send_data(frame = nil, encode = false)
  if frame
    if @send_buffer.empty?
      frame_size = frame[:payload].bytesize
      end_stream = frame[:flags].anybits?(END_STREAM)
      # if buffer is empty, and frame is either end 0 length OR
      # is within available window size, skip buffering and send immediately.
      if @remote_window.positive?
        return send_frame(frame, encode) if frame_size <= @remote_window
      elsif frame_size.zero? && end_stream
        return send_frame(frame, encode)
      end
    end

    @send_buffer << frame
  end

  while (frame = @send_buffer.retrieve(@remote_window))
    send_frame(frame, encode)
  end
end

#send_frame(frame, encode) ⇒ void

This method returns an undefined value.

Parameters:

  • frame (data_frame_props)
  • encode (Boolean)


91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/http/2/flow_buffer.rb', line 91

def send_frame(frame, encode)
  sent = frame[:payload].bytesize

  manage_state(frame) do
    if encode
      encode(frame)
    else
      emit(:frame, frame)
    end
    @remote_window -= sent
  end
end

#update_local_window(frame) ⇒ void

This method returns an undefined value.

Parameters:

  • frame (data_frame)


28
29
30
31
32
# File 'lib/http/2/flow_buffer.rb', line 28

def update_local_window(frame)
  frame_size = frame[:payload].bytesize
  frame_size += frame.fetch(:padding, 0)
  @local_window -= frame_size
end