@@ -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