Files
UUVPN/iOS-SwiftUI-Code/GRDB.swift-6.29.3/GRDB/Utils/Pool.swift
T

189 lines
6.7 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Dispatch
/// A Pool maintains a set of elements that are built them on demand. A pool has
/// a maximum number of elements.
///
/// // A pool of 3 integers
/// var number = 0
/// let pool = Pool<Int>(maximumCount: 3, makeElement: {
/// number = number + 1
/// return number
/// })
///
/// The function get() dequeues an available element and gives this element to
/// the block argument. During the block execution, the element is not
/// available. When the block is ended, the element is available again.
///
/// // got 1
/// pool.get { n in
/// print("got \(n)")
/// }
///
/// If there is no available element, the pool builds a new element, unless the
/// maximum number of elements is reached. In this case, the get() method
/// blocks the current thread, until an element eventually turns available again.
///
/// DispatchQueue.concurrentPerform(iterations: 6) { _ in
/// pool.get { n in
/// print("got \(n)")
/// }
/// }
///
/// got 1
/// got 2
/// got 3
/// got 2
/// got 1
/// got 3
final class Pool<T> {
private class Item {
let element: T
var isAvailable: Bool
init(element: T, isAvailable: Bool) {
self.element = element
self.isAvailable = isAvailable
}
}
private let makeElement: () throws -> T
@ReadWriteBox private var items: [Item] = []
private let itemsSemaphore: DispatchSemaphore // limits the number of elements
private let itemsGroup: DispatchGroup // knows when no element is used
private let barrierQueue: DispatchQueue
private let semaphoreWaitingQueue: DispatchQueue // Inspired by https://khanlou.com/2016/04/the-GCD-handbook/
/// Creates a Pool.
///
/// - parameters:
/// - maximumCount: The maximum number of elements.
/// - qos: The quality of service of asynchronous accesses.
/// - makeElement: A function that creates an element. It is called
/// on demand.
init(
maximumCount: Int,
qos: DispatchQoS = .unspecified,
makeElement: @escaping () throws -> T)
{
GRDBPrecondition(maximumCount > 0, "Pool size must be at least 1")
self.makeElement = makeElement
self.itemsSemaphore = DispatchSemaphore(value: maximumCount)
self.itemsGroup = DispatchGroup()
self.barrierQueue = DispatchQueue(label: "GRDB.Pool.barrier", qos: qos, attributes: [.concurrent])
self.semaphoreWaitingQueue = DispatchQueue(label: "GRDB.Pool.wait", qos: qos)
}
/// Returns a tuple (element, release)
/// Client must call release(), only once, after the element has been used.
func get() throws -> (element: T, release: (PoolCompletion) -> Void) {
try barrierQueue.sync {
itemsSemaphore.wait()
itemsGroup.enter()
do {
let item = try $items.update { items -> Item in
if let item = items.first(where: \.isAvailable) {
item.isAvailable = false
return item
} else {
let element = try makeElement()
let item = Item(element: element, isAvailable: false)
items.append(item)
return item
}
}
return (element: item.element, release: { self.release(item, completion: $0) })
} catch {
itemsSemaphore.signal()
itemsGroup.leave()
throw error
}
}
}
/// Eventually produces a tuple (element, release), where element is
/// intended to be used asynchronously.
///
/// Client must call release(), only once, after the element has been used.
///
/// - important: The `execute` argument is executed in a serial dispatch
/// queue, so make sure you use the element asynchronously.
func asyncGet(_ execute: @escaping (Result<(element: T, release: (PoolCompletion) -> Void), Error>) -> Void) {
// Inspired by https://khanlou.com/2016/04/the-GCD-handbook/
// > We wait on the semaphore in the serial queue, which means that
// > well have at most one blocked thread when we reach maximum
// > executing blocks on the concurrent queue. Any other tasks the user
// > enqueues will sit inertly on the serial queue waiting to be
// > executed, and wont cause new threads to be started.
semaphoreWaitingQueue.async {
execute(Result { try self.get() })
}
}
/// Performs a synchronous block with an element. The element turns
/// available after the block has executed.
func get<U>(block: (T) throws -> U) throws -> U {
let (element, completion) = try get()
defer { completion(.reuse) }
return try block(element)
}
private func release(_ item: Item, completion: PoolCompletion) {
$items.update { items in
switch completion {
case .reuse:
// This is why Item is a class, not a struct: so that we can
// release it without having to find in it the items array.
item.isAvailable = true
case .discard:
// Discard should be rare: perform lookup.
if let index = items.firstIndex(where: { $0 === item }) {
items.remove(at: index)
}
}
}
itemsSemaphore.signal()
itemsGroup.leave()
}
/// Performs a block on each pool element, available or not.
/// The block is run is some arbitrary dispatch queue.
func forEach(_ body: (T) throws -> Void) rethrows {
try $items.read { items in
for item in items {
try body(item.element)
}
}
}
/// Removes all elements from the pool.
/// Currently used elements won't be reused.
func removeAll() {
items = []
}
/// Blocks until no element is used, and runs the `barrier` function before
/// any other element is dequeued.
func barrier<R>(execute barrier: () throws -> R) rethrows -> R {
try barrierQueue.sync(flags: [.barrier]) {
itemsGroup.wait()
return try barrier()
}
}
/// Asynchronously runs the `barrier` function when no element is used, and
/// before any other element is dequeued.
func asyncBarrier(execute barrier: @escaping () -> Void) {
barrierQueue.async(flags: [.barrier]) {
self.itemsGroup.wait()
barrier()
}
}
}
enum PoolCompletion {
// Reuse the element
case reuse
// Discard the element
case discard
}