Skip to content

Commit c369658

Browse files
authored
Miscellaneous cleanup (#6)
Bump the versions of our dependencies, clean up the docs, and update tests for the Concurrency-related changes in Vapor.
1 parent cad57b4 commit c369658

File tree

8 files changed

+106
-81
lines changed

8 files changed

+106
-81
lines changed

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2020 Matthieu Barthélemy
3+
Copyright (c) 2024 Gwynne Raskind
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

Package.swift

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ let package = Package(
1010
.library(name: "QueuesFluentDriver", targets: ["QueuesFluentDriver"]),
1111
],
1212
dependencies: [
13-
.package(url: "https://github.com/vapor/vapor.git", from: "4.92.1"),
14-
.package(url: "https://github.com/vapor/fluent.git", from: "4.9.0"),
15-
.package(url: "https://github.com/vapor/fluent-kit.git", from: "1.45.1"),
16-
.package(url: "https://github.com/vapor/sql-kit.git", from: "3.29.2"),
13+
.package(url: "https://github.com/vapor/vapor.git", from: "4.100.0"),
14+
.package(url: "https://github.com/vapor/fluent.git", from: "4.10.0"),
15+
.package(url: "https://github.com/vapor/fluent-kit.git", from: "1.48.4"),
16+
.package(url: "https://github.com/vapor/sql-kit.git", from: "3.30.0"),
1717
.package(url: "https://github.com/vapor/queues.git", from: "1.13.0"),
18-
.package(url: "https://github.com/vapor/fluent-sqlite-driver.git", from: "4.6.0"),
19-
.package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.9.0"),
20-
.package(url: "https://github.com/vapor/fluent-mysql-driver.git", from: "4.0.0"),
18+
.package(url: "https://github.com/vapor/fluent-sqlite-driver.git", from: "4.7.1"),
19+
.package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.9.1"),
20+
.package(url: "https://github.com/vapor/fluent-mysql-driver.git", from: "4.5.0"),
2121
],
2222
targets: [
2323
.target(
@@ -50,6 +50,4 @@ var swiftSettings: [SwiftSetting] { [
5050
.enableUpcomingFeature("ForwardTrailingClosures"),
5151
.enableUpcomingFeature("ConciseMagicFile"),
5252
.enableUpcomingFeature("DisableOutwardActorInference"),
53-
.enableUpcomingFeature("StrictConcurrency"),
54-
.enableExperimentalFeature("StrictConcurrency=complete"),
5553
] }

[email protected]

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ let package = Package(
1313
.library(name: "QueuesFluentDriver", targets: ["QueuesFluentDriver"]),
1414
],
1515
dependencies: [
16-
.package(url: "https://github.com/vapor/vapor.git", from: "4.92.1"),
17-
.package(url: "https://github.com/vapor/fluent.git", from: "4.9.0"),
18-
.package(url: "https://github.com/vapor/fluent-kit.git", from: "1.45.1"),
19-
.package(url: "https://github.com/vapor/sql-kit.git", from: "3.29.2"),
16+
.package(url: "https://github.com/vapor/vapor.git", from: "4.100.0"),
17+
.package(url: "https://github.com/vapor/fluent.git", from: "4.10.0"),
18+
.package(url: "https://github.com/vapor/fluent-kit.git", from: "1.48.4"),
19+
.package(url: "https://github.com/vapor/sql-kit.git", from: "3.30.0"),
2020
.package(url: "https://github.com/vapor/queues.git", from: "1.13.0"),
21-
.package(url: "https://github.com/vapor/fluent-sqlite-driver.git", from: "4.6.0"),
22-
.package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.9.0"),
23-
.package(url: "https://github.com/vapor/fluent-mysql-driver.git", from: "4.0.0"),
21+
.package(url: "https://github.com/vapor/fluent-sqlite-driver.git", from: "4.7.1"),
22+
.package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.9.1"),
23+
.package(url: "https://github.com/vapor/fluent-mysql-driver.git", from: "4.5.0"),
2424
],
2525
targets: [
2626
.target(
@@ -54,6 +54,5 @@ var swiftSettings: [SwiftSetting] { [
5454
.enableUpcomingFeature("ExistentialAny"),
5555
.enableUpcomingFeature("ConciseMagicFile"),
5656
.enableUpcomingFeature("DisableOutwardActorInference"),
57-
.enableUpcomingFeature("StrictConcurrency"),
5857
.enableExperimentalFeature("StrictConcurrency=complete"),
5958
] }

README.md

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,18 @@ A driver for [Queues]. Uses [Fluent] to store job metadata in an SQL database.
99

1010
This package makes use of the `SKIP LOCKED` feature supported by some of the major database engines (most notably [PostgresSQL][postgres-skip-locked] and [MySQL][mysql-skip-locked]) when available to make a best-effort guarantee that a task or job won't be picked by multiple workers.
1111

12-
This package should be compatible with:
12+
This package should be compatible with any SQL database supported by the various Fluent drivers. It is specifically known to work with:
1313

1414
- PostgreSQL 11.0+
15-
- MySQL 8.0+
15+
- MySQL 5.7+
1616
- MariaDB 10.5+
17+
- SQLite
1718

18-
> [!NOTE]
19+
> [!WARNING]
1920
> Although SQLite can be used with this package, SQLite has no support for advanced locking. It is not likely to function correctly with more than one or two queue workers.
2021
2122
[postgres-skip-locked]: https://www.postgresql.org/docs/current/sql-select.html#SQL-FOR-UPDATE-SHARE
22-
[mysql-skip-locked]: https://dev.mysql.com/doc/refman/8.3/en/select.html#:~:text=SKIP%20LOCKED%20causes%20a
23+
[mysql-skip-locked]: https://dev.mysql.com/doc/refman/8.4/en/select.html#:~:text=SKIP%20LOCKED%20causes%20a
2324

2425
## Getting started
2526

@@ -29,7 +30,7 @@ Add `QueuesFluentDriver` as dependency to your `Package.swift`:
2930

3031
```swift
3132
dependencies: [
32-
.package(url: "https://github.com/vapor-community/vapor-queues-fluent-driver.git", from: "3.0.0-beta.2"),
33+
.package(url: "https://github.com/vapor-community/vapor-queues-fluent-driver.git", from: "3.0.0-beta.4"),
3334
...
3435
]
3536
```
@@ -62,26 +63,25 @@ app.queues.use(.fluent())
6263
## Options
6364

6465
### Using a custom Database
65-
You can optionally create a dedicated Database, set to `isdefault: false` and with a custom `DatabaseID` and use it for your Queues.
66-
In that case you would initialize the Queues configuration like this:
6766

68-
```swift
69-
let queuesDb = DatabaseID(string: "my_queues_db")
70-
app.databases.use(.postgres(configuration: dbConfig), as: queuesDb, isDefault: false)
71-
app.queues.use(.fluent(queuesDb))
72-
```
67+
You can optionally create a dedicated non-default `Database` with a custom `DatabaseID` for use with your queues, as in the following example:
7368

74-
### Customizing the jobs table name
75-
You can customize the name of the table used by this driver during the migration :
7669
```swift
77-
app.migrations.add(JobMetadataMigrate(schema: "my_jobs"))
70+
extension DatabaseID {
71+
static var queues: Self { .init(string: "my_queues_db") }
72+
}
73+
74+
func configure(_ app: Application) async throws {
75+
app.databases.use(.postgres(configuration: ...), as: .queues, isDefault: false)
76+
app.queues.use(.fluent(.queues))
77+
}
7878
```
7979

8080
## Caveats
8181

8282
### Polling interval and number of workers
8383

84-
By default, the Vapor Queues system starts 2 workers per available CPU core, with each worker would polling the database once per second. On a 4-core system, this would results in 8 workers querying the database every second. Most configurations do not need this many workers.
84+
By default, the Vapor Queues system starts 2 workers per available CPU core, with each worker would polling the database once per second. On a 4-core system, this would results in 8 workers querying the database every second. Most configurations do not need this many workers. Additionally, when using SQLite as the underlying database it is generally inadvisable to run more than one worker at a time, as SQLite does not have the .
8585

8686
The polling interval can be changed using the `refreshInterval` configuration setting:
8787

Sources/QueuesFluentDriver/Documentation.docc/Documentation.md

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,22 @@ A driver for [Queues]. Uses [Fluent] to store job metadata in an SQL database.
99
[Queues]: https://github.com/vapor/queues
1010
[Fluent]: https://github.com/vapor/fluent
1111

12-
## Overview
12+
## Compatibility
13+
14+
This package makes use of the `SKIP LOCKED` feature supported by some of the major database engines (most notably [PostgresSQL][postgres-skip-locked] and [MySQL][mysql-skip-locked]) when available to make a best-effort guarantee that a task or job won't be picked by multiple workers.
15+
16+
This package should be compatible with any SQL database supported by the various Fluent drivers. It is specifically known to work with:
17+
18+
- PostgreSQL 11.0+
19+
- MySQL 5.7+
20+
- MariaDB 10.5+
21+
- SQLite
22+
23+
> [!WARNING]
24+
> Although SQLite can be used with this package, SQLite has no support for advanced locking. It is not likely to function correctly with more than one or two queue workers.
25+
26+
[postgres-skip-locked]: https://www.postgresql.org/docs/current/sql-select.html#SQL-FOR-UPDATE-SHARE
27+
[mysql-skip-locked]: https://dev.mysql.com/doc/refman/8.4/en/select.html#:~:text=SKIP%20LOCKED%20causes%20a
1328

1429
## Getting started
1530

@@ -19,7 +34,7 @@ Add `QueuesFluentDriver` as dependency to your `Package.swift`:
1934

2035
```swift
2136
dependencies: [
22-
.package(url: "https://github.com/vapor-community/vapor-queues-fluent-driver.git", from: "3.0.0-beta.2"),
37+
.package(url: "https://github.com/vapor-community/vapor-queues-fluent-driver.git", from: "3.0.0-beta.4"),
2338
...
2439
]
2540
```
@@ -52,11 +67,28 @@ app.queues.use(.fluent())
5267

5368
> Warning: Always call `app.databases.use(...)` **before** calling `app.queues.use(.fluent())`!
5469
70+
## Options
71+
72+
### Using a custom Database
73+
74+
You can optionally create a dedicated non-default `Database` with a custom `DatabaseID` for use with your queues, as in the following example:
75+
76+
```swift
77+
extension DatabaseID {
78+
static var queues: Self { .init(string: "my_queues_db") }
79+
}
80+
81+
func configure(_ app: Application) async throws {
82+
app.databases.use(.postgres(configuration: ...), as: .queues, isDefault: false)
83+
app.queues.use(.fluent(.queues))
84+
}
85+
```
86+
5587
## Caveats
5688

5789
### Polling interval and number of workers
5890

59-
By default, the Vapor Queues system starts 2 workers per available CPU core, with each worker would polling the database once per second. On a 4-core system, this would results in 8 workers querying the database every second. Most configurations do not need this many workers.
91+
By default, the Vapor Queues system starts 2 workers per available CPU core, with each worker would polling the database once per second. On a 4-core system, this would results in 8 workers querying the database every second. Most configurations do not need this many workers. Additionally, when using SQLite as the underlying database it is generally inadvisable to run more than one worker at a time, as SQLite does not have the necessary support for cross-connection locking.
6092

6193
The polling interval can be changed using the `refreshInterval` configuration setting:
6294

Sources/QueuesFluentDriver/FluentQueue.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
@preconcurrency import Queues
2-
@preconcurrency import SQLKit
2+
import SQLKit
33
import NIOConcurrencyHelpers
44
import struct Foundation.Data
55

Sources/QueuesFluentDriver/SQLKit+Convenience.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,11 @@ struct SQLDateValue<E: SQLExpression>: SQLExpression {
3636
}
3737
}
3838

39-
/// An alternative of `SQLLockingClause` which specifies the `SKIP LOCKED` modifier when the underlying database
39+
/// An alternative to `SQLLockingClause` which specifies the `SKIP LOCKED` modifier when the underlying database
4040
/// supports it. As MySQL's and PostgreSQL's manuals both note, this should not be used except in very specific
4141
/// scenarios, such as that of this package.
42+
///
43+
/// It is safe to use this expression with SQLite; its dialect correctly denies support for locking expressions.
4244
enum SQLLockingClauseWithSkipLocked: SQLExpression {
4345
/// Request an exclusive "writer" lock, skipping rows that are already locked.
4446
case updateSkippingLocked

Tests/QueuesFluentDriverTests/QueuesFluentDriverTests.swift

Lines changed: 36 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import NIOSSL
1111

1212
final class QueuesFluentDriverTests: XCTestCase {
1313
var dbid: DatabaseID { .sqlite }
14+
var app: Application!
1415

1516
private func useDbs(_ app: Application) throws {
1617
app.databases.use(.sqlite(.memory), as: .sqlite)
@@ -35,103 +36,88 @@ final class QueuesFluentDriverTests: XCTestCase {
3536
}
3637

3738
func testApplication() async throws {
38-
let app = Application(.testing)
39-
defer { app.shutdown() }
40-
41-
try self.useDbs(app)
42-
app.migrations.add(JobModelMigration(), to: self.dbid)
39+
self.app.migrations.add(JobModelMigration(), to: self.dbid)
4340

4441
let email = Email()
45-
app.queues.add(email)
42+
self.app.queues.add(email)
4643

47-
app.queues.use(.fluent(self.dbid))
44+
self.app.queues.use(.fluent(self.dbid))
4845

49-
try await app.autoMigrate()
46+
try await self.app.autoMigrate()
5047

51-
app.get("send-email") { req in
48+
self.app.get("send-email") { req in
5249
req.queue.dispatch(Email.self, .init(to: "[email protected]"))
5350
.map { HTTPStatus.ok }
5451
}
5552

56-
try app.testable().test(.GET, "send-email") { res in
53+
try await self.app.testable().test(.GET, "send-email") { res async in
5754
XCTAssertEqual(res.status, .ok)
5855
}
5956

6057
XCTAssertEqual(email.sent, [])
61-
try await app.queues.queue.worker.run().get()
58+
try await self.app.queues.queue.worker.run().get()
6259
XCTAssertEqual(email.sent, [.init(to: "[email protected]")])
6360

64-
try await app.autoRevert()
61+
try await self.app.autoRevert()
6562
}
6663

6764
func testFailedJobLoss() async throws {
68-
let app = Application(.testing)
69-
defer { app.shutdown() }
70-
71-
try self.useDbs(app)
72-
app.queues.add(FailingJob())
73-
app.queues.use(.fluent(self.dbid))
74-
app.migrations.add(JobModelMigration(), to: self.dbid)
75-
try await app.autoMigrate()
65+
self.app.queues.add(FailingJob())
66+
self.app.queues.use(.fluent(self.dbid))
67+
self.app.migrations.add(JobModelMigration(), to: self.dbid)
68+
try await self.app.autoMigrate()
7669

7770
let jobId = JobIdentifier()
78-
app.get("test") { req in
71+
self.app.get("test") { req in
7972
req.queue.dispatch(FailingJob.self, ["foo": "bar"], id: jobId)
8073
.map { HTTPStatus.ok }
8174
}
8275

83-
try app.testable().test(.GET, "test") { res in
76+
try await self.app.testable().test(.GET, "test") { res async in
8477
XCTAssertEqual(res.status, .ok)
8578
}
8679

87-
await XCTAssertThrowsErrorAsync(try await app.queues.queue.worker.run().get()) {
80+
await XCTAssertThrowsErrorAsync(try await self.app.queues.queue.worker.run().get()) {
8881
XCTAssert($0 is FailingJob.Failure)
8982
}
9083

91-
await XCTAssertNotNilAsync(try await (app.databases.database(self.dbid, logger: .init(label: ""), on: app.eventLoopGroup.any())! as! any SQLDatabase)
84+
await XCTAssertNotNilAsync(try await (self.app.databases.database(self.dbid, logger: .init(label: ""), on: self.app.eventLoopGroup.any())! as! any SQLDatabase)
9285
.select().columns("*").from(JobModel.schema).where("id", .equal, jobId.string).first())
9386

94-
try await app.autoRevert()
87+
try await self.app.autoRevert()
9588
}
9689

9790
func testDelayedJobIsRemovedFromProcessingQueue() async throws {
98-
let app = Application(.testing)
99-
defer { app.shutdown() }
100-
101-
try self.useDbs(app)
102-
103-
app.queues.add(DelayedJob())
91+
self.app.queues.add(DelayedJob())
10492

105-
app.queues.use(.fluent(self.dbid))
93+
self.app.queues.use(.fluent(self.dbid))
10694

107-
app.migrations.add(JobModelMigration(), to: self.dbid)
108-
try await app.autoMigrate()
95+
self.app.migrations.add(JobModelMigration(), to: self.dbid)
96+
try await self.app.autoMigrate()
10997

11098
let jobId = JobIdentifier()
111-
app.get("delay-job") { req in
99+
self.app.get("delay-job") { req in
112100
req.queue.dispatch(DelayedJob.self, .init(name: "vapor"),
113101
delayUntil: Date().addingTimeInterval(3600),
114102
id: jobId)
115103
.map { HTTPStatus.ok }
116104
}
117105

118-
try app.testable().test(.GET, "delay-job") { res in
106+
try await self.app.testable().test(.GET, "delay-job") { res async in
119107
XCTAssertEqual(res.status, .ok)
120108
}
121109

122-
await XCTAssertEqualAsync(try await (app.databases.database(self.dbid, logger: .init(label: ""), on: app.eventLoopGroup.any())! as! any SQLDatabase)
110+
await XCTAssertEqualAsync(try await (self.app.databases.database(self.dbid, logger: .init(label: ""), on: self.app.eventLoopGroup.any())! as! any SQLDatabase)
123111
.select().columns("*").from(JobModel.schema).where("id", .equal, jobId.string)
124112
.first(decoding: JobModel.self, keyDecodingStrategy: .convertFromSnakeCase)?.state, .pending)
125113

126-
try await app.autoRevert()
114+
try await self.app.autoRevert()
127115
}
128116

129117
func testCoverageForFailingQueue() {
130-
let app = Application(.testing)
131-
defer { app.shutdown() }
132118
let queue = FailingQueue(
133119
failure: QueuesFluentError.unsupportedDatabase,
134-
context: .init(queueName: .init(string: ""), configuration: .init(), application: app, logger: .init(label: ""), on: app.eventLoopGroup.any())
120+
context: .init(queueName: .init(string: ""), configuration: .init(), application: self.app, logger: .init(label: ""), on: self.app.eventLoopGroup.any())
135121
)
136122
XCTAssertThrowsError(try queue.get(.init()).wait())
137123
XCTAssertThrowsError(try queue.set(.init(), to: JobData(payload: [], maxRetryCount: 0, jobName: "", delayUntil: nil, queuedAt: .init())).wait())
@@ -140,8 +126,16 @@ final class QueuesFluentDriverTests: XCTestCase {
140126
XCTAssertThrowsError(try queue.pop().wait())
141127
}
142128

143-
override func setUp() {
129+
override func setUp() async throws {
144130
XCTAssert(isLoggingConfigured)
131+
132+
self.app = try await Application.make(.testing)
133+
try self.useDbs(self.app)
134+
}
135+
136+
override func tearDown() async throws {
137+
try await self.app.asyncShutdown()
138+
self.app = nil
145139
}
146140
}
147141

0 commit comments

Comments
 (0)