Skip to content

Commit d889fe8

Browse files
committed
Add passthrough configuration parameters for the new idle connection pruning support in AsyncKit 1.21.0, enabling direct use by Fluent users.
1 parent 095bc5a commit d889fe8

File tree

7 files changed

+77
-25
lines changed

7 files changed

+77
-25
lines changed

.github/workflows/test.yml

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ jobs:
2929
container: swift:noble
3030
steps:
3131
- name: Checkout
32-
uses: actions/checkout@v4
32+
uses: actions/checkout@v5
3333
with: { 'fetch-depth': 0 }
3434
- name: API breaking changes
3535
run: |
@@ -74,10 +74,12 @@ jobs:
7474
POSTGRES_HOST_AUTH_METHOD: ${{ matrix.postgres-auth }}
7575
POSTGRES_INITDB_ARGS: --auth-host=${{ matrix.postgres-auth }}
7676
steps:
77+
- name: Ensure curl is available
78+
run: apt-get update -y && apt-get install -y curl
7779
- name: Check out package
78-
uses: actions/checkout@v4
80+
uses: actions/checkout@v5
7981
- name: Run all tests
80-
run: swift test --sanitize=thread --enable-code-coverage
82+
run: swift test --enable-code-coverage
8183
- name: Submit coverage report to Codecov.io
8284
uses: vapor/[email protected]
8385
with:
@@ -115,6 +117,6 @@ jobs:
115117
PGPASSWORD="${POSTGRES_PASSWORD_A}" psql -w "${POSTGRES_DB_B}" <<<"ALTER SCHEMA public OWNER TO ${POSTGRES_USER_B};"
116118
timeout-minutes: 15
117119
- name: Checkout code
118-
uses: actions/checkout@v4
120+
uses: actions/checkout@v5
119121
- name: Run all tests
120-
run: swift test --sanitize=thread
122+
run: swift test

Package.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ let package = Package(
1313
.library(name: "FluentPostgresDriver", targets: ["FluentPostgresDriver"]),
1414
],
1515
dependencies: [
16-
.package(url: "https://github.com/vapor/fluent-kit.git", from: "1.49.0"),
17-
.package(url: "https://github.com/vapor/postgres-kit.git", from: "2.13.4"),
16+
.package(url: "https://github.com/vapor/fluent-kit.git", from: "1.52.2"),
17+
.package(url: "https://github.com/vapor/postgres-kit.git", from: "2.14.0"),
18+
.package(url: "https://github.com/vapor/async-kit.git", from: "1.21.0"),
1819
],
1920
targets: [
2021
.target(
@@ -23,6 +24,7 @@ let package = Package(
2324
.product(name: "FluentKit", package: "fluent-kit"),
2425
.product(name: "FluentSQL", package: "fluent-kit"),
2526
.product(name: "PostgresKit", package: "postgres-kit"),
27+
.product(name: "AsyncKit", package: "async-kit"),
2628
],
2729
swiftSettings: swiftSettings
2830
),
@@ -39,6 +41,7 @@ let package = Package(
3941

4042
var swiftSettings: [SwiftSetting] { [
4143
.enableUpcomingFeature("ExistentialAny"),
44+
.enableUpcomingFeature("MemberImportVisibility"),
4245
.enableUpcomingFeature("ConciseMagicFile"),
4346
.enableUpcomingFeature("ForwardTrailingClosures"),
4447
.enableUpcomingFeature("DisableOutwardActorInference"),
Lines changed: 17 additions & 13 deletions
Loading

Sources/FluentPostgresDriver/FluentPostgresConfiguration.swift

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,19 @@ extension DatabaseConfigurationFactory {
1515
/// - urlString: The URL describing the connection, as a string.
1616
/// - maxConnectionsPerEventLoop: Maximum number of connections to open per event loop.
1717
/// - connectionPoolTimeout: Maximum time to wait for a connection to become available per request.
18+
/// - pruneInterval: How often to check for and prune idle database connections. If `nil` (the default),
19+
/// no pruning is performed.
20+
/// - maxIdleTimeBeforePruning: How long a connection may remain idle before being pruned, if pruning is enabled.
21+
/// Defaults to 2 minutes. Ignored if `pruneInterval` is `nil`.
1822
/// - encodingContext: Encoding context to use for serializing data.
1923
/// - decodingContext: Decoding context to use for deserializing data.
2024
/// - sqlLogLevel: Level at which to log SQL queries.
2125
public static func postgres(
2226
url urlString: String,
2327
maxConnectionsPerEventLoop: Int = 1,
2428
connectionPoolTimeout: TimeAmount = .seconds(10),
29+
pruneInterval: TimeAmount? = nil,
30+
maxIdleTimeBeforePruning: TimeAmount = .seconds(120),
2531
encodingContext: PostgresEncodingContext<some PostgresJSONEncoder> = .default,
2632
decodingContext: PostgresDecodingContext<some PostgresJSONDecoder> = .default,
2733
sqlLogLevel: Logger.Level = .debug
@@ -30,6 +36,8 @@ extension DatabaseConfigurationFactory {
3036
configuration: try .init(url: urlString),
3137
maxConnectionsPerEventLoop: maxConnectionsPerEventLoop,
3238
connectionPoolTimeout: connectionPoolTimeout,
39+
pruneInterval: pruneInterval,
40+
maxIdleTimeBeforePruning: maxIdleTimeBeforePruning,
3341
encodingContext: encodingContext,
3442
decodingContext: decodingContext,
3543
sqlLogLevel: sqlLogLevel
@@ -44,13 +52,19 @@ extension DatabaseConfigurationFactory {
4452
/// - url: The URL describing the connection.
4553
/// - maxConnectionsPerEventLoop: Maximum number of connections to open per event loop.
4654
/// - connectionPoolTimeout: Maximum time to wait for a connection to become available per request.
55+
/// - pruneInterval: How often to check for and prune idle database connections. If `nil` (the default),
56+
/// no pruning is performed.
57+
/// - maxIdleTimeBeforePruning: How long a connection may remain idle before being pruned, if pruning is enabled.
58+
/// Defaults to 2 minutes. Ignored if `pruneInterval` is `nil`.
4759
/// - encodingContext: Encoding context to use for serializing data.
4860
/// - decodingContext: Decoding context to use for deserializing data.
4961
/// - sqlLogLevel: Level at which to log SQL queries.
5062
public static func postgres(
5163
url: URL,
5264
maxConnectionsPerEventLoop: Int = 1,
5365
connectionPoolTimeout: TimeAmount = .seconds(10),
66+
pruneInterval: TimeAmount? = nil,
67+
maxIdleTimeBeforePruning: TimeAmount = .seconds(120),
5468
encodingContext: PostgresEncodingContext<some PostgresJSONEncoder> = .default,
5569
decodingContext: PostgresDecodingContext<some PostgresJSONDecoder> = .default,
5670
sqlLogLevel: Logger.Level = .debug
@@ -59,6 +73,8 @@ extension DatabaseConfigurationFactory {
5973
configuration: try .init(url: url),
6074
maxConnectionsPerEventLoop: maxConnectionsPerEventLoop,
6175
connectionPoolTimeout: connectionPoolTimeout,
76+
pruneInterval: pruneInterval,
77+
maxIdleTimeBeforePruning: maxIdleTimeBeforePruning,
6278
encodingContext: encodingContext,
6379
decodingContext: decodingContext,
6480
sqlLogLevel: sqlLogLevel
@@ -71,13 +87,19 @@ extension DatabaseConfigurationFactory {
7187
/// - configuration: A ``PostgresKit/SQLPostgresConfiguration`` describing the connection.
7288
/// - maxConnectionsPerEventLoop: Maximum number of connections to open per event loop.
7389
/// - connectionPoolTimeout: Maximum time to wait for a connection to become available per request.
90+
/// - pruneInterval: How often to check for and prune idle database connections. If `nil` (the default),
91+
/// no pruning is performed.
92+
/// - maxIdleTimeBeforePruning: How long a connection may remain idle before being pruned, if pruning is enabled.
93+
/// Defaults to 2 minutes. Ignored if `pruneInterval` is `nil`.
7494
/// - encodingContext: Encoding context to use for serializing data.
7595
/// - decodingContext: Decoding context to use for deserializing data.
7696
/// - sqlLogLevel: Level at which to log SQL queries.
7797
public static func postgres(
7898
configuration: SQLPostgresConfiguration,
7999
maxConnectionsPerEventLoop: Int = 1,
80100
connectionPoolTimeout: TimeAmount = .seconds(10),
101+
pruneInterval: TimeAmount? = nil,
102+
maxIdleTimeBeforePruning: TimeAmount = .seconds(120),
81103
encodingContext: PostgresEncodingContext<some PostgresJSONEncoder>,
82104
decodingContext: PostgresDecodingContext<some PostgresJSONDecoder>,
83105
sqlLogLevel: Logger.Level = .debug
@@ -87,6 +109,8 @@ extension DatabaseConfigurationFactory {
87109
configuration: configuration,
88110
maxConnectionsPerEventLoop: maxConnectionsPerEventLoop,
89111
connectionPoolTimeout: connectionPoolTimeout,
112+
pruningInterval: pruneInterval,
113+
maxIdleTimeBeforePruning: maxIdleTimeBeforePruning,
90114
encodingContext: encodingContext,
91115
decodingContext: decodingContext,
92116
sqlLogLevel: sqlLogLevel
@@ -108,56 +132,68 @@ extension DatabaseConfigurationFactory {
108132
///
109133
/// _ = DatabaseConfigurationFactory.postgres(configuration: .init(unixDomainSocketPath: "", username: ""))
110134
extension DatabaseConfigurationFactory {
111-
/// ``postgres(configuration:maxConnectionsPerEventLoop:connectionPoolTimeout:encodingContext:decodingContext:sqlLogLevel:)``
135+
/// ``postgres(configuration:maxConnectionsPerEventLoop:connectionPoolTimeout:pruneInterval:maxIdleTimeBeforePruning:encodingContext:decodingContext:sqlLogLevel:)``
112136
/// with the `decodingContext` defaulted.
113137
public static func postgres(
114138
configuration: SQLPostgresConfiguration,
115139
maxConnectionsPerEventLoop: Int = 1,
116140
connectionPoolTimeout: TimeAmount = .seconds(10),
141+
pruneInterval: TimeAmount? = nil,
142+
maxIdleTimeBeforePruning: TimeAmount = .seconds(120),
117143
encodingContext: PostgresEncodingContext<some PostgresJSONEncoder>,
118144
sqlLogLevel: Logger.Level = .debug
119145
) -> DatabaseConfigurationFactory {
120146
.postgres(
121147
configuration: configuration,
122148
maxConnectionsPerEventLoop: maxConnectionsPerEventLoop,
123149
connectionPoolTimeout: connectionPoolTimeout,
150+
pruneInterval: pruneInterval,
151+
maxIdleTimeBeforePruning: maxIdleTimeBeforePruning,
124152
encodingContext: encodingContext,
125153
decodingContext: .default,
126154
sqlLogLevel: sqlLogLevel
127155
)
128156
}
129157

130-
/// ``postgres(configuration:maxConnectionsPerEventLoop:connectionPoolTimeout:encodingContext:decodingContext:sqlLogLevel:)``
158+
/// ``postgres(configuration:maxConnectionsPerEventLoop:connectionPoolTimeout:pruneInterval:maxIdleTimeBeforePruning:encodingContext:decodingContext:sqlLogLevel:)``
131159
/// with the `encodingContext` defaulted.
132160
public static func postgres(
133161
configuration: SQLPostgresConfiguration,
134162
maxConnectionsPerEventLoop: Int = 1,
135163
connectionPoolTimeout: TimeAmount = .seconds(10),
164+
pruneInterval: TimeAmount? = nil,
165+
maxIdleTimeBeforePruning: TimeAmount = .seconds(120),
136166
decodingContext: PostgresDecodingContext<some PostgresJSONDecoder>,
137167
sqlLogLevel: Logger.Level = .debug
138168
) -> DatabaseConfigurationFactory {
139169
.postgres(
140170
configuration: configuration,
141171
maxConnectionsPerEventLoop: maxConnectionsPerEventLoop,
142172
connectionPoolTimeout: connectionPoolTimeout,
173+
pruneInterval: pruneInterval,
174+
maxIdleTimeBeforePruning: maxIdleTimeBeforePruning,
143175
encodingContext: .default,
144176
decodingContext: decodingContext,
145177
sqlLogLevel: sqlLogLevel
146178
)
147179
}
148180

149-
/// ``postgres(configuration:maxConnectionsPerEventLoop:connectionPoolTimeout:encodingContext:decodingContext:sqlLogLevel:)``
181+
/// ``postgres(configuration:maxConnectionsPerEventLoop:connectionPoolTimeout:pruneInterval:maxIdleTimeBeforePruning:encodingContext:decodingContext:sqlLogLevel:)``
150182
/// with both `encodingContext` and `decodingContext` defaulted.
151183
public static func postgres(
152184
configuration: SQLPostgresConfiguration,
153185
maxConnectionsPerEventLoop: Int = 1,
154186
connectionPoolTimeout: TimeAmount = .seconds(10),
187+
pruneInterval: TimeAmount? = nil,
188+
maxIdleTimeBeforePruning: TimeAmount = .seconds(120),
155189
sqlLogLevel: Logger.Level = .debug
156190
) -> DatabaseConfigurationFactory {
157191
.postgres(
158192
configuration: configuration,
159193
maxConnectionsPerEventLoop: maxConnectionsPerEventLoop,
160194
connectionPoolTimeout: connectionPoolTimeout,
195+
pruneInterval: pruneInterval,
196+
maxIdleTimeBeforePruning: maxIdleTimeBeforePruning,
161197
encodingContext: .default,
162198
decodingContext: .default,
163199
sqlLogLevel: sqlLogLevel
@@ -171,6 +207,8 @@ struct FluentPostgresConfiguration<E: PostgresJSONEncoder, D: PostgresJSONDecode
171207
fileprivate let configuration: SQLPostgresConfiguration
172208
let maxConnectionsPerEventLoop: Int
173209
let connectionPoolTimeout: TimeAmount
210+
let pruningInterval: TimeAmount?
211+
let maxIdleTimeBeforePruning: TimeAmount
174212
let encodingContext: PostgresEncodingContext<E>
175213
let decodingContext: PostgresDecodingContext<D>
176214
let sqlLogLevel: Logger.Level
@@ -181,6 +219,8 @@ struct FluentPostgresConfiguration<E: PostgresJSONEncoder, D: PostgresJSONDecode
181219
source: connectionSource,
182220
maxConnectionsPerEventLoop: self.maxConnectionsPerEventLoop,
183221
requestTimeout: self.connectionPoolTimeout,
222+
pruneInterval: self.pruningInterval,
223+
maxIdleTimeBeforePruning: self.maxIdleTimeBeforePruning,
184224
on: databases.eventLoopGroup
185225
)
186226

Sources/FluentPostgresDriver/FluentPostgresDatabase.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ extension _FluentPostgresDatabase: Database {
6161
}
6262
}
6363

64-
func transaction<T>(_ closure: @escaping @Sendable (any Database) -> EventLoopFuture<T>) -> EventLoopFuture<T> {
64+
func transaction<T: Sendable>(_ closure: @escaping @Sendable (any Database) -> EventLoopFuture<T>) -> EventLoopFuture<T> {
6565
guard !self.inTransaction else {
6666
return closure(self)
6767
}

Sources/FluentPostgresDriver/PostgresConverterDelegate.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ struct PostgresConverterDelegate: SQLConverterDelegate {
2020
case .dictionary:
2121
SQLRaw("JSONB")
2222
case .array(of: let type):
23-
if let type = type, let dataType = self.customDataType(type) {
23+
if let type, let dataType = self.customDataType(type) {
2424
SQLArrayDataType(dataType: dataType)
2525
} else {
2626
SQLRaw("JSONB")

Tests/FluentPostgresDriverTests/FluentPostgresDriverTests.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import FluentBenchmark
22
import FluentKit
33
import FluentPostgresDriver
4+
import FluentSQL
45
import Logging
56
import PostgresKit
67
import SQLKit
@@ -310,6 +311,8 @@ extension DatabaseConfigurationFactory {
310311
return .postgres(
311312
configuration: baseSubconfig,
312313
connectionPoolTimeout: .seconds(30),
314+
pruneInterval: .seconds(30),
315+
maxIdleTimeBeforePruning: .seconds(60),
313316
encodingContext: encodingContext,
314317
decodingContext: decodingContext
315318
)

0 commit comments

Comments
 (0)