Class: Raptor::DetachedBody
- Inherits:
-
Object
- Object
- Raptor::DetachedBody
- Defined in:
- lib/raptor/detached_body.rb,
sig/generated/raptor/detached_body.rbs
Overview
A bounded response body that may be written after its application thread has returned.
Defined Under Namespace
Classes: Budget
Constant Summary collapse
- DEFAULT_MAX_BUFFER_SIZE =
64 * 1024
Instance Method Summary collapse
-
#attach(budgets, wake, dispatch, finished) ⇒ Boolean
Connects the body to its reactor-owned response stream.
-
#close(trailers: {}) ⇒ void
Marks the body complete; the stream finishes once buffered bytes are written.
-
#closed? ⇒ Boolean
Returns whether the stream has closed.
-
#finish(reason) ⇒ void
Closes the stream and invokes its callback exactly once.
-
#initialize(max_buffer_size: DEFAULT_MAX_BUFFER_SIZE) ⇒ DetachedBody
constructor
Creates a detached body with a bounded byte buffer.
-
#next_size ⇒ Integer?
Returns the size of the next buffered chunk, 0 when closing, or nil while waiting for more data.
-
#on_close {|reason| ... } ⇒ DetachedBody
Registers a callback for when the response stream closes.
-
#on_open {|stream| ... } ⇒ DetachedBody
Registers a callback that runs on the request thread once the server accepts the response stream.
-
#open ⇒ void
Notifies the application that its response stream is ready.
-
#release(bytes) ⇒ void
Releases bytes from every budget attached to this body.
-
#reserve(bytes) ⇒ Boolean
Reserves bytes from every budget attached to this body.
-
#shift(max_bytes) ⇒ String?
Removes up to
max_bytesfrom the next buffered chunk. -
#trailers ⇒ Hash
Returns the response trailers supplied when the body closed.
-
#try_write(chunk) ⇒ Symbol
Adds body bytes without waiting for socket or flow-control capacity.
Constructor Details
#initialize(max_buffer_size: DEFAULT_MAX_BUFFER_SIZE) ⇒ DetachedBody
Creates a detached body with a bounded byte buffer.
72 73 74 75 76 77 78 79 80 81 82 83 |
# File 'lib/raptor/detached_body.rb', line 72 def initialize(max_buffer_size: DEFAULT_MAX_BUFFER_SIZE) raise ArgumentError, "max_buffer_size must be positive" unless max_buffer_size.positive? @max_buffer_size = max_buffer_size @state = Atom.new({ chunks: [], size: 0, closing: false, closed: false, notified: false, trailers: {} }) @budgets = [] @wake = proc {} @dispatch = proc { |callback| callback.call } @finished = proc {} @on_open = nil @on_close = nil end |
Instance Method Details
#attach(budgets, wake, dispatch, finished) ⇒ Boolean
Connects the body to its reactor-owned response stream.
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 |
# File 'lib/raptor/detached_body.rb', line 199 def attach(budgets, wake, dispatch, finished) @wake = wake @dispatch = dispatch @finished = finished size = @state.value[:size] reserved = [] budgets.each do |budget| unless budget.reserve(size) reserved.each { |held| held.release(size) } finish(:full) return false end reserved << budget end @budgets = budgets true end |
#close(trailers: {}) ⇒ void
This method returns an undefined value.
Marks the body complete; the stream finishes once buffered bytes are written.
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 |
# File 'lib/raptor/detached_body.rb', line 163 def close(trailers: {}) trailers = trailers.to_h do |name, value| value = value.is_a?(Array) ? value.map { |item| item.dup.freeze }.freeze : value.dup.freeze [name.dup.freeze, value] end.freeze wake = false @state.swap do |state| if state[:closed] || state[:closing] wake = false next state end wake = !state[:notified] state.merge(closing: true, notified: true, trailers: trailers) end @wake.call if wake end |
#closed? ⇒ Boolean
Returns whether the stream has closed.
186 187 188 |
# File 'lib/raptor/detached_body.rb', line 186 def closed? @state.value[:closed] end |
#finish(reason) ⇒ void
This method returns an undefined value.
Closes the stream and invokes its callback exactly once.
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 |
# File 'lib/raptor/detached_body.rb', line 284 def finish(reason) callback = false remaining = 0 @state.swap do |state| if state[:closed] callback = false remaining = 0 state else callback = true remaining = state[:size] state.merge(chunks: [], size: 0, closed: true, notified: false) end end return unless callback release(remaining) @dispatch.call(proc do begin @on_close&.call(reason) ensure @finished.call(reason) end end) end |
#next_size ⇒ Integer?
Returns the size of the next buffered chunk, 0 when closing, or nil while waiting for more data.
234 235 236 237 |
# File 'lib/raptor/detached_body.rb', line 234 def next_size state = @state.value state[:chunks].first&.bytesize || (0 if state[:closing]) end |
#on_close {|reason| ... } ⇒ DetachedBody
Registers a callback for when the response stream closes.
105 106 107 108 |
# File 'lib/raptor/detached_body.rb', line 105 def on_close(&block) @on_close = block self end |
#on_open {|stream| ... } ⇒ DetachedBody
Registers a callback that runs on the request thread once the server accepts the response stream.
93 94 95 96 |
# File 'lib/raptor/detached_body.rb', line 93 def on_open(&block) @on_open = block self end |
#open ⇒ void
This method returns an undefined value.
Notifies the application that its response stream is ready.
223 224 225 226 |
# File 'lib/raptor/detached_body.rb', line 223 def open @on_open&.call(self) @wake.call if @state.value[:notified] end |
#release(bytes) ⇒ void
This method returns an undefined value.
Releases bytes from every budget attached to this body.
336 337 338 |
# File 'lib/raptor/detached_body.rb', line 336 def release(bytes) @budgets.each { |budget| budget.release(bytes) } end |
#reserve(bytes) ⇒ Boolean
Reserves bytes from every budget attached to this body.
318 319 320 321 322 323 324 325 326 327 328 |
# File 'lib/raptor/detached_body.rb', line 318 def reserve(bytes) reserved = [] @budgets.each do |budget| unless budget.reserve(bytes) reserved.each { |held| held.release(bytes) } return false end reserved << budget end true end |
#shift(max_bytes) ⇒ String?
Removes up to max_bytes from the next buffered chunk.
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 |
# File 'lib/raptor/detached_body.rb', line 245 def shift(max_bytes) chunk = nil @state.swap do |state| chunk = nil first = state[:chunks].first next state unless first size = max_bytes < first.bytesize ? max_bytes : first.bytesize chunk = first.byteslice(0, size) chunks = if size == first.bytesize state[:chunks].drop(1) else [first.byteslice(size..-1).freeze] + state[:chunks].drop(1) end state.merge( chunks: chunks, size: state[:size] - size, notified: !chunks.empty? || state[:closing] ) end release(chunk.bytesize) if chunk chunk end |
#trailers ⇒ Hash
Returns the response trailers supplied when the body closed.
274 275 276 |
# File 'lib/raptor/detached_body.rb', line 274 def trailers @state.value[:trailers] end |
#try_write(chunk) ⇒ Symbol
Adds body bytes without waiting for socket or flow-control capacity.
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
# File 'lib/raptor/detached_body.rb', line 117 def try_write(chunk) raise TypeError, "body must yield String values" unless chunk.is_a?(String) chunk = chunk.dup.freeze if chunk.empty? state = @state.value return state[:closed] || state[:closing] ? :closed : :accepted end return :full unless reserve(chunk.bytesize) result = :closed wake = false @state.swap do |state| if state[:closed] || state[:closing] result = :closed wake = false state elsif state[:size] + chunk.bytesize > @max_buffer_size result = :full wake = false state else result = :accepted wake = !state[:notified] state.merge( chunks: state[:chunks] + [chunk], size: state[:size] + chunk.bytesize, notified: true ) end end if result == :accepted @wake.call if wake else release(chunk.bytesize) end result end |