diff --git a/Sources/BuildServerIntegration/SwiftPMBuildServer.swift b/Sources/BuildServerIntegration/SwiftPMBuildServer.swift index f012a17e..136b68ab 100644 --- a/Sources/BuildServerIntegration/SwiftPMBuildServer.swift +++ b/Sources/BuildServerIntegration/SwiftPMBuildServer.swift @@ -27,6 +27,7 @@ package import SKOptions import SourceControl @preconcurrency package import SourceKitLSPAPI import SwiftExtensions +import Synchronization @_spi(SourceKitLSP) import ToolsProtocolsSwiftExtensions import TSCExtensions package import ToolchainRegistry @@ -79,7 +80,7 @@ fileprivate extension TSCBasic.AbsolutePath { } } -private let preparationTaskID: AtomicUInt32 = AtomicUInt32(initialValue: 0) +private let preparationTaskID = Atomic(0) /// Swift Package Manager build server and workspace support. /// @@ -830,7 +831,9 @@ package actor SwiftPMBuildServer: BuiltInBuildServer { } let start = ContinuousClock.now - let taskID: TaskId = TaskId(id: "preparation-\(preparationTaskID.fetchAndIncrement())") + let taskID: TaskId = TaskId( + id: "preparation-\(preparationTaskID.wrappingAdd(1, ordering: .sequentiallyConsistent).oldValue)" + ) connectionToSourceKitLSP.send( BuildServerProtocol.OnBuildLogMessageNotification( type: .info, diff --git a/Sources/CompletionScoringTestSupport/TestExtensions.swift b/Sources/CompletionScoringTestSupport/TestExtensions.swift index 13eb99db..70187569 100644 --- a/Sources/CompletionScoringTestSupport/TestExtensions.swift +++ b/Sources/CompletionScoringTestSupport/TestExtensions.swift @@ -13,6 +13,7 @@ package import CompletionScoring import Foundation import SwiftExtensions +import Synchronization @_spi(SourceKitLSP) import ToolsProtocolsSwiftExtensions import XCTest @@ -102,7 +103,7 @@ extension XCTestCase { UserDefaults.standard.string(forKey: "TestMeasurementLogPath") }() - static let printBeginingOfLog = AtomicBool(initialValue: true) + static let printBeginingOfLog = Atomic(true) private static func openPerformanceLog() throws -> FileHandle? { try measurementsLogFile.map { path in @@ -115,9 +116,9 @@ extension XCTestCase { } let logFD = try FileHandle(forWritingAtPath: path).unwrap(orThrow: "Opening \(path) failed") try logFD.seekToEnd() - if printBeginingOfLog.value { + if printBeginingOfLog.load(ordering: .sequentiallyConsistent) { try logFD.print("========= \(Date().description(with: .current)) =========") - printBeginingOfLog.value = false + printBeginingOfLog.store(false, ordering: .sequentiallyConsistent) } return logFD } diff --git a/Sources/Diagnose/DiagnoseCommand.swift b/Sources/Diagnose/DiagnoseCommand.swift index d43e01f7..1d66f069 100644 --- a/Sources/Diagnose/DiagnoseCommand.swift +++ b/Sources/Diagnose/DiagnoseCommand.swift @@ -222,8 +222,8 @@ package struct DiagnoseCommand: AsyncParsableCommand { let outputFileUrl = bundlePath.appending(component: "log.txt") try FileManager.default.createFile(at: outputFileUrl, contents: nil) let fileHandle = try FileHandle(forWritingTo: outputFileUrl) - let bytesCollected = AtomicInt32(initialValue: 0) - let processExited = AtomicBool(initialValue: false) + let bytesCollected = ThreadSafeBox(initialValue: 0) + let processExited = ThreadSafeBox(initialValue: false) // 50 MB is an average log size collected by sourcekit-lsp diagnose. // It's a good proxy to show some progress indication for the majority of the time. let expectedLogSize = 50_000_000 @@ -239,8 +239,11 @@ package struct DiagnoseCommand: AsyncParsableCommand { outputRedirection: .stream( stdout: { @Sendable bytes in try? fileHandle.write(contentsOf: bytes) - bytesCollected.value += Int32(bytes.count) - var progress = Double(bytesCollected.value) / Double(expectedLogSize) + let totalBytes = bytesCollected.withLock { value -> Int32 in + value += Int32(bytes.count) + return value + } + var progress = Double(totalBytes) / Double(expectedLogSize) if progress > 1 { // The log is larger than we expected. Halt at 100% progress = 1 diff --git a/Sources/InProcessClient/InProcessSourceKitLSPClient.swift b/Sources/InProcessClient/InProcessSourceKitLSPClient.swift index 3d2c6a15..04caa12e 100644 --- a/Sources/InProcessClient/InProcessSourceKitLSPClient.swift +++ b/Sources/InProcessClient/InProcessSourceKitLSPClient.swift @@ -19,6 +19,7 @@ public import Foundation public import SKOptions package import SourceKitLSP import SwiftExtensions +import Synchronization import TSCExtensions package import ToolchainRegistry @_spi(SourceKitLSP) import ToolsProtocolsSwiftExtensions @@ -29,7 +30,7 @@ import struct TSCBasic.AbsolutePath public final class InProcessSourceKitLSPClient: Sendable { private let server: SourceKitLSPServer - private let nextRequestID = AtomicUInt32(initialValue: 0) + private let nextRequestID = Atomic(0) public convenience init( toolchainPath: URL?, @@ -123,7 +124,9 @@ public final class InProcessSourceKitLSPClient: Sendable { _ request: R, reply: @Sendable @escaping (LSPResult) -> Void ) -> RequestID { - let requestID = RequestID.string("sk-\(Int(nextRequestID.fetchAndIncrement()))") + let requestID = RequestID.string( + "sk-\(Int(nextRequestID.wrappingAdd(1, ordering: .sequentiallyConsistent).oldValue))" + ) server.handle(request, id: requestID, reply: reply) return requestID } diff --git a/Sources/SKTestSupport/MultiEntrySemaphore.swift b/Sources/SKTestSupport/MultiEntrySemaphore.swift index 4a7c1595..85e9d629 100644 --- a/Sources/SKTestSupport/MultiEntrySemaphore.swift +++ b/Sources/SKTestSupport/MultiEntrySemaphore.swift @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// import SwiftExtensions +import Synchronization @_spi(SourceKitLSP) import ToolsProtocolsSwiftExtensions import XCTest @@ -21,19 +22,21 @@ import XCTest /// done, all index operations should be able to run, not just one. package final class MultiEntrySemaphore: Sendable { private let name: String - private let signaled = AtomicBool(initialValue: false) + private let signaled = Atomic(false) package init(name: String) { self.name = name } package func signal() { - signaled.value = true + signaled.store(true, ordering: .sequentiallyConsistent) } package func waitOrThrow() async throws { do { - try await repeatUntilExpectedResult(sleepInterval: .seconds(0.01)) { signaled.value } + try await repeatUntilExpectedResult(sleepInterval: .seconds(0.01)) { + signaled.load(ordering: .sequentiallyConsistent) + } } catch { struct TimeoutError: Error, CustomStringConvertible { let name: String diff --git a/Sources/SKTestSupport/TestSourceKitLSPClient.swift b/Sources/SKTestSupport/TestSourceKitLSPClient.swift index 978b70ca..91c7ae48 100644 --- a/Sources/SKTestSupport/TestSourceKitLSPClient.swift +++ b/Sources/SKTestSupport/TestSourceKitLSPClient.swift @@ -22,6 +22,7 @@ import SourceKitD package import SourceKitLSP import SwiftExtensions package import SwiftSyntax +import Synchronization package import ToolchainRegistry @_spi(SourceKitLSP) import ToolsProtocolsSwiftExtensions import XCTest @@ -93,7 +94,7 @@ package final class TestSourceKitLSPClient: MessageHandler, Sendable { package typealias RequestHandler = @Sendable (Request) -> Request.Response /// The ID that should be assigned to the next request sent to the `server`. - private let nextRequestID = AtomicUInt32(initialValue: 0) + private let nextRequestID = Atomic(0) /// The server that handles the requests. package let server: SourceKitLSPServer @@ -242,7 +243,10 @@ package final class TestSourceKitLSPClient: MessageHandler, Sendable { // It's really unfortunate that there are no async deinits. If we had async // deinits, we could await the sending of a ShutdownRequest. let shutdownSemaphore = WrappedSemaphore(name: "Shutdown") - server.handle(ShutdownRequest(), id: .number(Int(nextRequestID.fetchAndIncrement()))) { result in + server.handle( + ShutdownRequest(), + id: .number(Int(nextRequestID.wrappingAdd(1, ordering: .sequentiallyConsistent).oldValue)) + ) { result in shutdownSemaphore.signal() } shutdownSemaphore.waitOrXCTFail() @@ -289,7 +293,7 @@ package final class TestSourceKitLSPClient: MessageHandler, Sendable { _ request: R, completionHandler: @Sendable @escaping (LSPResult) -> Void ) -> RequestID { - let requestID = RequestID.number(Int(nextRequestID.fetchAndIncrement())) + let requestID = RequestID.number(Int(nextRequestID.wrappingAdd(1, ordering: .sequentiallyConsistent).oldValue)) let replyOutstanding = ThreadSafeBox(initialValue: true) let timeoutTask = Task { try await Task.sleep(for: defaultTimeoutDuration) @@ -401,7 +405,7 @@ package final class TestSourceKitLSPClient: MessageHandler, Sendable { line: UInt = #line ) async throws -> Value where R.Response == VoidResponse { // Register a one-shot handler that records when the request arrives. - let received = AtomicBool(initialValue: false) + let received = ThreadSafeBox(initialValue: false) self.handleSingleRequest { (_: R) in received.value = true return VoidResponse() diff --git a/Sources/SemanticIndex/PreparationTaskDescription.swift b/Sources/SemanticIndex/PreparationTaskDescription.swift index 324c60e2..b6f64162 100644 --- a/Sources/SemanticIndex/PreparationTaskDescription.swift +++ b/Sources/SemanticIndex/PreparationTaskDescription.swift @@ -17,12 +17,13 @@ import Foundation @_spi(SourceKitLSP) import LanguageServerProtocolExtensions @_spi(SourceKitLSP) import SKLogging import SwiftExtensions +import Synchronization @_spi(SourceKitLSP) import ToolsProtocolsSwiftExtensions import struct TSCBasic.AbsolutePath import class TSCBasic.Process -private let preparationIDForLogging = AtomicUInt32(initialValue: 1) +private let preparationIDForLogging = Atomic(1) /// Describes a task to prepare a set of targets. /// @@ -30,7 +31,7 @@ private let preparationIDForLogging = AtomicUInt32(initialValue: 1) package struct PreparationTaskDescription: IndexTaskDescription { package static let idPrefix = "prepare" - package let id = preparationIDForLogging.fetchAndIncrement() + package let id = preparationIDForLogging.wrappingAdd(1, ordering: .sequentiallyConsistent).oldValue /// The targets that should be prepared. package let targetsToPrepare: [BuildTargetIdentifier] diff --git a/Sources/SemanticIndex/TaskScheduler.swift b/Sources/SemanticIndex/TaskScheduler.swift index a8dbd0ad..87700850 100644 --- a/Sources/SemanticIndex/TaskScheduler.swift +++ b/Sources/SemanticIndex/TaskScheduler.swift @@ -14,6 +14,7 @@ import Foundation @_spi(SourceKitLSP) import LanguageServerProtocolExtensions @_spi(SourceKitLSP) package import SKLogging import SwiftExtensions +import Synchronization @_spi(SourceKitLSP) import ToolsProtocolsSwiftExtensions /// See comment on ``TaskDescriptionProtocol/dependencies(to:taskPriority:)`` @@ -127,7 +128,7 @@ package actor QueuedTask { /// Every time `execute` gets called, a new task is placed in this continuation. See comment on `executionTask`. private let executionTaskCreatedContinuation: AsyncStream>.Continuation - private let _priority: AtomicUInt8 + private let _priority: Atomic /// The latest known priority of the task. /// @@ -135,10 +136,10 @@ package actor QueuedTask { /// it, the priority may get elevated. nonisolated var priority: TaskPriority { get { - TaskPriority(rawValue: _priority.value) + TaskPriority(rawValue: _priority.load(ordering: .sequentiallyConsistent)) } set { - _priority.value = newValue.rawValue + _priority.store(newValue.rawValue, ordering: .sequentiallyConsistent) } } @@ -148,13 +149,13 @@ package actor QueuedTask { private var cancelledToBeRescheduled: Bool = false /// Whether `resultTask` has been cancelled. - private let resultTaskCancelled: AtomicBool = .init(initialValue: false) + private let resultTaskCancelled = Atomic(false) - private let _isExecuting: AtomicBool = .init(initialValue: false) + private let _isExecuting = Atomic(false) /// Whether the task is currently executing or still queued to be executed later. package nonisolated var isExecuting: Bool { - return _isExecuting.value + return _isExecuting.load(ordering: .sequentiallyConsistent) } package nonisolated func cancel() { @@ -188,7 +189,7 @@ package actor QueuedTask { taskPriorityChangedCallback: @escaping @Sendable (_ newPriority: TaskPriority) -> Void, executionStateChangedCallback: (@Sendable (QueuedTask, TaskExecutionState) async -> Void)? ) async { - self._priority = AtomicUInt8(initialValue: priority.rawValue) + self._priority = Atomic(priority.rawValue) self.description = description self.executionStateChangedCallback = executionStateChangedCallback @@ -221,7 +222,7 @@ package actor QueuedTask { taskPriorityChangedCallback(self.priority) } } onCancel: { - self.resultTaskCancelled.value = true + self.resultTaskCancelled.store(true, ordering: .sequentiallyConsistent) } } } @@ -242,15 +243,15 @@ package actor QueuedTask { } precondition(executionTask == nil, "Task started twice") let task = Task.detached(priority: self.priority) { - if !Task.isCancelled && !self.resultTaskCancelled.value { + if !Task.isCancelled && !self.resultTaskCancelled.load(ordering: .sequentiallyConsistent) { await self.description.execute() } return await self.finalizeExecution() } - _isExecuting.value = true + _isExecuting.store(true, ordering: .sequentiallyConsistent) executionTask = task executionTaskCreatedContinuation.yield(task) - if self.resultTaskCancelled.value { + if self.resultTaskCancelled.load(ordering: .sequentiallyConsistent) { // The queued task might have been cancelled after the execution ask was started but before the task was yielded // to `executionTaskCreatedContinuation`. In that case the result task will simply cancel the await on the // `executionTaskCreatedStream` and hence not call `valuePropagatingCancellation` on the execution task. This @@ -265,7 +266,7 @@ package actor QueuedTask { /// Implementation detail of `execute` that is called after `self.description.execute()` finishes. private func finalizeExecution() async -> ExecutionTaskFinishStatus { self.executionTask = nil - _isExecuting.value = false + _isExecuting.store(false, ordering: .sequentiallyConsistent) if Task.isCancelled && self.cancelledToBeRescheduled { await executionStateChangedCallback?(self, .cancelledToBeRescheduled) self.cancelledToBeRescheduled = false diff --git a/Sources/SemanticIndex/UpdateIndexStoreTaskDescription.swift b/Sources/SemanticIndex/UpdateIndexStoreTaskDescription.swift index dfcfb196..af387d60 100644 --- a/Sources/SemanticIndex/UpdateIndexStoreTaskDescription.swift +++ b/Sources/SemanticIndex/UpdateIndexStoreTaskDescription.swift @@ -17,6 +17,7 @@ import Foundation @_spi(SourceKitLSP) import LanguageServerProtocolExtensions @_spi(SourceKitLSP) import SKLogging import SwiftExtensions +import Synchronization import TSCExtensions import ToolchainRegistry @_spi(SourceKitLSP) import ToolsProtocolsSwiftExtensions @@ -30,7 +31,7 @@ import enum TSCBasic.SystemError import WinSDK #endif -private let updateIndexStoreIDForLogging = AtomicUInt32(initialValue: 1) +private let updateIndexStoreIDForLogging = Atomic(1) package enum FileToIndex: CustomLogStringConvertible, Hashable { /// A non-header file @@ -114,7 +115,7 @@ private enum UpdateIndexStorePartition { /// This task description can be scheduled in a `TaskScheduler`. package struct UpdateIndexStoreTaskDescription: IndexTaskDescription { package static let idPrefix = "update-indexstore" - package let id = updateIndexStoreIDForLogging.fetchAndIncrement() + package let id = updateIndexStoreIDForLogging.wrappingAdd(1, ordering: .sequentiallyConsistent).oldValue /// The files that should be indexed. package let filesToIndex: [FileAndOutputPath] diff --git a/Sources/SwiftExtensions/AsyncUtils.swift b/Sources/SwiftExtensions/AsyncUtils.swift index fd32bbfa..e468a61b 100644 --- a/Sources/SwiftExtensions/AsyncUtils.swift +++ b/Sources/SwiftExtensions/AsyncUtils.swift @@ -94,7 +94,7 @@ package func withTimeoutResult( body: @escaping @Sendable () async throws -> T, resultReceivedAfterTimeout: @escaping @Sendable (_ result: T) async -> Void ) async throws -> WithTimeoutResult { - let didHitTimeout = AtomicBool(initialValue: false) + let didHitTimeout = ThreadSafeBox(initialValue: false) let stream = AsyncThrowingStream, any Error> { continuation in Task { diff --git a/Sources/SwiftLanguageService/CodeCompletionSession.swift b/Sources/SwiftLanguageService/CodeCompletionSession.swift index 19ad679c..0d4de3fa 100644 --- a/Sources/SwiftLanguageService/CodeCompletionSession.swift +++ b/Sources/SwiftLanguageService/CodeCompletionSession.swift @@ -24,12 +24,13 @@ import SwiftExtensions import SwiftParser @_spi(SourceKitLSP) import SwiftRefactor import SwiftSyntax +import Synchronization @_spi(SourceKitLSP) import ToolsProtocolsSwiftExtensions /// Uniquely identifies a code completion session. We need this so that when resolving a code completion item, we can /// verify that the item to resolve belongs to the code completion session that is currently open. struct CompletionSessionID: Equatable, Codable { - private static let nextSessionID = AtomicUInt32(initialValue: 0) + private static let nextSessionID = Atomic(0) let value: UInt32 @@ -38,7 +39,7 @@ struct CompletionSessionID: Equatable, Codable { } static func next() -> CompletionSessionID { - return CompletionSessionID(value: nextSessionID.fetchAndIncrement()) + return CompletionSessionID(value: nextSessionID.wrappingAdd(1, ordering: .sequentiallyConsistent).oldValue) } init(from decoder: any Decoder) throws { diff --git a/Sources/TSCExtensions/Process+Run.swift b/Sources/TSCExtensions/Process+Run.swift index 4b0fdb19..bac00b95 100644 --- a/Sources/TSCExtensions/Process+Run.swift +++ b/Sources/TSCExtensions/Process+Run.swift @@ -32,7 +32,7 @@ extension Process { /// Should the process not terminate on SIGINT after 2 seconds, it is terminated using `SIGKILL`. @discardableResult package func waitUntilExitStoppingProcessOnTaskCancellation() async throws -> ProcessResult { - let hasExited = AtomicBool(initialValue: false) + let hasExited = ThreadSafeBox(initialValue: false) return try await withTaskCancellationHandler { defer { hasExited.value = true