File: RecvByteBufferAllocator.swift

package info (click to toggle)
swiftlang 6.1.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky
  • size: 2,791,532 kB
  • sloc: cpp: 9,901,743; ansic: 2,201,431; asm: 1,091,827; python: 308,252; objc: 82,166; f90: 80,126; lisp: 38,358; pascal: 25,559; sh: 20,429; ml: 5,058; perl: 4,745; makefile: 4,484; awk: 3,535; javascript: 3,018; xml: 918; fortran: 664; cs: 573; ruby: 396
file content (136 lines) | stat: -rw-r--r-- 5,410 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

/// Allocates `ByteBuffer`s to be used to read bytes from a `Channel` and records the number of the actual bytes that were used.
public protocol RecvByteBufferAllocator: _NIOPreconcurrencySendable {
    /// Allocates a new `ByteBuffer` that will be used to read bytes from a `Channel`.
    func buffer(allocator: ByteBufferAllocator) -> ByteBuffer

    /// Returns the next size of buffer which should be returned by ``buffer(allocator:)``.
    func nextBufferSize() -> Int?

    /// Records the actual number of bytes that were read by the last socket call.
    ///
    /// - parameters:
    ///     - actualReadBytes: The number of bytes that were used by the previous allocated `ByteBuffer`
    /// - returns: `true` if the next call to `buffer` may return a bigger buffer then the last call to `buffer`.
    mutating func record(actualReadBytes: Int) -> Bool
}

extension RecvByteBufferAllocator {
    // Default implementation to maintain API compatability.
    public func nextBufferSize() -> Int? {
        return nil
    }
}

/// `RecvByteBufferAllocator` which will always return a `ByteBuffer` with the same fixed size no matter what was recorded.
public struct FixedSizeRecvByteBufferAllocator: RecvByteBufferAllocator {
    public let capacity: Int

    public init(capacity: Int) {
        precondition(capacity > 0)
        self.capacity = capacity
    }

    public mutating func record(actualReadBytes: Int) -> Bool {
        // Returns false as we always allocate the same size of buffers.
        return false
    }

    public func buffer(allocator: ByteBufferAllocator) -> ByteBuffer {
        return allocator.buffer(capacity: self.capacity)
    }
}

extension FixedSizeRecvByteBufferAllocator {
    public func nextBufferSize() -> Int? {
        return self.capacity
    }
}

/// `RecvByteBufferAllocator` which will gracefully increment or decrement the buffer size on the feedback that was recorded.
public struct AdaptiveRecvByteBufferAllocator: RecvByteBufferAllocator {
    public let minimum: Int
    public let maximum: Int
    public let initial: Int

    private var nextReceiveBufferSize: Int
    private var decreaseNow: Bool

    private static let maximumAllocationSize = 1 << 30

    public init() {
        self.init(minimum: 64, initial: 2048, maximum: 65536)
    }

    public init(minimum: Int, initial: Int, maximum: Int) {
        precondition(minimum >= 0, "minimum: \(minimum)")
        precondition(initial >= minimum, "initial: \(initial)")
        precondition(maximum >= initial, "maximum: \(maximum)")

        // We need to round all of these numbers to a power of 2. Initial will be rounded down,
        // minimum down, and maximum up.
        self.minimum = min(minimum, AdaptiveRecvByteBufferAllocator.maximumAllocationSize).previousPowerOf2()
        self.initial = min(initial, AdaptiveRecvByteBufferAllocator.maximumAllocationSize).previousPowerOf2()
        self.maximum = min(maximum, AdaptiveRecvByteBufferAllocator.maximumAllocationSize).nextPowerOf2()

        self.nextReceiveBufferSize = self.initial
        self.decreaseNow = false
    }

    public func buffer(allocator: ByteBufferAllocator) -> ByteBuffer {
        return allocator.buffer(capacity: self.nextReceiveBufferSize)
    }

    public mutating func record(actualReadBytes: Int) -> Bool {
        precondition(self.nextReceiveBufferSize % 2 == 0)
        precondition(self.nextReceiveBufferSize >= self.minimum)
        precondition(self.nextReceiveBufferSize <= self.maximum)

        var mayGrow = false

        // This right shift is safe: nextReceiveBufferSize can never be negative, so this will stop at 0.
        let lowerBound = self.nextReceiveBufferSize &>> 1

        // Here we need to be careful with 32-bit systems: if maximum is too large then any shift or multiply will overflow, which
        // we don't want. Instead we check, and clamp to this current value if we overflow.
        let upperBoundCandidate = Int(truncatingIfNeeded: Int64(self.nextReceiveBufferSize) &<< 1)
        let upperBound = upperBoundCandidate <= 0 ? self.nextReceiveBufferSize : upperBoundCandidate

        if actualReadBytes <= lowerBound && lowerBound >= self.minimum {
            if self.decreaseNow {
                self.nextReceiveBufferSize = lowerBound
                self.decreaseNow = false
            } else {
                self.decreaseNow = true
            }
        } else if actualReadBytes >= self.nextReceiveBufferSize && upperBound <= self.maximum &&
                  self.nextReceiveBufferSize != upperBound {
            self.nextReceiveBufferSize = upperBound
            self.decreaseNow = false
            mayGrow = true
        } else {
            self.decreaseNow = false
        }

        return mayGrow
    }
}

extension AdaptiveRecvByteBufferAllocator {
    public func nextBufferSize() -> Int? {
        return self.nextReceiveBufferSize
    }
}