mirror of
https://github.com/apple/sourcekit-lsp.git
synced 2026-03-02 18:23:24 +01:00
When we switch `SourceKitServer`, `SwiftLanguageServer` etc. to be actors, we can’t rely on them to provide ordering guarantees anymore because Swift concurrency doesn’t provide any ordering guarantees. What we should thus do, is to handle all messages on a serial queue on the `Connection` level. This queue will be blocked from handling any new messages until a message has been sufficiently handled to avoid out-of-order handling of messages. For sourcekitd, this means that a request has been sent to sourcekitd and for clangd, this means that we have forwarded the request to clangd. Note that this serial queue is not the main thread, so we will continue accepting data over stdin, just the handling of those messages is blocked.
79 lines
2.3 KiB
Swift
79 lines
2.3 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
import Foundation
|
|
|
|
/// Abstraction layer so we can store a heterogeneous collection of tasks in an
|
|
/// array.
|
|
private protocol AnyTask: Sendable {
|
|
func waitForCompletion() async
|
|
}
|
|
|
|
extension Task: AnyTask where Failure == Never {
|
|
func waitForCompletion() async {
|
|
_ = await value
|
|
}
|
|
}
|
|
|
|
/// A serial queue that allows the execution of asyncronous blocks of code.
|
|
public final class AsyncQueue {
|
|
/// Lock guarding `lastTask`.
|
|
private let lastTaskLock = NSLock()
|
|
|
|
/// The last scheduled task if it hasn't finished yet.
|
|
///
|
|
/// Any newly scheduled tasks need to await this task to ensure that tasks are
|
|
/// executed syncronously.
|
|
///
|
|
/// `id` is a unique value to identify the task. This allows us to set `lastTask`
|
|
/// to `nil` if the queue runs empty.
|
|
private var lastTask: (task: AnyTask, id: UUID)?
|
|
|
|
public init() {
|
|
self.lastTaskLock.name = "AsyncQueue.lastTaskLock"
|
|
}
|
|
|
|
/// Schedule a new closure to be executed on the queue.
|
|
///
|
|
/// All previously added tasks are guaranteed to finished executing before
|
|
/// this closure gets executed.
|
|
@discardableResult
|
|
public func async<Success: Sendable>(
|
|
priority: TaskPriority? = nil,
|
|
@_inheritActorContext operation: @escaping @Sendable () async -> Success
|
|
) -> Task<Success, Never> {
|
|
let id = UUID()
|
|
|
|
return lastTaskLock.withLock {
|
|
let task = Task<Success, Never>(priority: priority) { [previousLastTask = lastTask] in
|
|
await previousLastTask?.task.waitForCompletion()
|
|
|
|
defer {
|
|
lastTaskLock.withLock {
|
|
// If we haven't queued a new task since enquing this one, we can clear
|
|
// last task.
|
|
if self.lastTask?.id == id {
|
|
self.lastTask = nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return await operation()
|
|
}
|
|
|
|
lastTask = (task, id)
|
|
|
|
return task
|
|
}
|
|
}
|
|
}
|