Skip to content

Commit

Permalink
Merge pull request #169 from norio-nomura/nn-handle-sourcekitd-error
Browse files Browse the repository at this point in the history
handle sourcekitd error
  • Loading branch information
jpsim committed Feb 9, 2016
2 parents b405bb0 + 37894d0 commit 561fed0
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 24 deletions.
8 changes: 6 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@

##### Breaking

* None.
* Change `SwiftDocs.init(file:arguments:)` to
`SwiftDocs.init?(file:arguments:)`
[Norio Nomura](https://github.com/norio-nomura)

##### Enhancements

* None.
* Add `Request.failableSend()` that can handle SourceKitService crashes.
`sourcekitten doc` does not stop when SourceKitService crashes.
[Norio Nomura](https://github.com/norio-nomura)

##### Bug Fixes

Expand Down
4 changes: 2 additions & 2 deletions Source/SourceKittenFramework/File.swift
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,8 @@ public final class File {
var dictionary = dictionary
let offsetMap = generateOffsetMap(documentedTokenOffsets, dictionary: dictionary)
for offset in offsetMap.keys.reverse() { // Do this in reverse to insert the doc at the correct offset
let response = processDictionary(Request.sendCursorInfoRequest(cursorInfoRequest, atOffset: Int64(offset))!, cursorInfoRequest: nil, syntaxMap: syntaxMap)
if let kind = SwiftDocKey.getKind(response),
if let response = Request.sendCursorInfoRequest(cursorInfoRequest, atOffset: Int64(offset)).map({ processDictionary($0, cursorInfoRequest: nil, syntaxMap: syntaxMap) }),
kind = SwiftDocKey.getKind(response),
_ = SwiftDeclarationKind(rawValue: kind),
parentOffset = offsetMap[offset].flatMap({ Int64($0) }),
inserted = insertDoc(response, parent: dictionary, offset: parentOffset) {
Expand Down
72 changes: 71 additions & 1 deletion Source/SourceKittenFramework/Request.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ private func fromSourceKit(sourcekitObject: sourcekitd_variant_t) -> SourceKitRe
/// dispatch_once_t token used to only initialize SourceKit once per session.
private var sourceKitInitializationToken: dispatch_once_t = 0

/// dispatch_semaphore_t used when waiting for sourcekitd to be restored.
private var sourceKitWaitingRestoredSemaphore = dispatch_semaphore_create(0)
private let sourceKitWaitingRestoredTimeout = Int64(10 * NSEC_PER_SEC)

/// SourceKit UID to String map.
private var uidStringMap = [sourcekitd_uid_t: String]()

Expand Down Expand Up @@ -241,7 +245,7 @@ public enum Request {
return nil
}
sourcekitd_request_dictionary_set_int64(request, sourcekitd_uid_get_from_cstr(SwiftDocKey.Offset.rawValue), offset)
return Request.CustomRequest(request).send()
return try? Request.CustomRequest(request).failableSend()
}

/**
Expand All @@ -257,6 +261,72 @@ public enum Request {
defer { sourcekitd_response_dispose(response) }
return fromSourceKit(sourcekitd_response_get_value(response)) as! [String: SourceKitRepresentable]
}

/// A enum representation of SOURCEKITD_ERROR_*
public enum Error: ErrorType, CustomStringConvertible {
case ConnectionInterrupted(String?)
case Invalid(String?)
case Failed(String?)
case Cancelled(String?)
case Unknown(String?)

/// A textual representation of `self`.
public var description: String {
return getDescription() ?? "no description"
}

private func getDescription() -> String? {
switch self {
case .ConnectionInterrupted(let string): return string
case .Invalid(let string): return string
case .Failed(let string): return string
case .Cancelled(let string): return string
case .Unknown(let string): return string
}
}

private init(response: sourcekitd_response_t) {
let description = String(UTF8String: sourcekitd_response_error_get_description(response))
switch sourcekitd_response_error_get_kind(response) {
case SOURCEKITD_ERROR_CONNECTION_INTERRUPTED: self = .ConnectionInterrupted(description)
case SOURCEKITD_ERROR_REQUEST_INVALID: self = .Invalid(description)
case SOURCEKITD_ERROR_REQUEST_FAILED: self = .Failed(description)
case SOURCEKITD_ERROR_REQUEST_CANCELLED: self = .Cancelled(description)
default: self = .Unknown(description)
}
}
}

/**
Sends the request to SourceKit and return the response as an [String: SourceKitRepresentable].
- returns: SourceKit output as a dictionary.
- throws: Request.Error on fail ()
*/
public func failableSend() throws -> [String: SourceKitRepresentable] {
dispatch_once(&sourceKitInitializationToken) {
sourcekitd_initialize()
sourcekitd_set_notification_handler() { response in
if !sourcekitd_response_is_error(response) {
fflush(stdout)
fputs("sourcekitten: connection to SourceKitService restored!\n", stderr)
dispatch_semaphore_signal(sourceKitWaitingRestoredSemaphore)
}
sourcekitd_response_dispose(response)
}
}
let response = sourcekitd_send_request_sync(sourcekitObject)
defer { sourcekitd_response_dispose(response) }
if sourcekitd_response_is_error(response) {
let error = Request.Error(response: response)
if case .ConnectionInterrupted = error {
dispatch_semaphore_wait(sourceKitWaitingRestoredSemaphore,
dispatch_time(DISPATCH_TIME_NOW, sourceKitWaitingRestoredTimeout))
}
throw error
}
return fromSourceKit(sourcekitd_response_get_value(response)) as! [String: SourceKitRepresentable]
}
}

// MARK: CustomStringConvertible
Expand Down
19 changes: 13 additions & 6 deletions Source/SourceKittenFramework/SwiftDocs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,19 @@ public struct SwiftDocs {
- parameter file: Swift file to document.
- parameter arguments: compiler arguments to pass to SourceKit.
*/
public init(file: File, arguments: [String]) {
self.init(
file: file,
dictionary: Request.EditorOpen(file).send(),
cursorInfoRequest: Request.cursorInfoRequestForFilePath(file.path, arguments: arguments)
)
public init?(file: File, arguments: [String]) {
do {
self.init(
file: file,
dictionary: try Request.EditorOpen(file).failableSend(),
cursorInfoRequest: Request.cursorInfoRequestForFilePath(file.path, arguments: arguments)
)
} catch let error as Request.Error {
fputs(error.description, stderr)
return nil
} catch {
return nil
}
}

/**
Expand Down
2 changes: 1 addition & 1 deletion Source/SourceKittenFrameworkTests/SwiftDocsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func compareJSONStringWithFixturesName(name: String, jsonString: String) {

func compareDocsWithFixturesName(name: String) {
let swiftFilePath = fixturesDirectory + name + ".swift"
let docs = SwiftDocs(file: File(path: swiftFilePath)!, arguments: ["-j4", swiftFilePath])
let docs = SwiftDocs(file: File(path: swiftFilePath)!, arguments: ["-j4", swiftFilePath])!

let escapedFixturesDirectory = fixturesDirectory.stringByReplacingOccurrencesOfString("/", withString: "\\/")
let comparisonString = String(docs).stringByReplacingOccurrencesOfString(escapedFixturesDirectory, withString: "")
Expand Down
4 changes: 2 additions & 2 deletions Source/sourcekitten/DocCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ struct DocCommand: CommandType {
return .Failure(.InvalidArgument(description: "at least 5 arguments are required when using `--single-file`"))
}
let sourcekitdArguments = Array<String>(args[4..<args.count])
if let file = File(path: args[3]) {
let docs = SwiftDocs(file: file, arguments: sourcekitdArguments)
if let file = File(path: args[3]),
docs = SwiftDocs(file: file, arguments: sourcekitdArguments) {
print(docs)
return .Success()
}
Expand Down
27 changes: 17 additions & 10 deletions Source/sourcekitten/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,25 @@
//

import Darwin
import Foundation
import Commandant

let registry = CommandRegistry<SourceKittenError>()
registry.register(CompleteCommand())
registry.register(DocCommand())
registry.register(SyntaxCommand())
registry.register(StructureCommand())
registry.register(VersionCommand())
// `sourcekitd_set_notification_handler()` set the handler to be executed on main thread queue.
// So, we vacate main thread to `dispatch_main()`.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
let registry = CommandRegistry<SourceKittenError>()
registry.register(CompleteCommand())
registry.register(DocCommand())
registry.register(SyntaxCommand())
registry.register(StructureCommand())
registry.register(VersionCommand())

let helpCommand = HelpCommand(registry: registry)
registry.register(helpCommand)
let helpCommand = HelpCommand(registry: registry)
registry.register(helpCommand)

registry.main(defaultVerb: "help") { error in
fputs("\(error)\n", stderr)
registry.main(defaultVerb: "help") { error in
fputs("\(error)\n", stderr)
}
}

dispatch_main()

0 comments on commit 561fed0

Please sign in to comment.