1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
|
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2020 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 Dispatch
import LSPLogging
import LanguageServerProtocol
/// A connection between two message handlers in the same process.
///
/// You must call `start(handler:)` before sending any messages, and must call `close()` when finished to avoid a memory leak.
///
/// ```
/// let client: MessageHandler = ...
/// let server: MessageHandler = ...
/// let conn = LocalConnection()
/// conn.start(handler: server)
/// conn.send(...) // handled by server
/// conn.close()
/// ```
public final class LocalConnection: Connection, Sendable {
private enum State {
case ready, started, closed
}
/// A name of the endpoint for this connection, used for logging, e.g. `clangd`.
private let name: String
/// The queue guarding `_nextRequestID`.
private let queue: DispatchQueue = DispatchQueue(label: "local-connection-queue")
/// - Important: Must only be accessed from `queue`
nonisolated(unsafe) private var _nextRequestID: Int = 0
/// - Important: Must only be accessed from `queue`
nonisolated(unsafe) private var state: State = .ready
/// - Important: Must only be accessed from `queue`
nonisolated(unsafe) private var handler: MessageHandler? = nil
public init(name: String) {
self.name = name
}
deinit {
queue.sync {
if state != .closed {
closeAssumingOnQueue()
}
}
}
public func start(handler: MessageHandler) {
queue.sync {
precondition(state == .ready)
state = .started
self.handler = handler
}
}
/// - Important: Must only be called from `queue`
private func closeAssumingOnQueue() {
dispatchPrecondition(condition: .onQueue(queue))
precondition(state != .closed)
handler = nil
state = .closed
}
public func close() {
queue.sync {
closeAssumingOnQueue()
}
}
func nextRequestID() -> RequestID {
return queue.sync {
_nextRequestID += 1
return .number(_nextRequestID)
}
}
public func send<Notification: NotificationType>(_ notification: Notification) {
logger.info(
"""
Sending notification to \(self.name, privacy: .public)
\(notification.forLogging)
"""
)
guard let handler = queue.sync(execute: { handler }) else {
return
}
handler.handle(notification)
}
public func send<Request: RequestType>(
_ request: Request,
reply: @Sendable @escaping (LSPResult<Request.Response>) -> Void
) -> RequestID {
let id = nextRequestID()
logger.info(
"""
Sending request to \(self.name, privacy: .public) (id: \(id, privacy: .public)):
\(request.forLogging)
"""
)
guard let handler = queue.sync(execute: { handler }) else {
logger.info(
"""
Replying to request \(id, privacy: .public) with .serverCancelled because no handler is specified in \(self.name, privacy: .public)
"""
)
reply(.failure(.serverCancelled))
return id
}
precondition(self.state == .started)
handler.handle(request, id: id) { result in
switch result {
case .success(let response):
logger.info(
"""
Received reply for request \(id, privacy: .public) from \(self.name, privacy: .public)
\(response.forLogging)
"""
)
case .failure(let error):
logger.error(
"""
Received error for request \(id, privacy: .public) from \(self.name, privacy: .public)
\(error.forLogging)
"""
)
}
reply(result)
}
return id
}
}
|