@@ -19,6 +19,14 @@ import NIOHTTP1
1919import NIOSSL
2020
2121final class RequestBag < Delegate: HTTPClientResponseDelegate > {
22+ /// Defends against the call stack getting too large when consuming body parts.
23+ ///
24+ /// If the response body comes in lots of tiny chunks, we'll deliver those tiny chunks to users
25+ /// one at a time.
26+ private static var maxConsumeBodyPartStackDepth : Int {
27+ 50
28+ }
29+
2230 let task : HTTPClient . Task < Delegate . Response >
2331 var eventLoop : EventLoop {
2432 self . task. eventLoop
@@ -30,6 +38,9 @@ final class RequestBag<Delegate: HTTPClientResponseDelegate> {
3038 // the request state is synchronized on the task eventLoop
3139 private var state : StateMachine
3240
41+ // the consume body part stack depth is synchronized on the task event loop.
42+ private var consumeBodyPartStackDepth : Int
43+
3344 // MARK: HTTPClientTask properties
3445
3546 var logger : Logger {
@@ -55,6 +66,7 @@ final class RequestBag<Delegate: HTTPClientResponseDelegate> {
5566 self . eventLoopPreference = eventLoopPreference
5667 self . task = task
5768 self . state = . init( redirectHandler: redirectHandler)
69+ self . consumeBodyPartStackDepth = 0
5870 self . request = request
5971 self . connectionDeadline = connectionDeadline
6072 self . requestOptions = requestOptions
@@ -290,16 +302,39 @@ final class RequestBag<Delegate: HTTPClientResponseDelegate> {
290302 private func consumeMoreBodyData0( resultOfPreviousConsume result: Result < Void , Error > ) {
291303 self . task. eventLoop. assertInEventLoop ( )
292304
305+ // We get defensive here about the maximum stack depth. It's possible for the `didReceiveBodyPart`
306+ // future to be returned to us completed. If it is, we will recurse back into this method. To
307+ // break that recursion we have a max stack depth which we increment and decrement in this method:
308+ // if it gets too large, instead of recurring we'll insert an `eventLoop.execute`, which will
309+ // manually break the recursion and unwind the stack.
310+ //
311+ // Note that we don't bother starting this at the various other call sites that _begin_ stacks
312+ // that risk ending up in this loop. That's because we don't need an accurate count: our limit is
313+ // a best-effort target anyway, one stack frame here or there does not put us at risk. We're just
314+ // trying to prevent ourselves looping out of control.
315+ self . consumeBodyPartStackDepth += 1
316+ defer {
317+ self . consumeBodyPartStackDepth -= 1
318+ assert ( self . consumeBodyPartStackDepth >= 0 )
319+ }
320+
293321 let consumptionAction = self . state. consumeMoreBodyData ( resultOfPreviousConsume: result)
294322
295323 switch consumptionAction {
296324 case . consume( let byteBuffer) :
297325 self . delegate. didReceiveBodyPart ( task: self . task, byteBuffer)
298326 . hop ( to: self . task. eventLoop)
299- . whenComplete {
300- switch $0 {
327+ . whenComplete { result in
328+ switch result {
301329 case . success:
302- self . consumeMoreBodyData0 ( resultOfPreviousConsume: $0)
330+ if self . consumeBodyPartStackDepth < Self . maxConsumeBodyPartStackDepth {
331+ self . consumeMoreBodyData0 ( resultOfPreviousConsume: result)
332+ } else {
333+ // We need to unwind the stack, let's take a break.
334+ self . task. eventLoop. execute {
335+ self . consumeMoreBodyData0 ( resultOfPreviousConsume: result)
336+ }
337+ }
303338 case . failure( let error) :
304339 self . fail ( error)
305340 }
0 commit comments