Skip to content

Commit 7d18d3b

Browse files
authored
Merge pull request swiftlang#254 from benlangmuir/wait-io
[JSONRPC] Do not exit until outstanding I/O has finished
2 parents b00cff1 + fae2fe8 commit 7d18d3b

File tree

3 files changed

+36
-4
lines changed

3 files changed

+36
-4
lines changed

Sources/LanguageServerProtocolJSONRPC/JSONRPCConnection.swift

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,16 +64,28 @@ public final class JSONRPCConnection {
6464
self.messageRegistry = messageRegistry
6565
self.syncRequests = syncRequests
6666

67+
let ioGroup = DispatchGroup()
68+
69+
ioGroup.enter()
6770
receiveIO = DispatchIO(type: .stream, fileDescriptor: inFD, queue: queue) { (error: Int32) in
6871
if error != 0 {
6972
log("IO error \(error)", level: .error)
7073
}
74+
ioGroup.leave()
7175
}
7276

77+
ioGroup.enter()
7378
sendIO = DispatchIO(type: .stream, fileDescriptor: outFD, queue: sendQueue) { (error: Int32) in
7479
if error != 0 {
7580
log("IO error \(error)", level: .error)
7681
}
82+
ioGroup.leave()
83+
}
84+
85+
ioGroup.notify(queue: queue) { [weak self] in
86+
guard let self = self else { return }
87+
self.closeHandler()
88+
self.receiveHandler = nil // break retain cycle
7789
}
7890

7991
// We cannot assume the client will send us bytes in packets of any particular size, so set the lower limit to 1.
@@ -99,7 +111,9 @@ public final class JSONRPCConnection {
99111

100112
receiveIO.read(offset: 0, length: Int.max, queue: queue) { done, data, errorCode in
101113
guard errorCode == 0 else {
102-
log("IO error \(errorCode)", level: .error)
114+
if errorCode != POSIXError.ECANCELED.rawValue {
115+
log("IO error reading \(errorCode)", level: .error)
116+
}
103117
if done { self._close() }
104118
return
105119
}
@@ -287,6 +301,9 @@ public final class JSONRPCConnection {
287301
}
288302

289303
/// Close the connection.
304+
///
305+
/// The user-provided close handler will be called *asynchronously* when all outstanding I/O
306+
/// operations have completed. No new I/O will be accepted after `close` returns.
290307
public func close() {
291308
queue.sync { _close() }
292309
}
@@ -298,10 +315,10 @@ public final class JSONRPCConnection {
298315
state = .closed
299316

300317
log("\(JSONRPCConnection.self): closing...")
318+
// Attempt to close the reader immediately; we do not need to accept remaining inputs.
301319
receiveIO.close(flags: .stop)
302-
sendIO.close(flags: .stop)
303-
receiveHandler = nil // break retain cycle
304-
closeHandler()
320+
// Close the writer after it finishes outstanding work.
321+
sendIO.close()
305322
}
306323
}
307324

Tests/LanguageServerProtocolJSONRPCTests/ConnectionTests.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,20 @@ class ConnectionTests: XCTestCase {
197197
waitForExpectations(timeout: 10)
198198
}
199199

200+
func testSendBeforeClose() {
201+
let client = connection.client
202+
let server = connection.server
203+
204+
let expectation = self.expectation(description: "received notification")
205+
client.handleNextNotification { (note: Notification<EchoNotification>) in
206+
expectation.fulfill()
207+
}
208+
209+
server.client.send(EchoNotification(string: "about to close!"))
210+
connection.serverConnection.close()
211+
212+
waitForExpectations(timeout: 10)
213+
}
200214

201215
/// We can explicitly close a connection, but the connection also
202216
/// automatically closes itself if the pipe is closed (or has an error).

Tests/LanguageServerProtocolJSONRPCTests/XCTestManifests.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ extension ConnectionTests {
3434
("testMessageBuffer", testMessageBuffer),
3535
("testRound", testRound),
3636
("testSendAfterClose", testSendAfterClose),
37+
("testSendBeforeClose", testSendBeforeClose),
3738
("testUnexpectedResponse", testUnexpectedResponse),
3839
("testUnknownNotification", testUnknownNotification),
3940
("testUnknownRequest", testUnknownRequest),

0 commit comments

Comments
 (0)