Skip to content

Commit 5b1c2de

Browse files
authored
Vendor swift-async-algorithms (#157)
# Motivation We are about to tag a `2.0.0` and cannot depend on any non major package. # Modification This vendors in `swift-async-algorithms` so that we can use the `AsyncMergeSequence`. In the future, once `swift-async-algorithms` is `1.0.0` itself we can swap back. # Result No more non-major dependency.
1 parent e4592be commit 5b1c2de

10 files changed

+1442
-8
lines changed

.swiftformat

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
--swiftversion 5.7
44
--exclude .build
5+
--exclude Sources/_AsyncMergeSequence
56

67
# format options
78

NOTICE.txt

+9
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,12 @@ This product contains derivations of the Lock and LockedValueBox implementations
3434
* https://github.com/apple/swift-nio
3535

3636
---
37+
38+
This product uses swift-async-algorithms.
39+
40+
* LICENSE (Apache License 2.0):
41+
* https://www.apache.org/licenses/LICENSE-2.0
42+
* HOMEPAGE:
43+
* https://github.com/apple/swift-async-algorithms
44+
45+
---

Package.swift

+12-6
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ let package = Package(
3434
from: "1.0.0"
3535
),
3636
.package(
37-
url: "https://github.com/apple/swift-async-algorithms",
38-
from: "0.1.0"
37+
url: "https://github.com/apple/swift-collections.git",
38+
from: "1.0.0"
3939
),
4040
],
4141
targets: [
@@ -46,10 +46,7 @@ let package = Package(
4646
name: "Logging",
4747
package: "swift-log"
4848
),
49-
.product(
50-
name: "AsyncAlgorithms",
51-
package: "swift-async-algorithms"
52-
),
49+
.target(name: "_AsyncMergeSequence"),
5350
.target(name: "UnixSignals"),
5451
.target(name: "ConcurrencyHelpers"),
5552
]
@@ -69,6 +66,15 @@ let package = Package(
6966
.target(
7067
name: "ConcurrencyHelpers"
7168
),
69+
.target(
70+
name: "_AsyncMergeSequence",
71+
dependencies: [
72+
.product(
73+
name: "DequeModule",
74+
package: "swift-collections"
75+
),
76+
]
77+
),
7278
.testTarget(
7379
name: "ServiceLifecycleTests",
7480
dependencies: [

Sources/ServiceLifecycle/AsyncCancelOnGracefulShutdownSequence.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
//
1313
//===----------------------------------------------------------------------===//
1414

15-
import AsyncAlgorithms
15+
import _AsyncMergeSequence
1616

1717
extension AsyncSequence where Self: Sendable, Element: Sendable {
1818
/// Creates an asynchronous sequence that is cancelled once graceful shutdown has triggered.
+155
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftServiceLifecycle open source project
4+
//
5+
// Copyright (c) 2023 Apple Inc. and the SwiftServiceLifecycle project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftServiceLifecycle project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
//===----------------------------------------------------------------------===//
16+
//
17+
// This source file is part of the Swift Async Algorithms open source project
18+
//
19+
// Copyright (c) 2022 Apple Inc. and the Swift project authors
20+
// Licensed under Apache License v2.0 with Runtime Library Exception
21+
//
22+
// See https://swift.org/LICENSE.txt for license information
23+
//
24+
//===----------------------------------------------------------------------===//
25+
26+
#if canImport(Darwin)
27+
@_implementationOnly import Darwin
28+
#elseif canImport(Glibc)
29+
@_implementationOnly import Glibc
30+
#elseif canImport(WinSDK)
31+
@_implementationOnly import WinSDK
32+
#endif
33+
34+
internal struct Lock {
35+
#if canImport(Darwin)
36+
typealias Primitive = os_unfair_lock
37+
#elseif canImport(Glibc)
38+
typealias Primitive = pthread_mutex_t
39+
#elseif canImport(WinSDK)
40+
typealias Primitive = SRWLOCK
41+
#endif
42+
43+
typealias PlatformLock = UnsafeMutablePointer<Primitive>
44+
let platformLock: PlatformLock
45+
46+
private init(_ platformLock: PlatformLock) {
47+
self.platformLock = platformLock
48+
}
49+
50+
fileprivate static func initialize(_ platformLock: PlatformLock) {
51+
#if canImport(Darwin)
52+
platformLock.initialize(to: os_unfair_lock())
53+
#elseif canImport(Glibc)
54+
let result = pthread_mutex_init(platformLock, nil)
55+
precondition(result == 0, "pthread_mutex_init failed")
56+
#elseif canImport(WinSDK)
57+
InitializeSRWLock(platformLock)
58+
#endif
59+
}
60+
61+
fileprivate static func deinitialize(_ platformLock: PlatformLock) {
62+
#if canImport(Glibc)
63+
let result = pthread_mutex_destroy(platformLock)
64+
precondition(result == 0, "pthread_mutex_destroy failed")
65+
#endif
66+
platformLock.deinitialize(count: 1)
67+
}
68+
69+
fileprivate static func lock(_ platformLock: PlatformLock) {
70+
#if canImport(Darwin)
71+
os_unfair_lock_lock(platformLock)
72+
#elseif canImport(Glibc)
73+
pthread_mutex_lock(platformLock)
74+
#elseif canImport(WinSDK)
75+
AcquireSRWLockExclusive(platformLock)
76+
#endif
77+
}
78+
79+
fileprivate static func unlock(_ platformLock: PlatformLock) {
80+
#if canImport(Darwin)
81+
os_unfair_lock_unlock(platformLock)
82+
#elseif canImport(Glibc)
83+
let result = pthread_mutex_unlock(platformLock)
84+
precondition(result == 0, "pthread_mutex_unlock failed")
85+
#elseif canImport(WinSDK)
86+
ReleaseSRWLockExclusive(platformLock)
87+
#endif
88+
}
89+
90+
static func allocate() -> Lock {
91+
let platformLock = PlatformLock.allocate(capacity: 1)
92+
initialize(platformLock)
93+
return Lock(platformLock)
94+
}
95+
96+
func deinitialize() {
97+
Lock.deinitialize(platformLock)
98+
}
99+
100+
func lock() {
101+
Lock.lock(platformLock)
102+
}
103+
104+
func unlock() {
105+
Lock.unlock(platformLock)
106+
}
107+
108+
/// Acquire the lock for the duration of the given block.
109+
///
110+
/// This convenience method should be preferred to `lock` and `unlock` in
111+
/// most situations, as it ensures that the lock will be released regardless
112+
/// of how `body` exits.
113+
///
114+
/// - Parameter body: The block to execute while holding the lock.
115+
/// - Returns: The value returned by the block.
116+
func withLock<T>(_ body: () throws -> T) rethrows -> T {
117+
self.lock()
118+
defer {
119+
self.unlock()
120+
}
121+
return try body()
122+
}
123+
124+
// specialise Void return (for performance)
125+
func withLockVoid(_ body: () throws -> Void) rethrows -> Void {
126+
try self.withLock(body)
127+
}
128+
}
129+
130+
struct ManagedCriticalState<State> {
131+
private final class LockedBuffer: ManagedBuffer<State, Lock.Primitive> {
132+
deinit {
133+
withUnsafeMutablePointerToElements { Lock.deinitialize($0) }
134+
}
135+
}
136+
137+
private let buffer: ManagedBuffer<State, Lock.Primitive>
138+
139+
init(_ initial: State) {
140+
buffer = LockedBuffer.create(minimumCapacity: 1) { buffer in
141+
buffer.withUnsafeMutablePointerToElements { Lock.initialize($0) }
142+
return initial
143+
}
144+
}
145+
146+
func withCriticalRegion<R>(_ critical: (inout State) throws -> R) rethrows -> R {
147+
try buffer.withUnsafeMutablePointers { header, lock in
148+
Lock.lock(lock)
149+
defer { Lock.unlock(lock) }
150+
return try critical(&header.pointee)
151+
}
152+
}
153+
}
154+
155+
extension ManagedCriticalState: @unchecked Sendable where State: Sendable { }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftServiceLifecycle open source project
4+
//
5+
// Copyright (c) 2023 Apple Inc. and the SwiftServiceLifecycle project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftServiceLifecycle project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
//===----------------------------------------------------------------------===//
16+
//
17+
// This source file is part of the Swift Async Algorithms open source project
18+
//
19+
// Copyright (c) 2022 Apple Inc. and the Swift project authors
20+
// Licensed under Apache License v2.0 with Runtime Library Exception
21+
//
22+
// See https://swift.org/LICENSE.txt for license information
23+
//
24+
//===----------------------------------------------------------------------===//
25+
26+
@_implementationOnly import DequeModule
27+
28+
/// Creates an asynchronous sequence of elements from two underlying asynchronous sequences
29+
public func merge<Base1: AsyncSequence, Base2: AsyncSequence>(_ base1: Base1, _ base2: Base2) -> AsyncMerge2Sequence<Base1, Base2>
30+
where
31+
Base1.Element == Base2.Element,
32+
Base1: Sendable, Base2: Sendable,
33+
Base1.Element: Sendable
34+
{
35+
return AsyncMerge2Sequence(base1, base2)
36+
}
37+
38+
/// An ``Swift/AsyncSequence`` that takes two upstream ``Swift/AsyncSequence``s and combines their elements.
39+
public struct AsyncMerge2Sequence<
40+
Base1: AsyncSequence,
41+
Base2: AsyncSequence
42+
>: Sendable where
43+
Base1.Element == Base2.Element,
44+
Base1: Sendable, Base2: Sendable,
45+
Base1.Element: Sendable
46+
{
47+
public typealias Element = Base1.Element
48+
49+
private let base1: Base1
50+
private let base2: Base2
51+
52+
/// Initializes a new ``AsyncMerge2Sequence``.
53+
///
54+
/// - Parameters:
55+
/// - base1: The first upstream ``Swift/AsyncSequence``.
56+
/// - base2: The second upstream ``Swift/AsyncSequence``.
57+
public init(
58+
_ base1: Base1,
59+
_ base2: Base2
60+
) {
61+
self.base1 = base1
62+
self.base2 = base2
63+
}
64+
}
65+
66+
extension AsyncMerge2Sequence: AsyncSequence {
67+
public func makeAsyncIterator() -> AsyncIterator {
68+
let storage = MergeStorage<Base1, Base2, Base1>(
69+
base1: base1,
70+
base2: base2,
71+
base3: nil
72+
)
73+
return AsyncIterator(storage: storage)
74+
}
75+
}
76+
77+
extension AsyncMerge2Sequence {
78+
public struct AsyncIterator: AsyncIteratorProtocol {
79+
/// This class is needed to hook the deinit to observe once all references to the ``AsyncIterator`` are dropped.
80+
///
81+
/// If we get move-only types we should be able to drop this class and use the `deinit` of the ``AsyncIterator`` struct itself.
82+
final class InternalClass: Sendable {
83+
private let storage: MergeStorage<Base1, Base2, Base1>
84+
85+
fileprivate init(storage: MergeStorage<Base1, Base2, Base1>) {
86+
self.storage = storage
87+
}
88+
89+
deinit {
90+
self.storage.iteratorDeinitialized()
91+
}
92+
93+
func next() async rethrows -> Element? {
94+
try await storage.next()
95+
}
96+
}
97+
98+
let internalClass: InternalClass
99+
100+
fileprivate init(storage: MergeStorage<Base1, Base2, Base1>) {
101+
internalClass = InternalClass(storage: storage)
102+
}
103+
104+
public mutating func next() async rethrows -> Element? {
105+
try await internalClass.next()
106+
}
107+
}
108+
}

0 commit comments

Comments
 (0)