Skip to content

Commit a501353

Browse files
stevappleLukasa
andauthored
Deprecate NIOAtomics in favor of Atomics (#2204)
Co-authored-by: Cory Benfield <[email protected]>
1 parent adda737 commit a501353

22 files changed

+316
-273
lines changed

Package.swift

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,25 @@
1515

1616
import PackageDescription
1717

18+
let swiftAtomics: PackageDescription.Target.Dependency = .product(name: "Atomics", package: "swift-atomics")
19+
1820
var targets: [PackageDescription.Target] = [
1921
.target(name: "NIOCore",
2022
dependencies: ["NIOConcurrencyHelpers", "CNIOLinux", "CNIOWindows"]),
2123
.target(name: "_NIODataStructures"),
2224
.target(name: "NIOEmbedded",
2325
dependencies: ["NIOCore",
2426
"NIOConcurrencyHelpers",
25-
"_NIODataStructures"]),
27+
"_NIODataStructures",
28+
swiftAtomics]),
2629
.target(name: "NIOPosix",
2730
dependencies: ["CNIOLinux",
2831
"CNIODarwin",
2932
"CNIOWindows",
3033
"NIOConcurrencyHelpers",
3134
"NIOCore",
32-
"_NIODataStructures"]),
35+
"_NIODataStructures",
36+
swiftAtomics]),
3337
.target(name: "NIO",
3438
dependencies: ["NIOCore",
3539
"NIOEmbedded",
@@ -87,7 +91,7 @@ var targets: [PackageDescription.Target] = [
8791
dependencies: ["NIOPosix", "NIOCore"],
8892
exclude: ["README.md"]),
8993
.target(name: "NIOTestUtils",
90-
dependencies: ["NIOPosix", "NIOCore", "NIOEmbedded", "NIOHTTP1"]),
94+
dependencies: ["NIOPosix", "NIOCore", "NIOEmbedded", "NIOHTTP1", swiftAtomics]),
9195
.executableTarget(name: "NIOCrashTester",
9296
dependencies: ["NIOPosix", "NIOCore", "NIOEmbedded", "NIOHTTP1", "NIOWebSocket", "NIOFoundationCompat"]),
9397
.executableTarget(name: "NIOAsyncAwaitDemo",
@@ -135,6 +139,7 @@ let package = Package(
135139
.library(name: "NIOTestUtils", targets: ["NIOTestUtils"]),
136140
],
137141
dependencies: [
142+
.package(url: "https://github.com/apple/swift-atomics.git", from: "1.0.2"),
138143
],
139144
targets: targets
140145
)

Sources/NIOConcurrencyHelpers/NIOAtomic.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ extension UInt: NIOAtomicPrimitive {
193193
/// By necessity, all atomic values are references: after all, it makes no
194194
/// sense to talk about managing an atomic value when each time it's modified
195195
/// the thread that modified it gets a local copy!
196+
@available(*, deprecated, message:"please use ManagedAtomic from https://github.com/apple/swift-atomics instead")
196197
public final class NIOAtomic<T: NIOAtomicPrimitive> {
197198
@usableFromInline
198199
typealias Manager = ManagedBufferPointer<Void, T.AtomicWrapper>
@@ -313,6 +314,7 @@ public final class NIOAtomic<T: NIOAtomicPrimitive> {
313314
}
314315

315316
#if compiler(>=5.5) && canImport(_Concurrency)
317+
@available(*, deprecated)
316318
extension NIOAtomic: Sendable {
317319

318320
}

Sources/NIOConcurrencyHelpers/atomics.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ fileprivate func sys_sched_yield() {
5151
/// Atomic primitives are useful when building constructs that need to
5252
/// communicate or cooperate across multiple threads. In the case of
5353
/// SwiftNIO this usually involves communicating across multiple event loops.
54+
@available(*, deprecated, message: "please use UnsafeAtomic from https://github.com/apple/swift-atomics instead")
5455
public struct UnsafeEmbeddedAtomic<T: AtomicPrimitive> {
5556
@usableFromInline
5657
internal let value: OpaquePointer
@@ -173,7 +174,7 @@ public struct UnsafeEmbeddedAtomic<T: AtomicPrimitive> {
173174
/// By necessity, all atomic values are references: after all, it makes no
174175
/// sense to talk about managing an atomic value when each time it's modified
175176
/// the thread that modified it gets a local copy!
176-
@available(*, deprecated, message:"please use NIOAtomic instead")
177+
@available(*, deprecated, message:"please use ManagedAtomic from https://github.com/apple/swift-atomics instead")
177178
public final class Atomic<T: AtomicPrimitive> {
178179
@usableFromInline
179180
internal let embedded: UnsafeEmbeddedAtomic<T>

Sources/NIOEmbedded/AsyncEmbeddedEventLoop.swift

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
//
1313
//===----------------------------------------------------------------------===//
1414
#if compiler(>=5.5.2) && canImport(_Concurrency)
15+
import Atomics
1516
import Dispatch
1617
import _NIODataStructures
1718
import NIOCore
@@ -62,9 +63,9 @@ public final class NIOAsyncEmbeddedEventLoop: EventLoop, @unchecked Sendable {
6263

6364
/// The current "time" for this event loop. This is an amount in nanoseconds.
6465
/// As we need to access this from any thread, we store this as an atomic.
65-
private let _now = NIOAtomic<UInt64>.makeAtomic(value: 0)
66+
private let _now = ManagedAtomic<UInt64>(0)
6667
internal var now: NIODeadline {
67-
return NIODeadline.uptimeNanoseconds(self._now.load())
68+
return NIODeadline.uptimeNanoseconds(self._now.load(ordering: .relaxed))
6869
}
6970

7071
/// This is used to derive an identifier for this loop.
@@ -80,7 +81,7 @@ public final class NIOAsyncEmbeddedEventLoop: EventLoop, @unchecked Sendable {
8081
// arbitrary threads. This is required by the EventLoop protocol and cannot be avoided.
8182
// Specifically, Scheduled<T> creation requires us to be able to define the cancellation
8283
// operation, so the task ID has to be created early.
83-
private let scheduledTaskCounter = NIOAtomic<UInt64>.makeAtomic(value: 0)
84+
private let scheduledTaskCounter = ManagedAtomic<UInt64>(0)
8485
private var scheduledTasks = PriorityQueue<EmbeddedScheduledTask>()
8586

8687
/// Keep track of where promises are allocated to ensure we can identify their source if they leak.
@@ -143,7 +144,7 @@ public final class NIOAsyncEmbeddedEventLoop: EventLoop, @unchecked Sendable {
143144
@discardableResult
144145
public func scheduleTask<T>(deadline: NIODeadline, _ task: @escaping () throws -> T) -> Scheduled<T> {
145146
let promise: EventLoopPromise<T> = self.makePromise()
146-
let taskID = self.scheduledTaskCounter.add(1)
147+
let taskID = self.scheduledTaskCounter.loadThenWrappingIncrement(ordering: .relaxed)
147148

148149
let scheduled = Scheduled(promise: promise, cancellationTask: {
149150
if self.inEventLoop {
@@ -270,7 +271,7 @@ public final class NIOAsyncEmbeddedEventLoop: EventLoop, @unchecked Sendable {
270271

271272
// Set the time correctly before we call into user code, then
272273
// call in for all tasks.
273-
self._now.store(nextTask.readyTime.uptimeNanoseconds)
274+
self._now.store(nextTask.readyTime.uptimeNanoseconds, ordering: .relaxed)
274275

275276
for task in tasks {
276277
task.task()
@@ -280,7 +281,7 @@ public final class NIOAsyncEmbeddedEventLoop: EventLoop, @unchecked Sendable {
280281
}
281282

282283
// Finally ensure we got the time right.
283-
self._now.store(newTime.uptimeNanoseconds)
284+
self._now.store(newTime.uptimeNanoseconds, ordering: .relaxed)
284285

285286
continuation.resume()
286287
}
@@ -311,7 +312,7 @@ public final class NIOAsyncEmbeddedEventLoop: EventLoop, @unchecked Sendable {
311312
internal func drainScheduledTasksByRunningAllCurrentlyScheduledTasks() {
312313
var currentlyScheduledTasks = self.scheduledTasks
313314
while let nextTask = currentlyScheduledTasks.pop() {
314-
self._now.store(nextTask.readyTime.uptimeNanoseconds)
315+
self._now.store(nextTask.readyTime.uptimeNanoseconds, ordering: .relaxed)
315316
nextTask.task()
316317
}
317318
// Just fail all the remaining scheduled tasks. Despite having run all the tasks that were

Sources/NIOPosix/BaseSocketChannel.swift

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import NIOCore
1616
import NIOConcurrencyHelpers
17+
import Atomics
1718

1819
private struct SocketChannelLifecycleManager {
1920
// MARK: Types
@@ -35,7 +36,7 @@ private struct SocketChannelLifecycleManager {
3536
// MARK: properties
3637
private let eventLoop: EventLoop
3738
// this is queried from the Channel, ie. must be thread-safe
38-
internal let isActiveAtomic: NIOAtomic<Bool>
39+
internal let isActiveAtomic: ManagedAtomic<Bool>
3940
// these are only to be accessed on the EventLoop
4041

4142
// have we seen the `.readEOF` notification
@@ -50,9 +51,9 @@ private struct SocketChannelLifecycleManager {
5051
self.eventLoop.assertInEventLoop()
5152
switch (oldValue, self.currentState) {
5253
case (_, .activated):
53-
self.isActiveAtomic.store(true)
54+
self.isActiveAtomic.store(true, ordering: .relaxed)
5455
case (.activated, _):
55-
self.isActiveAtomic.store(false)
56+
self.isActiveAtomic.store(false, ordering: .relaxed)
5657
default:
5758
()
5859
}
@@ -63,7 +64,7 @@ private struct SocketChannelLifecycleManager {
6364
// isActiveAtomic needs to be injected as it's accessed from arbitrary threads and `SocketChannelLifecycleManager` is usually held mutable
6465
internal init(
6566
eventLoop: EventLoop,
66-
isActiveAtomic: NIOAtomic<Bool>,
67+
isActiveAtomic: ManagedAtomic<Bool>,
6768
supportReconnect: Bool
6869
) {
6970
self.eventLoop = eventLoop
@@ -238,7 +239,7 @@ class BaseSocketChannel<SocketType: BaseSocketProtocol>: SelectableChannel, Chan
238239
private let closePromise: EventLoopPromise<Void>
239240
internal let selectableEventLoop: SelectableEventLoop
240241
private let _offEventLoopLock = Lock()
241-
private let isActiveAtomic: NIOAtomic<Bool> = .makeAtomic(value: false)
242+
private let isActiveAtomic: ManagedAtomic<Bool> = .init(false)
242243
// just a thread-safe way of having something to print about the socket from any thread
243244
internal let socketDescription: String
244245

@@ -345,7 +346,7 @@ class BaseSocketChannel<SocketType: BaseSocketProtocol>: SelectableChannel, Chan
345346

346347
// This is `Channel` API so must be thread-safe.
347348
public var isActive: Bool {
348-
return self.isActiveAtomic.load()
349+
return self.isActiveAtomic.load(ordering: .relaxed)
349350
}
350351

351352
// This is `Channel` API so must be thread-safe.

Sources/NIOPosix/MultiThreadedEventLoopGroup.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import NIOCore
1616
import NIOConcurrencyHelpers
1717
import Dispatch
18+
import Atomics
1819

1920
struct NIORegistration: Registration {
2021
enum ChannelType {
@@ -33,7 +34,7 @@ struct NIORegistration: Registration {
3334
var registrationID: SelectorRegistrationID
3435
}
3536

36-
private let nextEventLoopGroupID = NIOAtomic.makeAtomic(value: 0)
37+
private let nextEventLoopGroupID = ManagedAtomic(0)
3738

3839
/// Called per `NIOThread` that is created for an EventLoop to do custom initialization of the `NIOThread` before the actual `EventLoop` is run on it.
3940
typealias ThreadInitializer = (NIOThread) -> Void
@@ -62,7 +63,7 @@ public final class MultiThreadedEventLoopGroup: EventLoopGroup {
6263
private static let threadSpecificEventLoop = ThreadSpecificVariable<SelectableEventLoop>()
6364

6465
private let myGroupID: Int
65-
private let index = NIOAtomic<Int>.makeAtomic(value: 0)
66+
private let index = ManagedAtomic<Int>(0)
6667
private var eventLoops: [SelectableEventLoop]
6768
private let shutdownLock: Lock = Lock()
6869
private var runState: RunState = .running
@@ -148,7 +149,7 @@ public final class MultiThreadedEventLoopGroup: EventLoopGroup {
148149
/// - threadInitializers: The `ThreadInitializer`s to use.
149150
internal init(threadInitializers: [ThreadInitializer],
150151
selectorFactory: @escaping () throws -> NIOPosix.Selector<NIORegistration> = NIOPosix.Selector<NIORegistration>.init) {
151-
let myGroupID = nextEventLoopGroupID.add(1)
152+
let myGroupID = nextEventLoopGroupID.loadThenWrappingIncrement(ordering: .relaxed)
152153
self.myGroupID = myGroupID
153154
var idx = 0
154155
self.eventLoops = [] // Just so we're fully initialised and can vend `self` to the `SelectableEventLoop`.
@@ -187,7 +188,7 @@ public final class MultiThreadedEventLoopGroup: EventLoopGroup {
187188
///
188189
/// - returns: The next `EventLoop` to use.
189190
public func next() -> EventLoop {
190-
return eventLoops[abs(index.add(1) % eventLoops.count)]
191+
return eventLoops[abs(index.loadThenWrappingIncrement(ordering: .relaxed) % eventLoops.count)]
191192
}
192193

193194
/// Returns the current `EventLoop` if we are on an `EventLoop` of this `MultiThreadedEventLoopGroup` instance.

Sources/NIOPosix/PendingDatagramWritesManager.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
//
1313
//===----------------------------------------------------------------------===//
1414
import NIOCore
15-
import NIOConcurrencyHelpers
15+
import Atomics
1616

1717
private struct PendingDatagramWrite {
1818
var data: ByteBuffer
@@ -400,7 +400,7 @@ final class PendingDatagramWritesManager: PendingWritesManager {
400400
private var state = PendingDatagramWritesState()
401401

402402
internal var waterMark: ChannelOptions.Types.WriteBufferWaterMark = ChannelOptions.Types.WriteBufferWaterMark(low: 32 * 1024, high: 64 * 1024)
403-
internal let channelWritabilityFlag: NIOAtomic<Bool> = .makeAtomic(value: true)
403+
internal let channelWritabilityFlag = ManagedAtomic<Bool>(true)
404404
internal var publishedWritability = true
405405
internal var writeSpinCount: UInt = 16
406406
private(set) var isOpen = true
@@ -452,7 +452,8 @@ final class PendingDatagramWritesManager: PendingWritesManager {
452452
assert(self.isOpen)
453453
self.state.append(pendingWrite)
454454

455-
if self.state.bytes > waterMark.high && channelWritabilityFlag.compareAndExchange(expected: true, desired: false) {
455+
if self.state.bytes > waterMark.high &&
456+
channelWritabilityFlag.compareExchange(expected: true, desired: false, ordering: .relaxed).exchanged {
456457
// Returns false to signal the Channel became non-writable and we need to notify the user.
457458
self.publishedWritability = false
458459
return false
@@ -550,7 +551,7 @@ final class PendingDatagramWritesManager: PendingWritesManager {
550551
let (promise, result) = self.state.didWrite(data, messages: messages)
551552

552553
if self.state.bytes < waterMark.low {
553-
channelWritabilityFlag.store(true)
554+
channelWritabilityFlag.store(true, ordering: .relaxed)
554555
}
555556

556557
self.fulfillPromise(promise)

Sources/NIOPosix/PendingWritesManager.swift

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
//
1313
//===----------------------------------------------------------------------===//
1414
import NIOCore
15-
import NIOConcurrencyHelpers
15+
import Atomics
1616

1717
private struct PendingStreamWrite {
1818
var data: IOData
@@ -283,7 +283,7 @@ final class PendingStreamWritesManager: PendingWritesManager {
283283
private var storageRefs: UnsafeMutableBufferPointer<Unmanaged<AnyObject>>
284284

285285
internal var waterMark: ChannelOptions.Types.WriteBufferWaterMark = ChannelOptions.Types.WriteBufferWaterMark(low: 32 * 1024, high: 64 * 1024)
286-
internal let channelWritabilityFlag: NIOAtomic<Bool> = .makeAtomic(value: true)
286+
internal let channelWritabilityFlag = ManagedAtomic(true)
287287
internal var publishedWritability = true
288288

289289
internal var writeSpinCount: UInt = 16
@@ -315,7 +315,8 @@ final class PendingStreamWritesManager: PendingWritesManager {
315315
assert(self.isOpen)
316316
self.state.append(.init(data: data, promise: promise))
317317

318-
if self.state.bytes > waterMark.high && channelWritabilityFlag.compareAndExchange(expected: true, desired: false) {
318+
if self.state.bytes > waterMark.high &&
319+
channelWritabilityFlag.compareExchange(expected: true, desired: false, ordering: .relaxed).exchanged {
319320
// Returns false to signal the Channel became non-writable and we need to notify the user.
320321
self.publishedWritability = false
321322
return false
@@ -364,7 +365,7 @@ final class PendingStreamWritesManager: PendingWritesManager {
364365
let (promise, result) = self.state.didWrite(itemCount: itemCount, result: result)
365366

366367
if self.state.bytes < waterMark.low {
367-
channelWritabilityFlag.store(true)
368+
channelWritabilityFlag.store(true, ordering: .relaxed)
368369
}
369370

370371
promise?.succeed(())
@@ -459,7 +460,7 @@ internal protocol PendingWritesManager: AnyObject {
459460
var isFlushPending: Bool { get }
460461
var writeSpinCount: UInt { get }
461462
var currentBestWriteMechanism: WriteMechanism { get }
462-
var channelWritabilityFlag: NIOAtomic<Bool> { get }
463+
var channelWritabilityFlag: ManagedAtomic<Bool> { get }
463464

464465
/// Represents the writability state the last time we published a writability change to the `Channel`.
465466
/// This is used in `triggerWriteOperations` to determine whether we need to trigger a writability
@@ -470,7 +471,7 @@ internal protocol PendingWritesManager: AnyObject {
470471
extension PendingWritesManager {
471472
// This is called from `Channel` API so must be thread-safe.
472473
var isWritable: Bool {
473-
return self.channelWritabilityFlag.load()
474+
return self.channelWritabilityFlag.load(ordering: .relaxed)
474475
}
475476

476477
internal func triggerWriteOperations(triggerOneWriteOperation: (WriteMechanism) throws -> OneWriteOperationResult) throws -> OverallWriteResult {
@@ -514,6 +515,6 @@ extension PendingWritesManager {
514515
extension PendingStreamWritesManager: CustomStringConvertible {
515516
var description: String {
516517
return "PendingStreamWritesManager { isFlushPending: \(self.isFlushPending), " +
517-
/* */ "writabilityFlag: \(self.channelWritabilityFlag.load())), state: \(self.state) }"
518+
/* */ "writabilityFlag: \(self.channelWritabilityFlag.load(ordering: .relaxed))), state: \(self.state) }"
518519
}
519520
}

Sources/NIOPosix/SelectableEventLoop.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import Dispatch
1616
import NIOCore
1717
import NIOConcurrencyHelpers
1818
import _NIODataStructures
19+
import Atomics
1920

2021
/// Execute the given closure and ensure we release all auto pools if needed.
2122
@inlinable
@@ -73,7 +74,7 @@ internal final class SelectableEventLoop: EventLoop {
7374
// This may only be read/written while holding the _tasksLock.
7475
internal var _pendingTaskPop = false
7576
@usableFromInline
76-
internal var scheduledTaskCounter = NIOAtomic.makeAtomic(value: UInt64(0))
77+
internal var scheduledTaskCounter = ManagedAtomic<UInt64>(0)
7778
@usableFromInline
7879
internal var _scheduledTasks = PriorityQueue<ScheduledTask>()
7980

@@ -276,7 +277,7 @@ Further information:
276277
@inlinable
277278
internal func scheduleTask<T>(deadline: NIODeadline, _ task: @escaping () throws -> T) -> Scheduled<T> {
278279
let promise: EventLoopPromise<T> = self.makePromise()
279-
let task = ScheduledTask(id: self.scheduledTaskCounter.add(1), {
280+
let task = ScheduledTask(id: self.scheduledTaskCounter.loadThenWrappingIncrement(ordering: .relaxed), {
280281
do {
281282
promise.succeed(try task())
282283
} catch let err {
@@ -317,7 +318,7 @@ Further information:
317318
@inlinable
318319
internal func execute(_ task: @escaping () -> Void) {
319320
// nothing we can do if we fail enqueuing here.
320-
try? self._schedule0(ScheduledTask(id: self.scheduledTaskCounter.add(1), task, { error in
321+
try? self._schedule0(ScheduledTask(id: self.scheduledTaskCounter.loadThenWrappingIncrement(ordering: .relaxed), task, { error in
321322
// do nothing
322323
}, .now()))
323324
}

0 commit comments

Comments
 (0)