Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 9 additions & 28 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ on:
push: { branches: [ main ] }
env:
LOG_LEVEL: info
SWIFT_DETERMINISTIC_HASHING: 1
REDIS_HOSTNAME: redis
REDIS_PORT: 6379
REDIS_HOSTNAME_2: redis-2
Expand All @@ -17,47 +16,27 @@ jobs:
api-breakage:
if: ${{ !(github.event.pull_request.draft || false) }}
runs-on: ubuntu-latest
container: swift:jammy
container: swift
steps:
- name: Check out code
uses: actions/checkout@v4
uses: actions/checkout@v5
with: { 'fetch-depth': 0 }
- name: Run API breakage check
run: |
git config --global --add safe.directory "${GITHUB_WORKSPACE}"
swift package diagnose-api-breaking-changes origin/main

# gh-codeql:
# if: ${{ !(github.event.pull_request.draft || false) }}
# runs-on: ubuntu-latest
# permissions: { actions: write, contents: read, security-events: write }
# timeout-minutes: 30
# steps:
# - name: Install latest Swift toolchain
# uses: vapor/swiftly-action@v0.1
# with: { toolchain: latest }
# - name: Check out code
# uses: actions/checkout@v4
# - name: Fix Git configuration
# run: 'git config --global --add safe.directory "${GITHUB_WORKSPACE}"'
# - name: Initialize CodeQL
# uses: github/codeql-action/init@v3
# with: { languages: swift }
# - name: Perform build
# run: swift build
# - name: Run CodeQL analyze
# uses: github/codeql-action/analyze@v3

linux-unit:
if: ${{ !(github.event.pull_request.draft || false) }}
strategy:
fail-fast: false
matrix:
container:
- swift:5.10-jammy
- swift:6.0-jammy
- swift:6.1-jammy
- swiftlang/swift:nightly-main-jammy
- swift:6.0-noble
- swift:6.1-noble
- swiftlang/swift:nightly-6.2-noble
- swiftlang/swift:nightly-main-noble
redis:
- redis:6
- redis:7
Expand All @@ -69,8 +48,10 @@ jobs:
redis-2:
image: ${{ matrix.redis }}
steps:
- name: Install curl
run: apt-get update -yq && apt-get install -y curl
- name: Check out package
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Run unit tests with Thread Sanitizer and coverage
run: swift test --sanitize=thread --enable-code-coverage
- name: Upload coverage data
Expand Down
12 changes: 9 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,21 @@ let package = Package(
.product(name: "RediStack", package: "RediStack"),
.product(name: "Vapor", package: "vapor"),
],
swiftSettings: [.enableExperimentalFeature("StrictConcurrency=complete")]
swiftSettings: [
.enableExperimentalFeature("StrictConcurrency=complete"),
.enableUpcomingFeature("ExistentialAny"),
]
),
.testTarget(
name: "RedisTests",
dependencies: [
.target(name: "Redis"),
.product(name: "XCTVapor", package: "vapor"),
],
swiftSettings: [.enableExperimentalFeature("StrictConcurrency=complete")]
)
swiftSettings: [
.enableExperimentalFeature("StrictConcurrency=complete"),
.enableUpcomingFeature("ExistentialAny"),
]
),
]
)
8 changes: 4 additions & 4 deletions Sources/Redis/Application+Redis.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,19 @@ extension Application {
}

@usableFromInline
internal func pool(for eventLoop: EventLoop) -> RedisConnectionPool {
internal func pool(for eventLoop: any EventLoop) -> RedisConnectionPool {
self.application.redisStorage.pool(for: eventLoop, id: self.id)
}
}
}

// MARK: RedisClient
extension Application.Redis: RedisClient {
public var eventLoop: EventLoop {
public var eventLoop: any EventLoop {
self.application.eventLoopGroup.next()
}

public func logging(to logger: Logger) -> RedisClient {
public func logging(to logger: Logger) -> any RedisClient {
self.application.redis(self.id)
.pool(for: self.eventLoop)
.logging(to: logger)
Expand Down Expand Up @@ -87,7 +87,7 @@ extension Application.Redis {
/// See `RedisConnectionPool.leaseConnection(_:)` for more details.
@inlinable
public func withBorrowedConnection<Result>(
_ operation: @escaping (RedisClient) -> EventLoopFuture<Result>
_ operation: @escaping (any RedisClient) -> EventLoopFuture<Result>
) -> EventLoopFuture<Result> {
return self.application.redis(self.id)
.pool(for: self.eventLoop)
Expand Down
4 changes: 2 additions & 2 deletions Sources/Redis/Application.Redis+PubSub.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import Vapor

extension Application.Redis {
private struct PubSubKey: StorageKey, LockKey {
typealias Value = [RedisID: RedisClient & Sendable]
typealias Value = [RedisID: any RedisClient & Sendable]
}

var pubsubClient: RedisClient {
var pubsubClient: any RedisClient {
if let existing = self.application.storage[PubSubKey.self]?[self.id] {
return existing
} else {
Expand Down
10 changes: 5 additions & 5 deletions Sources/Redis/Redis+Cache.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,22 @@ extension PropertyListDecoder: RedisCacheDecoder { public typealias Input = Data

extension Application.Caches {
/// A cache configured for the default Redis ID and the default coders.
public var redis: Cache {
public var redis: any Cache {
self.redis(.default)
}

/// A cache configured for a given Redis ID and the default coders.
public func redis(_ id: RedisID, jsonEncoder: JSONEncoder = JSONEncoder(), jsonDecoder: JSONDecoder = JSONDecoder()) -> Cache {
public func redis(_ id: RedisID, jsonEncoder: JSONEncoder = JSONEncoder(), jsonDecoder: JSONDecoder = JSONDecoder()) -> any Cache {
self.redis(id, encoder: jsonEncoder, decoder: jsonDecoder)
}

/// A cache configured for a given Redis ID and using the provided encoder and decoder.
public func redis<E: RedisCacheEncoder, D: RedisCacheDecoder>(_ id: RedisID = .default, encoder: E, decoder: D) -> Cache {
public func redis<E: RedisCacheEncoder, D: RedisCacheDecoder>(_ id: RedisID = .default, encoder: E, decoder: D) -> any Cache {
RedisCache(encoder: FakeSendable(value: encoder), decoder: FakeSendable(value: decoder), client: self.application.redis(id))
}

/// A cache configured for a given Redis ID and using the provided encoder and decoder wrapped as FakeSendable.
func redis(_ id: RedisID = .default, encoder: FakeSendable<some RedisCacheEncoder>, decoder: FakeSendable<some RedisCacheDecoder>) -> Cache {
func redis(_ id: RedisID = .default, encoder: FakeSendable<some RedisCacheEncoder>, decoder: FakeSendable<some RedisCacheDecoder>) -> any Cache {
RedisCache(encoder: encoder, decoder: decoder, client: self.application.redis(id))
}
}
Expand Down Expand Up @@ -79,7 +79,7 @@ struct FakeSendable<T>: @unchecked Sendable { let value: T }
private struct RedisCache<CacheEncoder: RedisCacheEncoder, CacheDecoder: RedisCacheDecoder>: Cache, Sendable {
let encoder: FakeSendable<CacheEncoder>
let decoder: FakeSendable<CacheDecoder>
let client: RedisClient
let client: any RedisClient

func get<T: Decodable>(_ key: String, as type: T.Type) -> EventLoopFuture<T?> {
self.client.get(RedisKey(key), as: CacheDecoder.Input.self).optionalFlatMapThrowing { try self.decoder.value.decode(T.self, from: $0) }
Expand Down
2 changes: 1 addition & 1 deletion Sources/Redis/RedisStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ final class RedisStorage: Sendable {
Set(self.box.withLockedValue { $0.configurations.keys })
}

func pool(for eventLoop: EventLoop, id redisID: RedisID) -> RedisConnectionPool {
func pool(for eventLoop: any EventLoop, id redisID: RedisID) -> RedisConnectionPool {
let key = PoolKey(eventLoopKey: eventLoop.key, redisID: redisID)
guard let pool = self.box.withLockedValue({ $0.pools[key] }) else {
fatalError("No redis found for id \(redisID), or the app may not have finished booting. Also, the eventLoop must be from Application's EventLoopGroup.")
Expand Down
6 changes: 3 additions & 3 deletions Sources/Redis/Request+Redis.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ extension Request {

// MARK: RedisClient
extension Request.Redis: RedisClient {
public var eventLoop: EventLoop {
public var eventLoop: any EventLoop {
self.request.eventLoop
}

public func logging(to logger: Logger) -> RedisClient {
public func logging(to logger: Logger) -> any RedisClient {
self.request.application.redis(self.id)
.pool(for: self.eventLoop)
.logging(to: logger)
Expand Down Expand Up @@ -82,7 +82,7 @@ extension Request.Redis {
/// See `RedisConnectionPool.leaseConnection(_:)` for more details.
@inlinable
public func withBorrowedClient<Result>(
_ operation: @escaping (RedisClient) -> EventLoopFuture<Result>
_ operation: @escaping (any RedisClient) -> EventLoopFuture<Result>
) -> EventLoopFuture<Result> {
return self.request.application.redis(self.id)
.pool(for: self.eventLoop)
Expand Down