Skip to content

Commit 82fe647

Browse files
committed
Make backpressure configurable via BACKPRESSURE_CONFIG
1 parent 4ae329c commit 82fe647

File tree

4 files changed

+44
-9
lines changed

4 files changed

+44
-9
lines changed

Sources/App/Core/BackpressureMiddleware.swift

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,7 @@ import Vapor
1818

1919

2020
final class BackpressureMiddleware: AsyncMiddleware {
21-
22-
let slidingWindow: Duration
23-
let countLimit: Int
21+
let configuration: Configuration
2422

2523
public func respond(to request: Request, chainingTo next: AsyncResponder) async throws -> Response {
2624
guard let cfray = request.headers.first(name: "cf-ray") else {
@@ -29,7 +27,7 @@ final class BackpressureMiddleware: AsyncMiddleware {
2927
let epochSeconds = Int(Date().timeIntervalSince1970)
3028
let combinedKey = "\(cfray):\(epochSeconds)"
3129

32-
let slidingWindow = Int(slidingWindow.components.seconds)
30+
let slidingWindow = configuration.slidingWindow
3331

3432
@Dependency(\.redis) var redis
3533

@@ -48,16 +46,41 @@ final class BackpressureMiddleware: AsyncMiddleware {
4846
}
4947
}
5048

51-
if countOverWindow >= countLimit {
49+
50+
if countOverWindow >= configuration.requestLimit {
5251
request.logger.log(level: .warning, "BackpressureMiddleware acting on request with cf-ray '\(cfray)' due to \(countOverWindow) requests in the last \(slidingWindow) seconds.")
53-
throw Abort(.tooManyRequests)
52+
53+
switch configuration.mode {
54+
case .block:
55+
throw Abort(.tooManyRequests)
56+
case .delay(let duration):
57+
try await Task.sleep(for: .init(seconds: duration))
58+
return try await next.respond(to: request)
59+
}
5460
} else {
5561
return try await next.respond(to: request)
5662
}
5763
}
5864

59-
init(slidingWindow: Duration, countLimit: Int) {
60-
self.slidingWindow = slidingWindow
61-
self.countLimit = countLimit
65+
init(configuration: Configuration) {
66+
self.configuration = configuration
67+
}
68+
69+
struct Configuration: Codable {
70+
var mode: Mode
71+
var requestLimit: Int
72+
var slidingWindow: Int // in seconds
73+
74+
enum Mode: Codable {
75+
case block
76+
case delay(seconds: Double)
77+
}
78+
}
79+
}
80+
81+
82+
extension Duration {
83+
init(seconds: Double) {
84+
self = .microseconds(seconds/1e-6)
6285
}
6386
}

Sources/App/Core/Dependencies/EnvironmentClient.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ struct EnvironmentClient {
3232
var awsDocsBucket: @Sendable () -> String?
3333
var awsReadmeBucket: @Sendable () -> String?
3434
var awsSecretAccessKey: @Sendable () -> String?
35+
var backpressureConfiguration: @Sendable () -> BackpressureMiddleware.Configuration?
3536
var builderToken: @Sendable () -> String?
3637
var buildTimeout: @Sendable () -> Int = { XCTFail("buildTimeout"); return 10 }
3738
var buildTriggerAllowList: @Sendable () -> [Package.Id] = { XCTFail("buildTriggerAllowList"); return [] }
@@ -85,6 +86,10 @@ extension EnvironmentClient: DependencyKey {
8586
awsDocsBucket: { Environment.get("AWS_DOCS_BUCKET") },
8687
awsReadmeBucket: { Environment.get("AWS_README_BUCKET") },
8788
awsSecretAccessKey: { Environment.get("AWS_SECRET_ACCESS_KEY") },
89+
backpressureConfiguration: {
90+
Environment.decode("BACKPRESSURE_CONFIG",
91+
as: BackpressureMiddleware.Configuration.self)
92+
},
8893
builderToken: { Environment.get("BUILDER_TOKEN") },
8994
buildTimeout: { Environment.get("BUILD_TIMEOUT").flatMap(Int.init) ?? 10 },
9095
buildTriggerAllowList: {
@@ -177,6 +182,7 @@ extension EnvironmentClient: TestDependencyKey {
177182
// mechanism for a few heavily used dependencies at the moment.
178183
var mock = Self()
179184
mock.appVersion = { "test" }
185+
mock.backpressureConfiguration = { nil }
180186
mock.current = { .development }
181187
mock.hideStagingBanner = { false }
182188
mock.siteURL = { "http://localhost:8080" }

Sources/App/configure.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ public func configure(_ app: Application) async throws -> String {
3939
// app.http.server.configuration.responseCompression = .enabled
4040
// app.http.server.configuration.requestDecompression = .enabled
4141

42+
@Dependency(\.environment) var environment
43+
if let config = environment.backpressureConfiguration() {
44+
print(config)
45+
app.middleware.use(BackpressureMiddleware(configuration: config))
46+
}
4247
app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory))
4348
app.middleware.use(ErrorMiddleware())
4449

app.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ x-shared: &shared
2828
AWS_DOCS_BUCKET: ${AWS_DOCS_BUCKET}
2929
AWS_README_BUCKET: ${AWS_README_BUCKET}
3030
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}
31+
BACKPRESSURE_CONFIG: ${BACKPRESSURE_CONFIG}
3132
BUILD_TIMEOUT: ${BUILD_TIMEOUT}
3233
BUILD_TRIGGER_ALLOW_LIST: ${BUILD_TRIGGER_ALLOW_LIST}
3334
BUILD_TRIGGER_DOWNSCALING: ${BUILD_TRIGGER_DOWNSCALING}

0 commit comments

Comments
 (0)