1313//===----------------------------------------------------------------------===//
1414
1515import CAsyncDNSResolver
16+ import Foundation
1617
1718/// ``DNSResolver`` implementation backed by c-ares C library.
1819@available ( macOS 10 . 15 , iOS 13 , tvOS 13 , watchOS 6 , * )
19- public class CAresDNSResolver : DNSResolver {
20+ public final class CAresDNSResolver : DNSResolver , Sendable {
2021 let options : Options
2122 let ares : Ares
2223
@@ -121,18 +122,15 @@ extension QueryType {
121122// MARK: - c-ares query wrapper
122123
123124@available ( macOS 10 . 15 , iOS 13 , tvOS 13 , watchOS 6 , * )
124- class Ares {
125+ final class Ares : Sendable {
125126 typealias QueryCallback = @convention ( c) (
126127 UnsafeMutableRawPointer ? , CInt , CInt , UnsafeMutablePointer < CUnsignedChar > ? , CInt
127128 ) -> Void
128129
129- let options : AresOptions
130- let channel : AresChannel
131-
130+ private let channel : AresChannel
132131 private let queryProcessor : QueryProcessor
133132
134133 init ( options: AresOptions ) throws {
135- self . options = options
136134 self . channel = try AresChannel ( options: options)
137135
138136 // Need to call `ares_process` or `ares_process_fd` for query callbacks to happen
@@ -145,7 +143,8 @@ class Ares {
145143 name: String ,
146144 replyParser: ReplyParser
147145 ) async throws -> ReplyParser . Reply {
148- try await withTaskCancellationHandler (
146+ let channel = self . channel
147+ return try await withTaskCancellationHandler (
149148 operation: {
150149 try await withCheckedThrowingContinuation { continuation in
151150 let handler = QueryReplyHandler ( parser: replyParser, continuation)
@@ -178,7 +177,7 @@ class Ares {
178177 }
179178 } ,
180179 onCancel: {
181- self . channel. withChannel { channel in
180+ channel. withChannel { channel in
182181 ares_cancel ( channel)
183182 }
184183 }
@@ -198,16 +197,18 @@ extension Ares {
198197 // https://github.com/dimbleby/c-ares-resolver/blob/master/src/unix/eventloop.rs // ignore-unacceptable-language
199198 // https://github.com/dimbleby/rust-c-ares/blob/master/src/channel.rs // ignore-unacceptable-language
200199 // https://github.com/dimbleby/rust-c-ares/blob/master/examples/event-loop.rs // ignore-unacceptable-language
201- class QueryProcessor {
200+ final class QueryProcessor : @ unchecked Sendable {
202201 static let defaultPollInterval : UInt64 = 10 * 1_000_000 // 10ms
203202
204203 private let channel : AresChannel
205204 private let pollIntervalNanos : UInt64
206205
207- private var pollingTask : Task < Void , Error > ?
206+ private let lock = NSLock ( )
207+ private var locked_pollingTask : Task < Void , Error > ?
208208
209209 deinit {
210- self . pollingTask? . cancel ( )
210+ // No need to lock here as there can exist no more strong references to self.
211+ self . locked_pollingTask? . cancel ( )
211212 }
212213
213214 init ( channel: AresChannel , pollIntervalNanos: UInt64 = QueryProcessor . defaultPollInterval) {
@@ -218,7 +219,7 @@ extension Ares {
218219 /// Asks c-ares for the set of socket descriptors we are waiting on for the `ares_channel`'s pending queries
219220 /// then call `ares_process_fd` if any is ready for read and/or write.
220221 /// c-ares returns up to `ARES_GETSOCK_MAXNUM` socket descriptors only. If more are in use (unlikely) they are not reported back.
221- func poll( ) async {
222+ func poll( ) {
222223 var socks = [ ares_socket_t] ( repeating: ares_socket_t ( ) , count: Int ( ARES_GETSOCK_MAXNUM) )
223224
224225 self . channel. withChannel { channel in
@@ -249,12 +250,14 @@ extension Ares {
249250 }
250251
251252 private func schedule( ) {
252- self . pollingTask = Task { [ weak self] in
253+ self . lock. lock ( )
254+ defer { self . lock. unlock ( ) }
255+ self . locked_pollingTask = Task { [ weak self] in
253256 guard let s = self else {
254257 return
255258 }
256259 try await Task . sleep ( nanoseconds: s. pollIntervalNanos)
257- await s. poll ( )
260+ s. poll ( )
258261 }
259262 }
260263 }
@@ -291,7 +294,7 @@ extension Ares {
291294// MARK: - c-ares query reply parsers
292295
293296protocol AresQueryReplyParser {
294- associatedtype Reply
297+ associatedtype Reply : Sendable
295298
296299 func parse( buffer: UnsafeMutablePointer < CUnsignedChar > ? , length: CInt ) throws -> Reply
297300}
0 commit comments