@@ -19,6 +19,14 @@ import NIOHTTP1
19
19
import NIOSSL
20
20
21
21
final 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
+
22
30
let task : HTTPClient . Task < Delegate . Response >
23
31
var eventLoop : EventLoop {
24
32
self . task. eventLoop
@@ -30,6 +38,9 @@ final class RequestBag<Delegate: HTTPClientResponseDelegate> {
30
38
// the request state is synchronized on the task eventLoop
31
39
private var state : StateMachine
32
40
41
+ // the consume body part stack depth is synchronized on the task event loop.
42
+ private var consumeBodyPartStackDepth : Int
43
+
33
44
// MARK: HTTPClientTask properties
34
45
35
46
var logger : Logger {
@@ -55,6 +66,7 @@ final class RequestBag<Delegate: HTTPClientResponseDelegate> {
55
66
self . eventLoopPreference = eventLoopPreference
56
67
self . task = task
57
68
self . state = . init( redirectHandler: redirectHandler)
69
+ self . consumeBodyPartStackDepth = 0
58
70
self . request = request
59
71
self . connectionDeadline = connectionDeadline
60
72
self . requestOptions = requestOptions
@@ -290,16 +302,39 @@ final class RequestBag<Delegate: HTTPClientResponseDelegate> {
290
302
private func consumeMoreBodyData0( resultOfPreviousConsume result: Result < Void , Error > ) {
291
303
self . task. eventLoop. assertInEventLoop ( )
292
304
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
+
293
321
let consumptionAction = self . state. consumeMoreBodyData ( resultOfPreviousConsume: result)
294
322
295
323
switch consumptionAction {
296
324
case . consume( let byteBuffer) :
297
325
self . delegate. didReceiveBodyPart ( task: self . task, byteBuffer)
298
326
. hop ( to: self . task. eventLoop)
299
- . whenComplete {
300
- switch $0 {
327
+ . whenComplete { result in
328
+ switch result {
301
329
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
+ }
303
338
case . failure( let error) :
304
339
self . fail ( error)
305
340
}
0 commit comments