p2p-swift / Sources / IrdP2PSignaling / TokenBucket.swift
285 words
 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
import Foundation

/// A token bucket matching the relay's per-connection budgets so frames are
/// paced instead of dropped as `rate-limited`.
public struct TokenBucket: Sendable {
    public let capacity: Double
    public let refillPerSecond: Double
    private var tokens: Double
    private var last: Duration

    public init(capacity: Double, refillPerSecond: Double, now: Duration) {
        self.capacity = capacity
        self.refillPerSecond = refillPerSecond
        tokens = capacity
        last = now
    }

    private mutating func refill(now: Duration) {
        let elapsed = max(0, (now - last).totalSeconds)
        tokens = min(capacity, tokens + elapsed * refillPerSecond)
        last = now
    }

    /// Takes `cost` tokens, returning how long the caller must wait first (zero when
    /// the budget allows it now). The bucket goes into debt, so concurrent callers
    /// queue behind each other: each waits for its own share of the refill.
    public mutating func take(_ cost: Double, now: Duration) -> Duration {
        refill(now: now)
        tokens -= cost
        if tokens >= 0 { return .zero }
        return .milliseconds(Int((-tokens / refillPerSecond * 1000).rounded(.up)))
    }

    public func available(now: Duration) -> Double {
        var copy = self
        copy.refill(now: now)
        return max(0, copy.tokens)
    }
}

/// The two relay budgets: messages per second (with burst) and bytes per second (4× burst).
public struct RelayPacer: Sendable {
    public var messages: TokenBucket
    public var bytes: TokenBucket
    public var enabled = true

    public init(limits: AppDescriptor.Limits, now: Duration) {
        messages = TokenBucket(capacity: Double(limits.messageBurst), refillPerSecond: Double(limits.messagesPerSec), now: now)
        bytes = TokenBucket(capacity: Double(max(limits.maxBytesPerSec * 4, limits.maxFrameBytes)), refillPerSecond: Double(limits.maxBytesPerSec), now: now)
    }

    /// The wait before a frame of `byteCount` bytes may be sent.
    public mutating func take(byteCount: Int, now: Duration) -> Duration {
        guard enabled else { return .zero }
        let a = messages.take(1, now: now)
        let b = bytes.take(Double(byteCount), now: now)
        return max(a, b)
    }
}

static mirror of HEAD · about · clone: git clone https://ird.cod-sursa.ro/p2p-swift.git