IORing.swift 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944
  1. #if compiler(>=6.2) && $Lifetimes
  2. #if os(Linux)
  3. import CSystem
  4. // needed for mmap
  5. #if canImport(Glibc)
  6. import Glibc
  7. #elseif canImport(Musl)
  8. import Musl
  9. #endif
  10. import Synchronization
  11. private var ioringSupported: Bool {
  12. __SWIFT_IORING_SUPPORTED != 0
  13. }
  14. //This was #defines in older headers, so we redeclare it to get a consistent import
  15. internal enum RegistrationOps: UInt32 {
  16. case registerBuffers = 0
  17. case unregisterBuffers = 1
  18. case registerFiles = 2
  19. case unregisterFiles = 3
  20. case registerEventFD = 4
  21. case unregisterEventFD = 5
  22. case registerFilesUpdate = 6
  23. case registerEventFDAsync = 7
  24. case registerProbe = 8
  25. case registerPersonality = 9
  26. case unregisterPersonality = 10
  27. }
  28. extension UnsafeMutableRawPointer {
  29. func advanced(by offset: UInt32) -> UnsafeMutableRawPointer {
  30. return advanced(by: Int(offset))
  31. }
  32. }
  33. extension UnsafeMutableRawBufferPointer {
  34. func to_iovec() -> iovec {
  35. iovec(iov_base: baseAddress, iov_len: count)
  36. }
  37. }
  38. /// Owns the FilePaths whose interior pointers have been written into pending
  39. /// SQEs. The kernel copies SQE-referenced data out during io_uring_enter (due to
  40. /// IORING_FEAT_SUBMIT_STABLE), so the paths only need to live across the prepare->submit gap
  41. /// This is a class purely to avoid making methods calling `pin` mutating, which would be an API break
  42. @usableFromInline
  43. internal final class PendingPathBuffers {
  44. @usableFromInline
  45. var paths: [FilePath] = []
  46. init(reservedCapacity: Int) {
  47. paths.reserveCapacity(reservedCapacity)
  48. }
  49. @usableFromInline
  50. func pin(_ path: FilePath) -> UnsafePointer<CInterop.PlatformChar> {
  51. paths.append(path)
  52. // Safe to escape: `paths` keeps the FilePath (and its underlying
  53. // [SystemChar] buffer) alive until clear() or class deinit.
  54. return paths.last!.withPlatformString { $0 }
  55. }
  56. @usableFromInline
  57. func clear() {
  58. paths.removeAll(keepingCapacity: true)
  59. }
  60. }
  61. // all pointers in this struct reference kernel-visible memory
  62. @usableFromInline struct SQRing: ~Copyable {
  63. @usableFromInline let kernelHead: UnsafePointer<Atomic<UInt32>>
  64. @usableFromInline let kernelTail: UnsafePointer<Atomic<UInt32>>
  65. @usableFromInline var userTail: UInt32
  66. // from liburing: the kernel should never change these
  67. // might change in the future with resizable rings?
  68. @usableFromInline let ringMask: UInt32
  69. // let ringEntries: UInt32 - absorbed into array.count
  70. // ring flags bitfield
  71. // currently used by the kernel only in SQPOLL mode to indicate
  72. // when the polling thread needs to be woken up
  73. @usableFromInline let flags: UnsafePointer<Atomic<UInt32>>
  74. // ring array
  75. // maps indexes between the actual ring and the submissionQueueEntries list,
  76. // allowing the latter to be used as a kind of freelist with enough work?
  77. // currently, just 1:1 mapping (0..<n)
  78. @usableFromInline let array: UnsafeMutableBufferPointer<UInt32>
  79. }
  80. @usableFromInline struct CQRing: ~Copyable {
  81. @usableFromInline let kernelHead: UnsafePointer<Atomic<UInt32>>
  82. @usableFromInline let kernelTail: UnsafePointer<Atomic<UInt32>>
  83. @usableFromInline let ringMask: UInt32
  84. @usableFromInline let cqes: UnsafeBufferPointer<io_uring_cqe>
  85. }
  86. @inline(__always) @inlinable
  87. internal func _tryWriteRequest(
  88. _ request: __owned RawIORequest, ring: inout SQRing,
  89. submissionQueueEntries: UnsafeMutableBufferPointer<swift_io_uring_sqe>
  90. )
  91. -> Bool
  92. {
  93. if let entry = _getSubmissionEntry(
  94. ring: &ring, submissionQueueEntries: submissionQueueEntries) {
  95. entry.pointee = request.rawValue
  96. return true
  97. }
  98. return false
  99. }
  100. //TODO: omitting signal mask for now
  101. //Tell the kernel that we've submitted requests and/or are waiting for completions
  102. @inlinable
  103. internal func _enter(
  104. ring: borrowing SQRing,
  105. ringDescriptor: Int32,
  106. numEvents: UInt32,
  107. minCompletions: UInt32,
  108. flags: UInt32
  109. ) throws(Errno) -> Int32 {
  110. // Ring always needs enter right now;
  111. // TODO: support SQPOLL here
  112. while true {
  113. do {
  114. let ret = try _ioUringEnter(
  115. ringDescriptor: ringDescriptor,
  116. toSubmit: numEvents,
  117. minComplete: minCompletions,
  118. flags: flags,
  119. sig: nil
  120. )
  121. if _getSubmissionQueueCount(ring: ring) > 0 {
  122. // See https://github.com/axboe/liburing/issues/309,
  123. // in some cases not all pending requests are submitted
  124. continue
  125. }
  126. return ret
  127. // error handling:
  128. // EAGAIN (try again),
  129. // EBADF / EBADFD / EOPNOTSUPP / ENXIO
  130. // (failure in ring lifetime management, fatal),
  131. // EINVAL (bad constant flag?, fatal),
  132. // EFAULT (bad address for argument from library, fatal)
  133. } catch Errno.resourceTemporarilyUnavailable {
  134. //TODO: should we wait a bit on AGAIN?
  135. //alternatively we could treat EAGAIN the same as EINTR in the wrapper
  136. continue
  137. }
  138. }
  139. }
  140. @inlinable
  141. internal func _submitRequests(ring: borrowing SQRing, ringDescriptor: Int32) throws(Errno) {
  142. let flushedEvents = _flushQueue(ring: ring)
  143. _ = try _enter(
  144. ring: ring, ringDescriptor: ringDescriptor, numEvents: flushedEvents, minCompletions: 0, flags: 0)
  145. }
  146. @inlinable
  147. internal func _getSubmissionQueueCount(ring: borrowing SQRing) -> UInt32 {
  148. return ring.userTail - ring.kernelHead.pointee.load(ordering: .acquiring)
  149. }
  150. @inlinable
  151. internal func _getRemainingSubmissionQueueCapacity(ring: borrowing SQRing) -> UInt32 {
  152. return UInt32(truncatingIfNeeded: ring.array.count) - _getSubmissionQueueCount(ring: ring)
  153. }
  154. @inlinable
  155. internal func _getUnconsumedCompletionCount(ring: borrowing CQRing) -> UInt32 {
  156. return ring.kernelTail.pointee.load(ordering: .acquiring)
  157. - ring.kernelHead.pointee.load(ordering: .acquiring)
  158. }
  159. @inlinable
  160. internal func _flushQueue(ring: borrowing SQRing) -> UInt32 {
  161. ring.kernelTail.pointee.store(
  162. ring.userTail, ordering: .releasing
  163. )
  164. return _getSubmissionQueueCount(ring: ring)
  165. }
  166. @inlinable
  167. internal func _getSubmissionEntry(
  168. ring: inout SQRing, submissionQueueEntries: UnsafeMutableBufferPointer<swift_io_uring_sqe>
  169. ) -> UnsafeMutablePointer<
  170. swift_io_uring_sqe
  171. >? {
  172. let next = ring.userTail &+ 1 //this is expected to wrap
  173. let kernelHead: UInt32 = ring.kernelHead.pointee.load(ordering: .acquiring)
  174. // FEAT: 128-bit event support (not in MVP)
  175. if next - kernelHead <= ring.array.count {
  176. // let sqe = &sq->sqes[(sq->sqe_tail & sq->ring_mask) << shift];
  177. let sqeIndex = Int(
  178. ring.userTail & ring.ringMask
  179. )
  180. let sqe = submissionQueueEntries
  181. .baseAddress.unsafelyUnwrapped
  182. .advanced(by: sqeIndex)
  183. ring.userTail = next
  184. return sqe
  185. }
  186. return nil
  187. }
  188. private func setUpRing(
  189. queueDepth: UInt32, flags: IORing.SetupFlags
  190. ) throws(Errno) ->
  191. (params: io_uring_params, ringDescriptor: Int32, ringPtr: UnsafeMutableRawPointer?, ringSize: Int, submissionRingPtr: UnsafeMutableRawPointer?, submissionRingSize: Int, completionRingPtr: UnsafeMutableRawPointer?, completionRingSize: Int, sqes: UnsafeMutableRawPointer) {
  192. var params = io_uring_params()
  193. params.flags = flags.rawValue
  194. var err: Errno? = nil
  195. let ringDescriptor = withUnsafeMutablePointer(to: &params) {
  196. let result = io_uring_setup(queueDepth, $0)
  197. if result < 0 {
  198. err = Errno.current
  199. }
  200. return result
  201. }
  202. if let err {
  203. throw err
  204. }
  205. // We require IORING_FEAT_SUBMIT_STABLE so the kernel copies SQE-referenced
  206. // pathnames (and other inline data) out of userspace before io_uring_enter
  207. // returns; the path-buffer side store on IORing relies on this to free
  208. // path buffers as soon as a submission completes.
  209. let requiredFeatures =
  210. IORing.Features.nonDroppingCompletions.rawValue
  211. | IORing.Features.stableSubmissions.rawValue
  212. if params.features & requiredFeatures != requiredFeatures
  213. {
  214. close(ringDescriptor)
  215. throw Errno.invalidArgument
  216. }
  217. let submitRingSize =
  218. params.sq_off.array
  219. + params.sq_entries * UInt32(MemoryLayout<UInt32>.size)
  220. let completionRingSize =
  221. params.cq_off.cqes
  222. + params.cq_entries * UInt32(MemoryLayout<io_uring_cqe>.size)
  223. let ringSize = Int(max(submitRingSize, completionRingSize))
  224. var ringPtr: UnsafeMutableRawPointer!
  225. var sqPtr: UnsafeMutableRawPointer!
  226. var cqPtr: UnsafeMutableRawPointer!
  227. if params.features & IORING_FEAT_SINGLE_MMAP != 0{
  228. ringPtr = mmap(
  229. /* addr: */ nil,
  230. /* len: */ ringSize,
  231. /* prot: */ PROT_READ | PROT_WRITE,
  232. /* flags: */ MAP_SHARED | MAP_POPULATE,
  233. /* fd: */ ringDescriptor,
  234. /* offset: */ off_t(IORING_OFF_SQ_RING)
  235. )
  236. if ringPtr == MAP_FAILED {
  237. let errno = Errno.current
  238. close(ringDescriptor)
  239. throw errno
  240. }
  241. } else {
  242. sqPtr = mmap(
  243. /* addr: */ nil,
  244. /* len: */ Int(submitRingSize),
  245. /* prot: */ PROT_READ | PROT_WRITE,
  246. /* flags: */ MAP_SHARED | MAP_POPULATE,
  247. /* fd: */ ringDescriptor,
  248. /* offset: */ off_t(IORING_OFF_SQ_RING)
  249. )
  250. if sqPtr == MAP_FAILED {
  251. let errno = Errno.current
  252. close(ringDescriptor)
  253. throw errno
  254. }
  255. cqPtr = mmap(
  256. /* addr: */ nil,
  257. /* len: */ Int(completionRingSize),
  258. /* prot: */ PROT_READ | PROT_WRITE,
  259. /* flags: */ MAP_SHARED | MAP_POPULATE,
  260. /* fd: */ ringDescriptor,
  261. /* offset: */ off_t(IORING_OFF_CQ_RING)
  262. )
  263. if cqPtr == MAP_FAILED {
  264. let errno: Errno = Errno.current
  265. close(ringDescriptor)
  266. throw errno
  267. }
  268. }
  269. // map the submission queue
  270. let sqes = mmap(
  271. /* addr: */ nil,
  272. /* len: */ Int(params.sq_entries) * MemoryLayout<swift_io_uring_sqe>.size,
  273. /* prot: */ PROT_READ | PROT_WRITE,
  274. /* flags: */ MAP_SHARED | MAP_POPULATE,
  275. /* fd: */ ringDescriptor,
  276. /* offset: */ off_t(IORING_OFF_SQES)
  277. )
  278. if sqes == MAP_FAILED {
  279. let errno = Errno.current
  280. if ringPtr != nil {
  281. munmap(ringPtr, ringSize)
  282. } else {
  283. if sqPtr != nil {
  284. munmap(sqPtr, Int(submitRingSize))
  285. }
  286. if cqPtr != nil {
  287. munmap(cqPtr, Int(completionRingSize))
  288. }
  289. }
  290. close(ringDescriptor)
  291. throw errno
  292. }
  293. return (params: params, ringDescriptor: ringDescriptor, ringPtr: ringPtr, ringSize: ringSize, submissionRingPtr: sqPtr, submissionRingSize: Int(submitRingSize), completionRingPtr: cqPtr, completionRingSize: Int(completionRingSize), sqes: sqes!)
  294. }
  295. ///IORing provides facilities for
  296. /// * Registering and unregistering resources (files and buffers), an `io_uring` specific variation on Unix file IOdescriptors that improves their efficiency
  297. /// * Registering and unregistering eventfds, which allow asynchronous waiting for completions
  298. /// * Enqueueing IO requests
  299. /// * Dequeueing IO completions
  300. public struct IORing: ~Copyable {
  301. let ringFlags: UInt32
  302. @usableFromInline let ringDescriptor: Int32
  303. @usableFromInline var submissionRing: SQRing
  304. // FEAT: set this eventually
  305. let submissionPolling: Bool = false
  306. @usableFromInline let completionRing: CQRing
  307. @usableFromInline let submissionQueueEntries: UnsafeMutableBufferPointer<swift_io_uring_sqe>
  308. // kept around for unmap / cleanup. TODO: we can save a few words of memory by figuring out how to handle cleanup for non-IORING_FEAT_SINGLE_MMAP better
  309. let ringSize: Int
  310. let ringPtr: UnsafeMutableRawPointer?
  311. let submissionRingSize: Int
  312. let submissionRingPtr: UnsafeMutableRawPointer?
  313. let completionRingSize: Int
  314. let completionRingPtr: UnsafeMutableRawPointer?
  315. @usableFromInline var _registeredFiles: [UInt32]
  316. @usableFromInline var _registeredBuffers: [iovec]
  317. @usableFromInline let _pendingPathBuffers: PendingPathBuffers
  318. var features = Features(rawValue: 0)
  319. /// RegisteredResource is used via its typealiases, RegisteredFile and RegisteredBuffer. Registering file descriptors and buffers with the IORing allows for more efficient access to them.
  320. public struct RegisteredResource<T> {
  321. public typealias Resource = T
  322. @usableFromInline let resource: T
  323. public let index: Int
  324. @inlinable internal init(
  325. resource: T,
  326. index: Int
  327. ) {
  328. self.resource = resource
  329. self.index = index
  330. }
  331. }
  332. public typealias RegisteredFile = RegisteredResource<UInt32>
  333. public typealias RegisteredBuffer = RegisteredResource<iovec>
  334. /// SetupFlags represents configuration options to an IORing as it's being created
  335. public struct SetupFlags: OptionSet, RawRepresentable, Hashable {
  336. public var rawValue: UInt32
  337. @inlinable public init(rawValue: UInt32) {
  338. self.rawValue = rawValue
  339. }
  340. @inlinable public static var pollCompletions: SetupFlags { .init(rawValue: UInt32(1) << 0) } //IORING_SETUP_IOPOLL
  341. @inlinable public static var pollSubmissions: SetupFlags { .init(rawValue: UInt32(1) << 1) } //IORING_SETUP_SQPOLL
  342. //TODO: figure out how to expose IORING_SETUP_SQ_AFF, IORING_SETUP_CQSIZE, IORING_SETUP_ATTACH_WQ
  343. @inlinable public static var clampMaxEntries: SetupFlags { .init(rawValue: UInt32(1) << 4) } //IORING_SETUP_CLAMP
  344. @inlinable public static var startDisabled: SetupFlags { .init(rawValue: UInt32(1) << 6) } //IORING_SETUP_R_DISABLED
  345. @inlinable public static var continueSubmittingOnError: SetupFlags { .init(rawValue: UInt32(1) << 7) } //IORING_SETUP_SUBMIT_ALL
  346. //TODO: do we want to expose IORING_SETUP_COOP_TASKRUN and IORING_SETUP_TASKRUN_FLAG?
  347. //public static var runTasksCooperatively: SetupFlags { .init(rawValue: UInt32(1) << 8) } //IORING_SETUP_COOP_TASKRUN
  348. //TODO: can we even do different size sqe/cqe? It requires a kernel feature, but how do we convince swift to let the types be different sizes?
  349. //internal static var use128ByteSQEs: SetupFlags { .init(rawValue: UInt32(1) << 10) } //IORING_SETUP_SQE128
  350. //internal static var use32ByteCQEs: SetupFlags { .init(rawValue: UInt32(1) << 11) } //IORING_SETUP_CQE32
  351. @inlinable public static var singleSubmissionThread: SetupFlags { .init(rawValue: UInt32(1) << 12) } //IORING_SETUP_SINGLE_ISSUER
  352. @inlinable public static var deferRunningTasks: SetupFlags { .init(rawValue: UInt32(1) << 13) } //IORING_SETUP_DEFER_TASKRUN
  353. //pretty sure we don't want to expose IORING_SETUP_NO_MMAP or IORING_SETUP_REGISTERED_FD_ONLY currently
  354. //TODO: should IORING_SETUP_NO_SQARRAY be the default? do we need to adapt anything to it?
  355. }
  356. /// Initializes an IORing with enough space for `queueDepth` prepared requests and completed operations
  357. public init(queueDepth: UInt32, flags: SetupFlags = []) throws(Errno) {
  358. guard ioringSupported else {
  359. throw Errno.notSupported
  360. }
  361. let (params, tmpRingDescriptor, tmpRingPtr, tmpRingSize, tmpSQPtr, tmpSQSize, tmpCQPtr, tmpCQSize, sqes) = try setUpRing(queueDepth: queueDepth, flags: flags)
  362. // All throws need to be before initializing ivars here to avoid
  363. // "error: conditional initialization or destruction of noncopyable types is not supported;
  364. // this variable must be consistently in an initialized or uninitialized state through every code path"
  365. // Pre-compute values to avoid accessing partially initialized state
  366. let ringBasePtr = tmpRingPtr ?? tmpSQPtr!
  367. let completionBasePtr = tmpRingPtr ?? tmpCQPtr!
  368. let submissionRing = SQRing(
  369. kernelHead: UnsafePointer<Atomic<UInt32>>(
  370. ringBasePtr.advanced(by: params.sq_off.head)
  371. .assumingMemoryBound(to: Atomic<UInt32>.self)
  372. ),
  373. kernelTail: UnsafePointer<Atomic<UInt32>>(
  374. ringBasePtr.advanced(by: params.sq_off.tail)
  375. .assumingMemoryBound(to: Atomic<UInt32>.self)
  376. ),
  377. userTail: 0, // no requests yet
  378. ringMask: ringBasePtr.advanced(by: params.sq_off.ring_mask)
  379. .assumingMemoryBound(to: UInt32.self).pointee,
  380. flags: UnsafePointer<Atomic<UInt32>>(
  381. ringBasePtr.advanced(by: params.sq_off.flags)
  382. .assumingMemoryBound(to: Atomic<UInt32>.self)
  383. ),
  384. array: UnsafeMutableBufferPointer(
  385. start: ringBasePtr.advanced(by: params.sq_off.array)
  386. .assumingMemoryBound(to: UInt32.self),
  387. count: Int(
  388. ringBasePtr.advanced(by: params.sq_off.ring_entries)
  389. .assumingMemoryBound(to: UInt32.self).pointee)
  390. )
  391. )
  392. let completionRing = CQRing(
  393. kernelHead: UnsafePointer<Atomic<UInt32>>(
  394. completionBasePtr.advanced(by: params.cq_off.head)
  395. .assumingMemoryBound(to: Atomic<UInt32>.self)
  396. ),
  397. kernelTail: UnsafePointer<Atomic<UInt32>>(
  398. completionBasePtr.advanced(by: params.cq_off.tail)
  399. .assumingMemoryBound(to: Atomic<UInt32>.self)
  400. ),
  401. ringMask: completionBasePtr.advanced(by: params.cq_off.ring_mask)
  402. .assumingMemoryBound(to: UInt32.self).pointee,
  403. cqes: UnsafeBufferPointer(
  404. start: completionBasePtr.advanced(by: params.cq_off.cqes)
  405. .assumingMemoryBound(to: io_uring_cqe.self),
  406. count: Int(
  407. completionBasePtr.advanced(by: params.cq_off.ring_entries)
  408. .assumingMemoryBound(to: UInt32.self).pointee)
  409. )
  410. )
  411. let submissionQueueEntries = UnsafeMutableBufferPointer(
  412. start: sqes.assumingMemoryBound(to: swift_io_uring_sqe.self),
  413. count: Int(params.sq_entries)
  414. )
  415. // Now initialize all stored properties
  416. self.features = Features(rawValue: params.features)
  417. self.ringDescriptor = tmpRingDescriptor
  418. self.ringPtr = tmpRingPtr
  419. self.ringSize = tmpRingSize
  420. self.submissionRingPtr = tmpSQPtr
  421. self.submissionRingSize = tmpSQSize
  422. self.completionRingPtr = tmpCQPtr
  423. self.completionRingSize = tmpCQSize
  424. self._registeredFiles = []
  425. self._registeredBuffers = []
  426. self._pendingPathBuffers = PendingPathBuffers(reservedCapacity: Int(params.sq_entries))
  427. self.submissionRing = submissionRing
  428. self.completionRing = completionRing
  429. self.submissionQueueEntries = submissionQueueEntries
  430. self.ringFlags = params.flags
  431. // fill submission ring array with 1:1 map to underlying SQEs
  432. // (happens after all properties are initialized)
  433. for i in 0 ..< self.submissionRing.array.count {
  434. self.submissionRing.array[i] = UInt32(i)
  435. }
  436. }
  437. @inlinable
  438. internal func _blockingConsumeCompletionGuts<Err: Error>(
  439. minimumCount: UInt32,
  440. maximumCount: UInt32,
  441. extraArgs: UnsafeMutablePointer<swift_io_uring_getevents_arg>? = nil,
  442. consumer: (consuming IORing.Completion?, Errno?, Bool) throws(Err) -> Void
  443. ) throws(Err) {
  444. var count = 0
  445. while let completion = _tryConsumeCompletion(ring: completionRing) {
  446. count += 1
  447. if completion.result < 0 {
  448. try consumer(nil, Errno(rawValue: -completion.result), false)
  449. } else {
  450. try consumer(completion, nil, false)
  451. }
  452. if count == maximumCount {
  453. try consumer(nil, nil, true)
  454. return
  455. }
  456. }
  457. if count < minimumCount {
  458. while count < minimumCount {
  459. var sz = 0
  460. var flags = IORING_ENTER_GETEVENTS
  461. if extraArgs != nil {
  462. sz = MemoryLayout<swift_io_uring_getevents_arg>.size
  463. flags |= IORING_ENTER_EXT_ARG
  464. }
  465. do {
  466. _ = try _ioUringEnter2(
  467. ringDescriptor: ringDescriptor,
  468. toSubmit: 0,
  469. minComplete: minimumCount,
  470. flags: flags,
  471. args: extraArgs,
  472. argsSize: sz
  473. )
  474. break
  475. // error handling:
  476. // EAGAIN (try again),
  477. // EBADF / EBADFD / EOPNOTSUPP / ENXIO
  478. // (failure in ring lifetime management, fatal),
  479. // EINVAL (bad constant flag?, fatal),
  480. // EFAULT (bad address for argument from library, fatal)
  481. // EBUSY (not enough space for events; implies events filled
  482. // by kernel between kernelTail load and now)
  483. // ETIME (timeout from extraArgs.ts elapsed before
  484. // minimumCount completions arrived)
  485. } catch Errno.resourceBusy {
  486. break
  487. } catch Errno.resourceTemporarilyUnavailable {
  488. continue
  489. } catch Errno.timeout {
  490. try consumer(nil, .timeout, true)
  491. return
  492. } catch {
  493. fatalError(
  494. "fatal error in receiving requests: "
  495. + error.debugDescription
  496. )
  497. }
  498. }
  499. var count = 0
  500. while let completion = _tryConsumeCompletion(ring: completionRing) {
  501. count += 1
  502. if completion.result < 0 {
  503. try consumer(nil, Errno(rawValue: -completion.result), false)
  504. } else {
  505. try consumer(completion, nil, false)
  506. }
  507. if count == maximumCount {
  508. break
  509. }
  510. }
  511. try consumer(nil, nil, true)
  512. }
  513. }
  514. @inlinable
  515. internal func _blockingConsumeOneCompletion(
  516. extraArgs: UnsafeMutablePointer<swift_io_uring_getevents_arg>? = nil
  517. ) throws(Errno) -> Completion {
  518. var result: Completion? = nil
  519. try _blockingConsumeCompletionGuts(minimumCount: 1, maximumCount: 1, extraArgs: extraArgs) {
  520. (completion: consuming Completion?, error, done) throws(Errno) in
  521. if let error {
  522. throw error
  523. }
  524. if let completion {
  525. result = consume completion
  526. }
  527. }
  528. return result.take()!
  529. }
  530. /// Synchronously waits for an operation to complete for up to `timeout` (or forever if not specified)
  531. @inlinable
  532. public func blockingConsumeCompletion(
  533. timeout: Duration? = nil
  534. ) throws(Errno) -> Completion {
  535. if let timeout {
  536. var ts = timespec(
  537. tv_sec: Int(timeout.components.seconds),
  538. tv_nsec: Int(timeout.components.attoseconds / 1_000_000_000)
  539. )
  540. return try withUnsafePointer(to: &ts) { (tsPtr) throws(Errno) -> Completion in
  541. var args = swift_io_uring_getevents_arg(
  542. sigmask: 0,
  543. sigmask_sz: 0,
  544. min_wait_usec: 0,
  545. ts: UInt64(UInt(bitPattern: tsPtr))
  546. )
  547. return try _blockingConsumeOneCompletion(extraArgs: &args)
  548. }
  549. } else {
  550. return try _blockingConsumeOneCompletion()
  551. }
  552. }
  553. /// Synchronously waits for `minimumCount` or more operations to complete for up to `timeout` (or forever if not specified). For each completed operation found, `consumer` is called to handle processing it
  554. @inlinable
  555. public func blockingConsumeCompletions<Err: Error>(
  556. minimumCount: UInt32 = 1,
  557. timeout: Duration? = nil,
  558. consumer: (consuming Completion?, Errno?, Bool) throws(Err) -> Void
  559. ) throws(Err) {
  560. if let timeout {
  561. var ts = timespec(
  562. tv_sec: Int(timeout.components.seconds),
  563. tv_nsec: Int(timeout.components.attoseconds / 1_000_000_000)
  564. )
  565. try withUnsafePointer(to: &ts) { (tsPtr) throws(Err) in
  566. var args = swift_io_uring_getevents_arg(
  567. sigmask: 0,
  568. sigmask_sz: 0,
  569. min_wait_usec: 0,
  570. ts: UInt64(UInt(bitPattern: tsPtr))
  571. )
  572. try _blockingConsumeCompletionGuts(
  573. minimumCount: minimumCount, maximumCount: UInt32.max, extraArgs: &args,
  574. consumer: consumer)
  575. }
  576. } else {
  577. try _blockingConsumeCompletionGuts(
  578. minimumCount: minimumCount, maximumCount: UInt32.max, consumer: consumer)
  579. }
  580. }
  581. // public func peekNextCompletion() -> IOCompletion {
  582. // }
  583. /// Takes a completed operation from the ring and returns it, if one is ready. Otherwise, returns nil.
  584. @inlinable
  585. public func tryConsumeCompletion() -> Completion? {
  586. return _tryConsumeCompletion(ring: completionRing)
  587. }
  588. @inlinable
  589. func _tryConsumeCompletion(ring: borrowing CQRing) -> Completion? {
  590. let tail = ring.kernelTail.pointee.load(ordering: .acquiring)
  591. let head = ring.kernelHead.pointee.load(ordering: .acquiring)
  592. if tail != head {
  593. // 32 byte copy - oh well
  594. let res = ring.cqes[Int(head & ring.ringMask)]
  595. ring.kernelHead.pointee.store(head &+ 1, ordering: .releasing)
  596. return Completion(rawValue: res)
  597. }
  598. return nil
  599. }
  600. /// Registers an event monitoring file descriptor with the ring. The file descriptor becomes readable whenever completions are ready to be dequeued. See `man eventfd(2)` for additional information.
  601. public mutating func registerEventFD(_ descriptor: FileDescriptor) throws(Errno) {
  602. var rawfd = descriptor.rawValue
  603. _ = try _ioUringRegister(
  604. ringDescriptor: ringDescriptor,
  605. opcode: RegistrationOps.registerEventFD.rawValue,
  606. arg: &rawfd,
  607. nrArgs: 1
  608. )
  609. }
  610. /// Removes a registered event file descriptor from the ring
  611. public mutating func unregisterEventFD() throws(Errno) {
  612. _ = try _ioUringRegister(
  613. ringDescriptor: ringDescriptor,
  614. opcode: RegistrationOps.unregisterEventFD.rawValue,
  615. arg: nil,
  616. nrArgs: 0
  617. )
  618. }
  619. /// Registers `count` files with the ring for later use in IO operations
  620. public mutating func registerFileSlots(count: Int) throws(Errno) -> RegisteredResources<RegisteredFile.Resource> {
  621. precondition(_registeredFiles.isEmpty)
  622. precondition(count < UInt32.max)
  623. let files = [UInt32](repeating: UInt32.max, count: count)
  624. try files.withUnsafeBufferPointer { bPtr throws(Errno) in
  625. _ = try _ioUringRegister(
  626. ringDescriptor: self.ringDescriptor,
  627. opcode: RegistrationOps.registerFiles.rawValue,
  628. arg: UnsafeMutableRawPointer(mutating: bPtr.baseAddress),
  629. nrArgs: UInt32(truncatingIfNeeded: count)
  630. )
  631. }
  632. _registeredFiles = files
  633. return registeredFileSlots
  634. }
  635. /// Removes registered files from the ring
  636. public func unregisterFiles() throws(Errno) {
  637. _ = try _ioUringRegister(
  638. ringDescriptor: ringDescriptor,
  639. opcode: RegistrationOps.unregisterFiles.rawValue,
  640. arg: nil,
  641. nrArgs: 0
  642. )
  643. }
  644. /// Allows access to registered files by index
  645. @inlinable
  646. public var registeredFileSlots: RegisteredResources<RegisteredFile.Resource> {
  647. RegisteredResources(resources: _registeredFiles)
  648. }
  649. /// Registers buffers with the ring for later use in IO operations
  650. public mutating func registerBuffers(_ buffers: some Collection<UnsafeMutableRawBufferPointer>) throws(Errno)
  651. -> RegisteredResources<RegisteredBuffer.Resource>
  652. {
  653. precondition(buffers.count < UInt32.max)
  654. precondition(_registeredBuffers.isEmpty)
  655. let iovecs = buffers.map { $0.to_iovec() }
  656. try iovecs.withUnsafeBufferPointer { bPtr throws(Errno) in
  657. _ = try _ioUringRegister(
  658. ringDescriptor: self.ringDescriptor,
  659. opcode: RegistrationOps.registerBuffers.rawValue,
  660. arg: UnsafeMutableRawPointer(mutating: bPtr.baseAddress),
  661. nrArgs: UInt32(truncatingIfNeeded: buffers.count)
  662. )
  663. }
  664. _registeredBuffers = iovecs
  665. return registeredBuffers
  666. }
  667. /// Registers buffers with the ring for later use in IO operations
  668. @inlinable
  669. public mutating func registerBuffers(_ buffers: UnsafeMutableRawBufferPointer...) throws(Errno)
  670. -> RegisteredResources<RegisteredBuffer.Resource>
  671. {
  672. try registerBuffers(buffers)
  673. }
  674. /// A view of the registered files or buffers in a ring
  675. public struct RegisteredResources<T>: RandomAccessCollection {
  676. @usableFromInline let resources: [T]
  677. @inlinable public var startIndex: Int { 0 }
  678. @inlinable public var endIndex: Int { resources.endIndex }
  679. @inlinable init(resources: [T]) {
  680. self.resources = resources
  681. }
  682. @inlinable public subscript(position: Int) -> RegisteredResource<T> {
  683. RegisteredResource(resource: resources[position], index: position)
  684. }
  685. @inlinable public subscript(position: UInt16) -> RegisteredResource<T> {
  686. RegisteredResource(resource: resources[Int(position)], index: Int(position))
  687. }
  688. }
  689. /// Allows access to registered files by index
  690. @inlinable
  691. public var registeredBuffers: RegisteredResources<RegisteredBuffer.Resource> {
  692. RegisteredResources(resources: _registeredBuffers)
  693. }
  694. public func unregisterBuffers() throws(Errno) {
  695. _ = try _ioUringRegister(
  696. ringDescriptor: self.ringDescriptor,
  697. opcode: RegistrationOps.unregisterBuffers.rawValue,
  698. arg: nil,
  699. nrArgs: 0
  700. )
  701. }
  702. /// Sends all prepared requests to the kernel for processing. Results will be delivered as completions, which can be dequeued from the ring.
  703. @inlinable
  704. public func submitPreparedRequests() throws(Errno) {
  705. try _submitRequests(ring: submissionRing, ringDescriptor: ringDescriptor)
  706. // IORING_FEAT_SUBMIT_STABLE guarantees the kernel has copied any
  707. // SQE-referenced data (e.g. openat/unlinkAt pathnames) before
  708. // io_uring_enter returns; safe to release path storage now.
  709. _pendingPathBuffers.clear()
  710. }
  711. /// Sends all prepared requests to the kernel for processing, and then dequeues at least `minimumCount` completions, waiting up to `timeout` for them to become available. `consumer` is called to process each completed IO operation as it becomes available.
  712. @inlinable
  713. public func submitPreparedRequestsAndConsumeCompletions<Err: Error>(
  714. minimumCount: UInt32 = 1,
  715. timeout: Duration? = nil,
  716. consumer: (consuming Completion?, Errno?, Bool) throws(Err) -> Void
  717. ) throws(Err) {
  718. //TODO: optimize this to one uring_enter
  719. do {
  720. try submitPreparedRequests()
  721. } catch (let e) {
  722. try consumer(nil, e, true)
  723. }
  724. try blockingConsumeCompletions(
  725. minimumCount: minimumCount,
  726. timeout: timeout,
  727. consumer: consumer
  728. )
  729. }
  730. /// Attempts to prepare an IO request for submission to the kernel. Returns false if no space is available to enqueue the request
  731. @inlinable
  732. public mutating func prepare(request: __owned Request) -> Bool {
  733. guard _getRemainingSubmissionQueueCapacity(ring: submissionRing) >= 1 else {
  734. return false
  735. }
  736. var raw: RawIORequest? = request.makeRawRequest(pathBuffers: _pendingPathBuffers)
  737. let ok = _tryWriteRequest(
  738. raw.take()!, ring: &submissionRing, submissionQueueEntries: submissionQueueEntries)
  739. assert(ok)
  740. return ok
  741. }
  742. /// Attempts to prepare a chain of linked IO requests for submission to the kernel. Returns false if not enough space is available to enqueue the request. If any linked operation fails, subsequent operations will be canceled. Linked operations always execute in order.
  743. @inlinable
  744. mutating func prepare(linkedRequests: some BidirectionalCollection<Request>) -> Bool {
  745. guard linkedRequests.count > 0 else {
  746. return true
  747. }
  748. let freeSQECount = _getRemainingSubmissionQueueCapacity(ring: submissionRing)
  749. guard freeSQECount >= linkedRequests.count else {
  750. return false
  751. }
  752. let last = linkedRequests.last!
  753. var allAdded = true
  754. for req in linkedRequests.dropLast() {
  755. var raw = req.makeRawRequest(pathBuffers: _pendingPathBuffers)
  756. raw.linkToNextRequest()
  757. let successfullyAdded = _tryWriteRequest(
  758. raw, ring: &submissionRing, submissionQueueEntries: submissionQueueEntries)
  759. assert(successfullyAdded)
  760. allAdded = allAdded && successfullyAdded
  761. }
  762. let successfullyAdded = _tryWriteRequest(
  763. last.makeRawRequest(pathBuffers: _pendingPathBuffers), ring: &submissionRing,
  764. submissionQueueEntries: submissionQueueEntries)
  765. assert(successfullyAdded)
  766. return allAdded && successfullyAdded
  767. }
  768. /// Prepares a sequence of requests for submission to the ring. Returns false if the submission queue doesn't have enough available space.
  769. @inlinable
  770. public mutating func prepare(linkedRequests: Request...) -> Bool {
  771. prepare(linkedRequests: linkedRequests)
  772. }
  773. /// Prepares and submits a sequence of requests to the ring. Returns false if the submission queue doesn't have enough available space.
  774. @inlinable
  775. public mutating func submit(linkedRequests: Request...) throws(Errno) -> Bool {
  776. if !prepare(linkedRequests: linkedRequests) {
  777. return false
  778. }
  779. try submitPreparedRequests()
  780. return true
  781. }
  782. /// Describes which io_uring features are supported by the kernel this program is running on
  783. public struct Features: OptionSet, RawRepresentable, Hashable {
  784. public let rawValue: UInt32
  785. @inlinable public init(rawValue: UInt32) {
  786. self.rawValue = rawValue
  787. }
  788. //IORING_FEAT_SINGLE_MMAP is handled internally
  789. @inlinable public static var nonDroppingCompletions: Features { .init(rawValue: UInt32(1) << 1) } //IORING_FEAT_NODROP
  790. @inlinable public static var stableSubmissions: Features { .init(rawValue: UInt32(1) << 2) } //IORING_FEAT_SUBMIT_STABLE
  791. @inlinable public static var currentFilePosition: Features { .init(rawValue: UInt32(1) << 3) } //IORING_FEAT_RW_CUR_POS
  792. @inlinable public static var assumingTaskCredentials: Features { .init(rawValue: UInt32(1) << 4) } //IORING_FEAT_CUR_PERSONALITY
  793. @inlinable public static var fastPolling: Features { .init(rawValue: UInt32(1) << 5) } //IORING_FEAT_FAST_POLL
  794. @inlinable public static var epoll32BitFlags: Features { .init(rawValue: UInt32(1) << 6) } //IORING_FEAT_POLL_32BITS
  795. @inlinable public static var pollNonFixedFiles: Features { .init(rawValue: UInt32(1) << 7) } //IORING_FEAT_SQPOLL_NONFIXED
  796. @inlinable public static var extendedArguments: Features { .init(rawValue: UInt32(1) << 8) } //IORING_FEAT_EXT_ARG
  797. @inlinable public static var nativeWorkers: Features { .init(rawValue: UInt32(1) << 9) } //IORING_FEAT_NATIVE_WORKERS
  798. @inlinable public static var resourceTags: Features { .init(rawValue: UInt32(1) << 10) } //IORING_FEAT_RSRC_TAGS
  799. @inlinable public static var allowsSkippingSuccessfulCompletions: Features { .init(rawValue: UInt32(1) << 11) } //IORING_FEAT_CQE_SKIP
  800. @inlinable public static var improvedLinkedFiles: Features { .init(rawValue: UInt32(1) << 12) } //IORING_FEAT_LINKED_FILE
  801. @inlinable public static var registerRegisteredRings: Features { .init(rawValue: UInt32(1) << 13) } //IORING_FEAT_REG_REG_RING
  802. @inlinable public static var minimumTimeout: Features { .init(rawValue: UInt32(1) << 15) } //IORING_FEAT_MIN_TIMEOUT
  803. @inlinable public static var bundledSendReceive: Features { .init(rawValue: UInt32(1) << 14) } //IORING_FEAT_RECVSEND_BUNDLE
  804. }
  805. /// Describes which io_uring features are supported by the kernel this program is running on
  806. public var supportedFeatures: Features {
  807. return features
  808. }
  809. deinit {
  810. if let ringPtr {
  811. munmap(ringPtr, ringSize)
  812. } else if let submissionRingPtr, let completionRingPtr {
  813. munmap(submissionRingPtr, submissionRingSize)
  814. munmap(completionRingPtr, completionRingSize)
  815. }
  816. munmap(
  817. UnsafeMutableRawPointer(submissionQueueEntries.baseAddress!),
  818. submissionQueueEntries.count * MemoryLayout<swift_io_uring_sqe>.size
  819. )
  820. close(ringDescriptor)
  821. }
  822. }
  823. extension IORing.RegisteredBuffer {
  824. @unsafe @inlinable public var unsafeBuffer: UnsafeMutableRawBufferPointer {
  825. return .init(start: resource.iov_base, count: resource.iov_len)
  826. }
  827. @inlinable public var mutableBytes: MutableRawSpan {
  828. @_lifetime(&self)
  829. mutating get {
  830. let span = MutableRawSpan(_unsafeBytes: unsafeBuffer)
  831. return unsafe _overrideLifetime(span, mutating: &self)
  832. }
  833. }
  834. @inlinable public var bytes: RawSpan {
  835. let span = RawSpan(_unsafeBytes: UnsafeRawBufferPointer(unsafeBuffer))
  836. return unsafe _overrideLifetime(span, borrowing: self)
  837. }
  838. }
  839. #endif // os(Linux)
  840. #endif // compiler(>=6.2) && $Lifetimes