Skip to content

Commit 10a1fbc

Browse files
committed
Move AsyncXPCService to XPCExtensionService in XPCShared
1 parent 6821dcc commit 10a1fbc

File tree

3 files changed

+217
-119
lines changed

3 files changed

+217
-119
lines changed

Core/Sources/Client/XPCService.swift

Lines changed: 3 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -3,58 +3,12 @@ import Logger
33
import os.log
44
import XPCShared
55

6-
let shared = XPCService()
6+
let shared = XPCExtensionService(logger: .client)
77

8-
public func getService() throws -> AsyncXPCService {
8+
public func getService() throws -> XPCExtensionService {
99
if ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" {
1010
struct RunningInPreview: Error {}
1111
throw RunningInPreview()
1212
}
13-
return AsyncXPCService(service: shared)
14-
}
15-
16-
class XPCService {
17-
private var isInvalidated = false
18-
private lazy var _connection: NSXPCConnection = buildConnection()
19-
20-
var connection: NSXPCConnection {
21-
if isInvalidated {
22-
_connection.invalidationHandler = {}
23-
_connection.interruptionHandler = {}
24-
isInvalidated = false
25-
_connection.invalidate()
26-
rebuildConnection()
27-
}
28-
return _connection
29-
}
30-
31-
private func buildConnection() -> NSXPCConnection {
32-
let connection = NSXPCConnection(
33-
machServiceName: Bundle(for: XPCService.self)
34-
.object(forInfoDictionaryKey: "BUNDLE_IDENTIFIER_BASE") as! String +
35-
".ExtensionService"
36-
)
37-
connection.remoteObjectInterface =
38-
NSXPCInterface(with: XPCServiceProtocol.self)
39-
connection.invalidationHandler = { [weak self] in
40-
Logger.client.info("XPCService Invalidated")
41-
self?.isInvalidated = true
42-
}
43-
connection.interruptionHandler = { [weak self] in
44-
Logger.client.info("XPCService interrupted")
45-
self?.isInvalidated = true
46-
}
47-
connection.resume()
48-
return connection
49-
}
50-
51-
func rebuildConnection() {
52-
_connection = buildConnection()
53-
}
54-
55-
deinit {
56-
_connection.invalidationHandler = {}
57-
_connection.interruptionHandler = {}
58-
_connection.invalidate()
59-
}
13+
return shared
6014
}

Core/Sources/Client/AsyncXPCService.swift renamed to Tool/Sources/XPCShared/XPCExtensionService.swift

Lines changed: 94 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,38 @@
11
import Foundation
2-
import GitHubCopilotService
32
import Logger
4-
import SuggestionModel
5-
import XPCShared
63

7-
public struct AsyncXPCService {
8-
public var connection: NSXPCConnection { service.connection }
9-
let service: XPCService
4+
public enum XPCExtensionServiceError: Swift.Error, LocalizedError {
5+
case failedToGetServiceEndpoint
6+
case failedToCreateXPCConnection
7+
case xpcServiceError(Error)
108

11-
init(service: XPCService) {
12-
self.service = service
9+
public var errorDescription: String? {
10+
switch self {
11+
case .failedToGetServiceEndpoint:
12+
return "Failed to get service endpoint"
13+
case .failedToCreateXPCConnection:
14+
return "Failed to create XPC connection"
15+
case let .xpcServiceError(error):
16+
return "XPC Service error: \(error.localizedDescription)"
17+
}
18+
}
19+
}
20+
21+
@XPCServiceActor
22+
public class XPCExtensionService {
23+
var service: XPCService?
24+
var connection: NSXPCConnection? { service?.connection }
25+
let logger: Logger
26+
let bridge: XPCCommunicationBridge
27+
28+
nonisolated
29+
public init(logger: Logger) {
30+
self.logger = logger
31+
bridge = XPCCommunicationBridge(logger: logger)
1332
}
1433

1534
public func getXPCServiceVersion() async throws -> (version: String, build: String) {
16-
try await withXPCServiceConnected(connection: connection) {
35+
try await withXPCServiceConnected {
1736
service, continuation in
1837
service.getXPCServiceVersion { version, build in
1938
continuation.resume((version, build))
@@ -22,7 +41,7 @@ public struct AsyncXPCService {
2241
}
2342

2443
public func getXPCServiceAccessibilityPermission() async throws -> Bool {
25-
try await withXPCServiceConnected(connection: connection) {
44+
try await withXPCServiceConnected {
2645
service, continuation in
2746
service.getXPCServiceAccessibilityPermission { isGranted in
2847
continuation.resume(isGranted)
@@ -32,15 +51,13 @@ public struct AsyncXPCService {
3251

3352
public func getSuggestedCode(editorContent: EditorContent) async throws -> UpdatedContent? {
3453
try await suggestionRequest(
35-
connection,
3654
editorContent,
3755
{ $0.getSuggestedCode }
3856
)
3957
}
4058

4159
public func getNextSuggestedCode(editorContent: EditorContent) async throws -> UpdatedContent? {
4260
try await suggestionRequest(
43-
connection,
4461
editorContent,
4562
{ $0.getNextSuggestedCode }
4663
)
@@ -50,7 +67,6 @@ public struct AsyncXPCService {
5067
-> UpdatedContent?
5168
{
5269
try await suggestionRequest(
53-
connection,
5470
editorContent,
5571
{ $0.getPreviousSuggestedCode }
5672
)
@@ -60,7 +76,6 @@ public struct AsyncXPCService {
6076
-> UpdatedContent?
6177
{
6278
try await suggestionRequest(
63-
connection,
6479
editorContent,
6580
{ $0.getSuggestionAcceptedCode }
6681
)
@@ -70,7 +85,6 @@ public struct AsyncXPCService {
7085
-> UpdatedContent?
7186
{
7287
try await suggestionRequest(
73-
connection,
7488
editorContent,
7589
{ $0.getSuggestionRejectedCode }
7690
)
@@ -80,7 +94,6 @@ public struct AsyncXPCService {
8094
-> UpdatedContent?
8195
{
8296
try await suggestionRequest(
83-
connection,
8497
editorContent,
8598
{ $0.getRealtimeSuggestedCode }
8699
)
@@ -90,14 +103,13 @@ public struct AsyncXPCService {
90103
-> UpdatedContent?
91104
{
92105
try await suggestionRequest(
93-
connection,
94106
editorContent,
95107
{ $0.getPromptToCodeAcceptedCode }
96108
)
97109
}
98110

99111
public func toggleRealtimeSuggestion() async throws {
100-
try await withXPCServiceConnected(connection: connection) {
112+
try await withXPCServiceConnected {
101113
service, continuation in
102114
service.toggleRealtimeSuggestion { error in
103115
if let error {
@@ -111,7 +123,7 @@ public struct AsyncXPCService {
111123

112124
public func prefetchRealtimeSuggestions(editorContent: EditorContent) async {
113125
guard let data = try? JSONEncoder().encode(editorContent) else { return }
114-
try? await withXPCServiceConnected(connection: connection) { service, continuation in
126+
try? await withXPCServiceConnected { service, continuation in
115127
service.prefetchRealtimeSuggestions(editorContent: data) {
116128
continuation.resume(())
117129
}
@@ -120,15 +132,13 @@ public struct AsyncXPCService {
120132

121133
public func chatWithSelection(editorContent: EditorContent) async throws -> UpdatedContent? {
122134
try await suggestionRequest(
123-
connection,
124135
editorContent,
125136
{ $0.chatWithSelection }
126137
)
127138
}
128139

129140
public func promptToCode(editorContent: EditorContent) async throws -> UpdatedContent? {
130141
try await suggestionRequest(
131-
connection,
132142
editorContent,
133143
{ $0.promptToCode }
134144
)
@@ -139,14 +149,13 @@ public struct AsyncXPCService {
139149
editorContent: EditorContent
140150
) async throws -> UpdatedContent? {
141151
try await suggestionRequest(
142-
connection,
143152
editorContent,
144153
{ service in { service.customCommand(id: id, editorContent: $0, withReply: $1) } }
145154
)
146155
}
147156

148157
public func postNotification(name: String) async throws {
149-
try await withXPCServiceConnected(connection: connection) {
158+
try await withXPCServiceConnected {
150159
service, continuation in
151160
service.postNotification(name: name) {
152161
continuation.resume(())
@@ -157,7 +166,7 @@ public struct AsyncXPCService {
157166
public func send<M: ExtensionServiceRequestType>(
158167
requestBody: M
159168
) async throws -> M.ResponseBody {
160-
try await withXPCServiceConnected(connection: connection) { service, continuation in
169+
try await withXPCServiceConnected { service, continuation in
161170
do {
162171
let requestBodyData = try JSONEncoder().encode(requestBody)
163172
service.send(endpoint: M.endpoint, requestBody: requestBodyData) { data, error in
@@ -186,61 +195,76 @@ public struct AsyncXPCService {
186195
}
187196
}
188197

189-
struct NoDataError: Error {}
198+
extension XPCExtensionService: XPCServiceDelegate {
199+
func connectionDidInterrupt() async {
200+
// do nothing
201+
}
190202

191-
struct AutoFinishContinuation<T> {
192-
var continuation: AsyncThrowingStream<T, Error>.Continuation
203+
func connectionDidInvalidate() async {
204+
service = nil
205+
}
206+
}
193207

194-
func resume(_ value: T) {
195-
continuation.yield(value)
196-
continuation.finish()
208+
extension XPCExtensionService {
209+
private func updateEndpoint(_ endpoint: NSXPCListenerEndpoint) {
210+
service = XPCService(
211+
kind: .anonymous(endpoint: endpoint),
212+
interface: NSXPCInterface(with: XPCServiceProtocol.self),
213+
logger: logger
214+
)
215+
service?.delegate = self
197216
}
198217

199-
func reject(_ error: Error) {
200-
if (error as NSError).code == -100 {
201-
continuation.finish(throwing: CancellationError())
218+
private func withXPCServiceConnected<T>(
219+
_ fn: @escaping (XPCServiceProtocol, AutoFinishContinuation<T>) -> Void
220+
) async throws -> T {
221+
if let service, let connection = service.connection {
222+
do {
223+
return try await XPCShared.withXPCServiceConnected(connection: connection, fn)
224+
} catch {
225+
throw XPCExtensionServiceError.xpcServiceError(error)
226+
}
202227
} else {
203-
continuation.finish(throwing: error)
228+
guard let endpoint = try await bridge.launchExtensionServiceIfNeeded()
229+
else { throw XPCExtensionServiceError.failedToGetServiceEndpoint }
230+
updateEndpoint(endpoint)
231+
232+
if let service, let connection = service.connection {
233+
do {
234+
return try await XPCShared.withXPCServiceConnected(connection: connection, fn)
235+
} catch {
236+
throw XPCExtensionServiceError.xpcServiceError(error)
237+
}
238+
} else {
239+
throw XPCExtensionServiceError.failedToCreateXPCConnection
240+
}
204241
}
205242
}
206-
}
207243

208-
func withXPCServiceConnected<T>(
209-
connection: NSXPCConnection,
210-
_ fn: @escaping (XPCServiceProtocol, AutoFinishContinuation<T>) -> Void
211-
) async throws -> T {
212-
let stream: AsyncThrowingStream<T, Error> = AsyncThrowingStream { continuation in
213-
let service = connection.remoteObjectProxyWithErrorHandler {
214-
continuation.finish(throwing: $0)
215-
} as! XPCServiceProtocol
216-
fn(service, .init(continuation: continuation))
217-
}
218-
return try await stream.first(where: { _ in true })!
219-
}
220-
221-
func suggestionRequest(
222-
_ connection: NSXPCConnection,
223-
_ editorContent: EditorContent,
224-
_ fn: @escaping (any XPCServiceProtocol) -> (Data, @escaping (Data?, Error?) -> Void) -> Void
225-
) async throws -> UpdatedContent? {
226-
let data = try JSONEncoder().encode(editorContent)
227-
return try await withXPCServiceConnected(connection: connection) {
228-
service, continuation in
229-
fn(service)(data) { updatedData, error in
230-
if let error {
231-
continuation.reject(error)
232-
return
233-
}
234-
do {
235-
if let updatedData {
236-
let updatedContent = try JSONDecoder()
237-
.decode(UpdatedContent.self, from: updatedData)
238-
continuation.resume(updatedContent)
239-
} else {
240-
continuation.resume(nil)
244+
private func suggestionRequest(
245+
_ editorContent: EditorContent,
246+
_ fn: @escaping (any XPCServiceProtocol) -> (Data, @escaping (Data?, Error?) -> Void)
247+
-> Void
248+
) async throws -> UpdatedContent? {
249+
let data = try JSONEncoder().encode(editorContent)
250+
return try await withXPCServiceConnected {
251+
service, continuation in
252+
fn(service)(data) { updatedData, error in
253+
if let error {
254+
continuation.reject(error)
255+
return
256+
}
257+
do {
258+
if let updatedData {
259+
let updatedContent = try JSONDecoder()
260+
.decode(UpdatedContent.self, from: updatedData)
261+
continuation.resume(updatedContent)
262+
} else {
263+
continuation.resume(nil)
264+
}
265+
} catch {
266+
continuation.reject(error)
241267
}
242-
} catch {
243-
continuation.reject(error)
244268
}
245269
}
246270
}

0 commit comments

Comments
 (0)