Skip to content

Commit 4d3b507

Browse files
authored
Merge pull request #1120 from ahoppen/ahoppen/fix-tsan-issue
2 parents 5471f7e + abd534b commit 4d3b507

File tree

4 files changed

+51
-41
lines changed

4 files changed

+51
-41
lines changed

Package.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ let package = Package(
155155
dependencies: [
156156
"LanguageServerProtocol",
157157
"LanguageServerProtocolJSONRPC",
158+
"SKSupport",
158159
]
159160
),
160161

Sources/LSPTestSupport/TestJSONRPCConnection.swift

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import LanguageServerProtocol
1414
import LanguageServerProtocolJSONRPC
15+
import SKSupport
1516
import XCTest
1617

1718
import class Foundation.Pipe
@@ -24,7 +25,7 @@ public final class TestJSONRPCConnection {
2425
public let server: TestServer
2526
public let serverConnection: JSONRPCConnection
2627

27-
public init() {
28+
public init(allowUnexpectedNotification: Bool = true) {
2829
clientConnection = JSONRPCConnection(
2930
name: "client",
3031
protocol: testMessageRegistry,
@@ -39,7 +40,7 @@ public final class TestJSONRPCConnection {
3940
outFD: serverToClient.fileHandleForWriting
4041
)
4142

42-
client = TestMessageHandler(server: clientConnection)
43+
client = TestMessageHandler(server: clientConnection, allowUnexpectedNotification: allowUnexpectedNotification)
4344
server = TestServer(client: serverConnection)
4445

4546
clientConnection.start(receiveHandler: client) {
@@ -66,8 +67,8 @@ public struct TestLocalConnection {
6667
public let server: TestServer
6768
public let serverConnection: LocalConnection = .init()
6869

69-
public init() {
70-
client = TestMessageHandler(server: serverConnection)
70+
public init(allowUnexpectedNotification: Bool = true) {
71+
client = TestMessageHandler(server: serverConnection, allowUnexpectedNotification: allowUnexpectedNotification)
7172
server = TestServer(client: clientConnection)
7273

7374
clientConnection.start(handler: client)
@@ -80,17 +81,20 @@ public struct TestLocalConnection {
8081
}
8182
}
8283

83-
public final class TestMessageHandler: MessageHandler {
84+
public actor TestMessageHandler: MessageHandler {
8485
/// The connection to the language client.
8586
public let server: Connection
8687

87-
public init(server: Connection) {
88-
self.server = server
89-
}
88+
private let messageHandlingQueue = AsyncQueue<Serial>()
9089

91-
var oneShotNotificationHandlers: [((Any) -> Void)] = []
90+
private var oneShotNotificationHandlers: [((Any) -> Void)] = []
9291

93-
public var allowUnexpectedNotification: Bool = true
92+
private let allowUnexpectedNotification: Bool
93+
94+
public init(server: Connection, allowUnexpectedNotification: Bool = true) {
95+
self.server = server
96+
self.allowUnexpectedNotification = allowUnexpectedNotification
97+
}
9498

9599
public func appendOneShotNotificationHandler<N: NotificationType>(_ handler: @escaping (N) -> Void) {
96100
oneShotNotificationHandlers.append({ anyNote in
@@ -101,7 +105,14 @@ public final class TestMessageHandler: MessageHandler {
101105
})
102106
}
103107

104-
public func handle(_ notification: some NotificationType, from clientID: ObjectIdentifier) {
108+
/// The LSP server sent a notification to the client. Handle it.
109+
public nonisolated func handle(_ notification: some NotificationType, from clientID: ObjectIdentifier) {
110+
messageHandlingQueue.async {
111+
await self.handleNotificationImpl(notification)
112+
}
113+
}
114+
115+
public func handleNotificationImpl(_ notification: some NotificationType) {
105116
guard !oneShotNotificationHandlers.isEmpty else {
106117
if allowUnexpectedNotification { return }
107118
fatalError("unexpected notification \(notification)")
@@ -110,25 +121,25 @@ public final class TestMessageHandler: MessageHandler {
110121
handler(notification)
111122
}
112123

113-
public func handle<R: RequestType>(
114-
_ params: R,
124+
/// The LSP server sent a request to the client. Handle it.
125+
public nonisolated func handle<Request: RequestType>(
126+
_ request: Request,
115127
id: RequestID,
116128
from clientID: ObjectIdentifier,
117-
reply: @escaping (LSPResult<R.Response>) -> Void
129+
reply: @escaping (LSPResult<Request.Response>) -> Void
118130
) {
119-
reply(.failure(.methodNotFound(R.method)))
131+
reply(.failure(.methodNotFound(Request.method)))
120132
}
121133
}
122134

123135
extension TestMessageHandler: Connection {
124-
125-
/// Send a notification to the language server.
126-
public func send(_ notification: some NotificationType) {
136+
/// Send a notification to the LSP server.
137+
public nonisolated func send(_ notification: some NotificationType) {
127138
server.send(notification)
128139
}
129140

130-
/// Send a request to the language server and (asynchronously) receive a reply.
131-
public func send<Request: RequestType>(
141+
/// Send a request to the LSP server and (asynchronously) receive a reply.
142+
public nonisolated func send<Request: RequestType>(
132143
_ request: Request,
133144
reply: @escaping (LSPResult<Request.Response>) -> Void
134145
) -> RequestID {

Tests/LanguageServerProtocolJSONRPCTests/ConnectionTests.swift

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,7 @@ class ConnectionTests: XCTestCase {
2424
var connection: TestJSONRPCConnection! = nil
2525

2626
override func setUp() {
27-
connection = TestJSONRPCConnection()
28-
connection.client.allowUnexpectedNotification = false
27+
connection = TestJSONRPCConnection(allowUnexpectedNotification: false)
2928
}
3029

3130
override func tearDown() {
@@ -52,12 +51,12 @@ class ConnectionTests: XCTestCase {
5251
waitForExpectations(timeout: defaultTimeout)
5352
}
5453

55-
func testMessageBuffer() throws {
54+
func testMessageBuffer() async throws {
5655
let client = connection.client
5756
let clientConnection = connection.clientConnection
5857
let expectation = self.expectation(description: "note received")
5958

60-
client.appendOneShotNotificationHandler { (note: EchoNotification) in
59+
await client.appendOneShotNotificationHandler { (note: EchoNotification) in
6160
XCTAssertEqual(note.string, "hello!")
6261
expectation.fulfill()
6362
}
@@ -76,11 +75,11 @@ class ConnectionTests: XCTestCase {
7675
_rawData: [note1Str.utf8.last!, note2Str.utf8.first!].withUnsafeBytes { DispatchData(bytes: $0) }
7776
)
7877

79-
waitForExpectations(timeout: defaultTimeout)
78+
try await fulfillmentOfOrThrow([expectation])
8079

8180
let expectation2 = self.expectation(description: "note received")
8281

83-
client.appendOneShotNotificationHandler { (note: EchoNotification) in
82+
await client.appendOneShotNotificationHandler { (note: EchoNotification) in
8483
XCTAssertEqual(note.string, "no way!")
8584
expectation2.fulfill()
8685
}
@@ -89,7 +88,7 @@ class ConnectionTests: XCTestCase {
8988
clientConnection.send(_rawData: [b].withUnsafeBytes { DispatchData(bytes: $0) })
9089
}
9190

92-
waitForExpectations(timeout: defaultTimeout)
91+
try await fulfillmentOfOrThrow([expectation2])
9392

9493
// Close the connection before accessing _requestBuffer, which ensures we don't race.
9594
connection.serverConnection.close()
@@ -118,18 +117,18 @@ class ConnectionTests: XCTestCase {
118117
waitForExpectations(timeout: defaultTimeout)
119118
}
120119

121-
func testEchoNote() {
120+
func testEchoNote() async throws {
122121
let client = connection.client
123122
let expectation = self.expectation(description: "note received")
124123

125-
client.appendOneShotNotificationHandler { (note: EchoNotification) in
124+
await client.appendOneShotNotificationHandler { (note: EchoNotification) in
126125
XCTAssertEqual(note.string, "hello!")
127126
expectation.fulfill()
128127
}
129128

130129
client.send(EchoNotification(string: "hello!"))
131130

132-
waitForExpectations(timeout: defaultTimeout)
131+
try await fulfillmentOfOrThrow([expectation])
133132
}
134133

135134
func testUnknownRequest() {
@@ -210,33 +209,33 @@ class ConnectionTests: XCTestCase {
210209
waitForExpectations(timeout: defaultTimeout)
211210
}
212211

213-
func testSendBeforeClose() {
212+
func testSendBeforeClose() async throws {
214213
let client = connection.client
215214
let server = connection.server
216215

217216
let expectation = self.expectation(description: "received notification")
218-
client.appendOneShotNotificationHandler { (note: EchoNotification) in
217+
await client.appendOneShotNotificationHandler { (note: EchoNotification) in
219218
expectation.fulfill()
220219
}
221220

222221
server.client.send(EchoNotification(string: "about to close!"))
223222
connection.serverConnection.close()
224223

225-
waitForExpectations(timeout: defaultTimeout)
224+
try await fulfillmentOfOrThrow([expectation])
226225
}
227226

228-
func testSendSynchronouslyBeforeClose() {
227+
func testSendSynchronouslyBeforeClose() async throws {
229228
let client = connection.client
230229

231230
let expectation = self.expectation(description: "received notification")
232-
client.appendOneShotNotificationHandler { (note: EchoNotification) in
231+
await client.appendOneShotNotificationHandler { (note: EchoNotification) in
233232
expectation.fulfill()
234233
}
235234
let notification = EchoNotification(string: "about to close!")
236235
connection.serverConnection._send(.notification(notification), async: false)
237236
connection.serverConnection.close()
238237

239-
waitForExpectations(timeout: defaultTimeout)
238+
try await fulfillmentOfOrThrow([expectation])
240239
}
241240

242241
/// We can explicitly close a connection, but the connection also

Tests/LanguageServerProtocolTests/ConnectionTests.swift

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ class ConnectionTests: XCTestCase {
1919
var connection: TestLocalConnection! = nil
2020

2121
override func setUp() {
22-
connection = TestLocalConnection()
23-
connection.client.allowUnexpectedNotification = false
22+
connection = TestLocalConnection(allowUnexpectedNotification: false)
2423
}
2524

2625
override func tearDown() {
@@ -61,17 +60,17 @@ class ConnectionTests: XCTestCase {
6160
waitForExpectations(timeout: defaultTimeout)
6261
}
6362

64-
func testEchoNote() {
63+
func testEchoNote() async throws {
6564
let client = connection.client
6665
let expectation = self.expectation(description: "note received")
6766

68-
client.appendOneShotNotificationHandler { (note: EchoNotification) in
67+
await client.appendOneShotNotificationHandler { (note: EchoNotification) in
6968
XCTAssertEqual(note.string, "hello!")
7069
expectation.fulfill()
7170
}
7271

7372
client.send(EchoNotification(string: "hello!"))
7473

75-
waitForExpectations(timeout: defaultTimeout)
74+
try await fulfillmentOfOrThrow([expectation])
7675
}
7776
}

0 commit comments

Comments
 (0)