Skip to content

Commit fff9eb5

Browse files
authored
Merge pull request #1382 from ahoppen/stream-index-log
Instead of sending a message to the index log when an indexing task finishes, stream results as they come in
2 parents b7fa9a9 + 09ad77b commit fff9eb5

22 files changed

+226
-174
lines changed

Sources/CAtomics/include/CAtomics.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ typedef struct {
7070
} AtomicUInt32;
7171

7272
__attribute__((swift_name("AtomicUInt32.init(initialValue:)")))
73-
static inline AtomicUInt32 atomic_int_create(uint8_t initialValue) {
73+
static inline AtomicUInt32 atomic_int_create(uint32_t initialValue) {
7474
AtomicUInt32 atomic;
7575
atomic.value = initialValue;
7676
return atomic;

Sources/SKCore/BuildServerBuildSystem.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ extension BuildServerBuildSystem: BuildSystem {
293293

294294
public func prepare(
295295
targets: [ConfiguredTarget],
296-
indexProcessDidProduceResult: @Sendable (IndexProcessResult) -> Void
296+
logMessageToIndexLog: @Sendable (_ taskID: IndexTaskID, _ message: String) -> Void
297297
) async throws {
298298
throw PrepareNotSupportedError()
299299
}

Sources/SKCore/BuildSystem.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ public protocol BuildSystem: AnyObject, Sendable {
169169
/// dependencies.
170170
func prepare(
171171
targets: [ConfiguredTarget],
172-
indexProcessDidProduceResult: @Sendable (IndexProcessResult) -> Void
172+
logMessageToIndexLog: @escaping @Sendable (_ taskID: IndexTaskID, _ message: String) -> Void
173173
) async throws
174174

175175
/// If the build system has knowledge about the language that this document should be compiled in, return it.

Sources/SKCore/BuildSystemManager.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,9 +237,9 @@ extension BuildSystemManager {
237237

238238
public func prepare(
239239
targets: [ConfiguredTarget],
240-
indexProcessDidProduceResult: @Sendable (IndexProcessResult) -> Void
240+
logMessageToIndexLog: @escaping @Sendable (_ taskID: IndexTaskID, _ message: String) -> Void
241241
) async throws {
242-
try await buildSystem?.prepare(targets: targets, indexProcessDidProduceResult: indexProcessDidProduceResult)
242+
try await buildSystem?.prepare(targets: targets, logMessageToIndexLog: logMessageToIndexLog)
243243
}
244244

245245
public func registerForChangeNotifications(for uri: DocumentURI, language: Language) async {

Sources/SKCore/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ add_library(SKCore STATIC
1111
ExperimentalFeatures.swift
1212
FallbackBuildSystem.swift
1313
FileBuildSettings.swift
14-
IndexProcessResult.swift
14+
IndexTaskID.swift
1515
MainFilesProvider.swift
1616
PathPrefixMapping.swift
1717
SplitShellCommand.swift

Sources/SKCore/CompilationDatabaseBuildSystem.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ extension CompilationDatabaseBuildSystem: BuildSystem {
125125

126126
public func prepare(
127127
targets: [ConfiguredTarget],
128-
indexProcessDidProduceResult: @Sendable (IndexProcessResult) -> Void
128+
logMessageToIndexLog: @Sendable (_ taskID: IndexTaskID, _ message: String) -> Void
129129
) async throws {
130130
throw PrepareNotSupportedError()
131131
}

Sources/SKCore/IndexProcessResult.swift

Lines changed: 0 additions & 71 deletions
This file was deleted.

Sources/SKCore/IndexTaskID.swift

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import struct TSCBasic.ProcessResult
14+
15+
/// The ID of a preparation or update indexstore task. This allows us to log messages from multiple concurrently running
16+
/// indexing tasks to the index log while still being able to differentiate them.
17+
public enum IndexTaskID: Sendable {
18+
case preparation(id: UInt32)
19+
case updateIndexStore(id: UInt32)
20+
21+
private static func numberToEmojis(_ number: Int, numEmojis: Int) -> String {
22+
let emojis = ["🟥", "🟩", "🟦", "🟧", "⬜️", "🟪", "⬛️", "🟨", "🟫"]
23+
var number = abs(number)
24+
var result = ""
25+
for _ in 0..<numEmojis {
26+
let (quotient, remainder) = number.quotientAndRemainder(dividingBy: emojis.count)
27+
result += emojis[remainder]
28+
number = quotient
29+
}
30+
return result
31+
}
32+
33+
/// Returns a two-character emoji string that allows easy differentiation between different task IDs.
34+
///
35+
/// This marker is prepended to every line in the index log.
36+
public var emojiRepresentation: String {
37+
// Multiply by 2 and optionally add 1 to make sure preparation and update index store have distinct IDs.
38+
// Run .hashValue to make sure we semi-randomly pick new emoji markers for new tasks
39+
switch self {
40+
case .preparation(id: let id):
41+
return Self.numberToEmojis((id * 2).hashValue, numEmojis: 2)
42+
case .updateIndexStore(id: let id):
43+
return Self.numberToEmojis((id * 2 + 1).hashValue, numEmojis: 2)
44+
}
45+
}
46+
}

Sources/SKSupport/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ add_library(SKSupport STATIC
1111
DocumentURI+CustomLogStringConvertible.swift
1212
FileSystem.swift
1313
LineTable.swift
14+
PipeAsStringHandler.swift
1415
Process+LaunchWithWorkingDirectoryIfPossible.swift
1516
Process+WaitUntilExitWithCancellation.swift
1617
Random.swift
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import Foundation
14+
15+
/// Gathers data from a stdout or stderr pipe. When it has accumulated a full line, calls the handler to handle the
16+
/// string.
17+
public actor PipeAsStringHandler {
18+
/// Queue on which all data from the pipe will be handled. This allows us to have a
19+
/// nonisolated `handle` function but ensure that data gets processed in order.
20+
private let queue = AsyncQueue<Serial>()
21+
private var buffer = Data()
22+
23+
/// The closure that actually handles
24+
private let handler: @Sendable (String) -> Void
25+
26+
public init(handler: @escaping @Sendable (String) -> Void) {
27+
self.handler = handler
28+
}
29+
30+
private func handleDataFromPipeImpl(_ newData: Data) {
31+
self.buffer += newData
32+
while let newlineIndex = self.buffer.firstIndex(of: UInt8(ascii: "\n")) {
33+
// Output a separate log message for every line in the pipe.
34+
// The reason why we don't output multiple lines in a single log message is that
35+
// a) os_log truncates log messages at about 1000 bytes. The assumption is that a single line is usually less
36+
// than 1000 bytes long but if we merge multiple lines into one message, we might easily exceed this limit.
37+
// b) It might be confusing why sometimes a single log message contains one line while sometimes it contains
38+
// multiple.
39+
handler(String(data: self.buffer[...newlineIndex], encoding: .utf8) ?? "<invalid UTF-8>")
40+
buffer = buffer[buffer.index(after: newlineIndex)...]
41+
}
42+
}
43+
44+
public nonisolated func handleDataFromPipe(_ newData: Data) {
45+
queue.async {
46+
await self.handleDataFromPipeImpl(newData)
47+
}
48+
}
49+
}

Sources/SKSupport/Process+LaunchWithWorkingDirectoryIfPossible.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ extension Process {
2626
arguments: [String],
2727
environmentBlock: ProcessEnvironmentBlock = ProcessEnv.block,
2828
workingDirectory: AbsolutePath?,
29+
outputRedirection: OutputRedirection = .collect,
2930
startNewProcessGroup: Bool = true,
3031
loggingHandler: LoggingHandler? = .none
3132
) throws -> Process {
@@ -35,13 +36,15 @@ extension Process {
3536
arguments: arguments,
3637
environmentBlock: environmentBlock,
3738
workingDirectory: workingDirectory,
39+
outputRedirection: outputRedirection,
3840
startNewProcessGroup: startNewProcessGroup,
3941
loggingHandler: loggingHandler
4042
)
4143
} else {
4244
Process(
4345
arguments: arguments,
4446
environmentBlock: environmentBlock,
47+
outputRedirection: outputRedirection,
4548
startNewProcessGroup: startNewProcessGroup,
4649
loggingHandler: loggingHandler
4750
)
@@ -57,6 +60,7 @@ extension Process {
5760
arguments: arguments,
5861
environmentBlock: environmentBlock,
5962
workingDirectory: nil,
63+
outputRedirection: outputRedirection,
6064
startNewProcessGroup: startNewProcessGroup,
6165
loggingHandler: loggingHandler
6266
)

Sources/SKSwiftPMWorkspace/SwiftPMBuildSystem.swift

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313
import Basics
1414
import Build
1515
import BuildServerProtocol
16+
import CAtomics
1617
import Dispatch
18+
import Foundation
1719
import LSPLogging
1820
import LanguageServerProtocol
1921
import PackageGraph
@@ -71,6 +73,9 @@ fileprivate extension ConfiguredTarget {
7173
static let forPackageManifest = ConfiguredTarget(targetID: "", runDestinationID: "")
7274
}
7375

76+
/// `nonisolated(unsafe)` is fine because `preparationTaskID` is atomic.
77+
fileprivate nonisolated(unsafe) var preparationTaskID: AtomicUInt32 = AtomicUInt32(initialValue: 0)
78+
7479
/// Swift Package Manager build system and workspace support.
7580
///
7681
/// This class implements the `BuildSystem` interface to provide the build settings for a Swift
@@ -516,20 +521,20 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem {
516521

517522
public func prepare(
518523
targets: [ConfiguredTarget],
519-
indexProcessDidProduceResult: @Sendable (IndexProcessResult) -> Void
524+
logMessageToIndexLog: @escaping @Sendable (_ taskID: IndexTaskID, _ message: String) -> Void
520525
) async throws {
521526
// TODO (indexing): Support preparation of multiple targets at once.
522527
// https://github.com/apple/sourcekit-lsp/issues/1262
523528
for target in targets {
524-
try await prepare(singleTarget: target, indexProcessDidProduceResult: indexProcessDidProduceResult)
529+
try await prepare(singleTarget: target, logMessageToIndexLog: logMessageToIndexLog)
525530
}
526531
let filesInPreparedTargets = targets.flatMap { self.targets[$0]?.buildTarget.sources ?? [] }
527532
await fileDependenciesUpdatedDebouncer.scheduleCall(Set(filesInPreparedTargets.map(DocumentURI.init)))
528533
}
529534

530535
private func prepare(
531536
singleTarget target: ConfiguredTarget,
532-
indexProcessDidProduceResult: @Sendable (IndexProcessResult) -> Void
537+
logMessageToIndexLog: @escaping @Sendable (_ taskID: IndexTaskID, _ message: String) -> Void
533538
) async throws {
534539
if target == .forPackageManifest {
535540
// Nothing to prepare for package manifests.
@@ -586,15 +591,28 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem {
586591
return
587592
}
588593
let start = ContinuousClock.now
589-
let process = try Process.launch(arguments: arguments, workingDirectory: nil)
590-
let result = try await process.waitUntilExitSendingSigIntOnTaskCancellation()
591-
indexProcessDidProduceResult(
592-
IndexProcessResult(
593-
taskDescription: "Preparing \(target.targetID) for \(target.runDestinationID)",
594-
processResult: result,
595-
start: start
594+
595+
let logID = IndexTaskID.preparation(id: preparationTaskID.fetchAndIncrement())
596+
logMessageToIndexLog(
597+
logID,
598+
"""
599+
Preparing \(target.targetID) for \(target.runDestinationID)
600+
\(arguments.joined(separator: " "))
601+
"""
602+
)
603+
let stdoutHandler = PipeAsStringHandler { logMessageToIndexLog(logID, $0) }
604+
let stderrHandler = PipeAsStringHandler { logMessageToIndexLog(logID, $0) }
605+
606+
let process = try Process.launch(
607+
arguments: arguments,
608+
workingDirectory: nil,
609+
outputRedirection: .stream(
610+
stdout: { stdoutHandler.handleDataFromPipe(Data($0)) },
611+
stderr: { stderrHandler.handleDataFromPipe(Data($0)) }
596612
)
597613
)
614+
let result = try await process.waitUntilExitSendingSigIntOnTaskCancellation()
615+
logMessageToIndexLog(logID, "Finished in \(start.duration(to: .now))")
598616
switch result.exitStatus.exhaustivelySwitchable {
599617
case .terminated(code: 0):
600618
break

Sources/SKTestSupport/TestSourceKitLSPClient.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,12 +258,12 @@ public final class TestSourceKitLSPClient: MessageHandler {
258258
/// Ignores any notifications that are of a different type or that don't satisfy the predicate.
259259
public func nextNotification<ExpectedNotificationType: NotificationType>(
260260
ofType: ExpectedNotificationType.Type,
261-
satisfying predicate: (ExpectedNotificationType) -> Bool = { _ in true },
261+
satisfying predicate: (ExpectedNotificationType) throws -> Bool = { _ in true },
262262
timeout: Duration = .seconds(defaultTimeout)
263263
) async throws -> ExpectedNotificationType {
264264
while true {
265265
let nextNotification = try await nextNotification(timeout: timeout)
266-
if let notification = nextNotification as? ExpectedNotificationType, predicate(notification) {
266+
if let notification = nextNotification as? ExpectedNotificationType, try predicate(notification) {
267267
return notification
268268
}
269269
}

Sources/SemanticIndex/PreparationTaskDescription.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ public struct PreparationTaskDescription: IndexTaskDescription {
3737

3838
private let preparationUpToDateTracker: UpToDateTracker<ConfiguredTarget>
3939

40-
/// See `SemanticIndexManager.indexProcessDidProduceResult`
41-
private let indexProcessDidProduceResult: @Sendable (IndexProcessResult) -> Void
40+
/// See `SemanticIndexManager.logMessageToIndexLog`.
41+
private let logMessageToIndexLog: @Sendable (_ taskID: IndexTaskID, _ message: String) -> Void
4242

4343
/// Test hooks that should be called when the preparation task finishes.
4444
private let testHooks: IndexTestHooks
@@ -60,13 +60,13 @@ public struct PreparationTaskDescription: IndexTaskDescription {
6060
targetsToPrepare: [ConfiguredTarget],
6161
buildSystemManager: BuildSystemManager,
6262
preparationUpToDateTracker: UpToDateTracker<ConfiguredTarget>,
63-
indexProcessDidProduceResult: @escaping @Sendable (IndexProcessResult) -> Void,
63+
logMessageToIndexLog: @escaping @Sendable (_ taskID: IndexTaskID, _ message: String) -> Void,
6464
testHooks: IndexTestHooks
6565
) {
6666
self.targetsToPrepare = targetsToPrepare
6767
self.buildSystemManager = buildSystemManager
6868
self.preparationUpToDateTracker = preparationUpToDateTracker
69-
self.indexProcessDidProduceResult = indexProcessDidProduceResult
69+
self.logMessageToIndexLog = logMessageToIndexLog
7070
self.testHooks = testHooks
7171
}
7272

@@ -105,7 +105,7 @@ public struct PreparationTaskDescription: IndexTaskDescription {
105105
do {
106106
try await buildSystemManager.prepare(
107107
targets: targetsToPrepare,
108-
indexProcessDidProduceResult: indexProcessDidProduceResult
108+
logMessageToIndexLog: logMessageToIndexLog
109109
)
110110
} catch {
111111
logger.error(

0 commit comments

Comments
 (0)