Skip to content
61 changes: 37 additions & 24 deletions Sources/AsyncDNSResolver/dnssd/DNSResolver_dnssd.swift
Original file line number Diff line number Diff line change
Expand Up @@ -110,21 +110,6 @@ struct DNSSD {
let recordStream = AsyncThrowingStream<ReplyHandler.Record, Error> { continuation in
let handler = QueryReplyHandler(handler: replyHandler, continuation)

// Wrap `handler` into a pointer so we can pass it to DNSServiceQueryRecord
let handlerPointer = UnsafeMutableRawPointer.allocate(
byteCount: MemoryLayout<QueryReplyHandler>.stride,
alignment: MemoryLayout<QueryReplyHandler>.alignment
)

handlerPointer.initializeMemory(as: QueryReplyHandler.self, repeating: handler, count: 1)

// The handler might be called multiple times so don't deallocate inside `callback`
defer {
let pointer = handlerPointer.assumingMemoryBound(to: QueryReplyHandler.self)
pointer.deinitialize(count: 1)
pointer.deallocate()
}

// This is called once per record received
let callback: DNSServiceQueryRecordReply = { _, _, _, errorCode, _, _, _, rdlen, rdata, _, context in
guard let handlerPointer = context else {
Expand All @@ -138,32 +123,54 @@ struct DNSSD {
handler.handleRecord(errorCode: errorCode, data: rdata, length: rdlen)
}

let serviceRefPtr = UnsafeMutablePointer<DNSServiceRef?>.allocate(capacity: 1)
defer { serviceRefPtr.deallocate() }
let serviceRefPointer = UnsafeMutablePointer<DNSServiceRef?>.allocate(capacity: 1)

// Wrap 'handler' into a pointer so we can pass it to DNSServiceQueryRecord
let replyHandlerPointer = UnsafeMutablePointer<QueryReplyHandler>.allocate(capacity: 1)
replyHandlerPointer.initialize(to: handler)

// Run the query
let _code = DNSServiceQueryRecord(
serviceRefPtr,
serviceRefPointer,
kDNSServiceFlagsTimeout,
0,
name,
UInt16(type.kDNSServiceType),
UInt16(kDNSServiceClass_IN),
callback,
handlerPointer
replyHandlerPointer
)

// Check if query completed successfully
guard _code == kDNSServiceErr_NoError else {
DNSSD.deallocatePointers(serviceRefPointer: serviceRefPointer, replyHandlerPointer: replyHandlerPointer)
return continuation.finish(throwing: AsyncDNSResolver.Error(dnssdCode: _code))
}

// Read reply from the socket (blocking) then call reply handler
DNSServiceProcessResult(serviceRefPtr.pointee)
DNSServiceRefDeallocate(serviceRefPtr.pointee)
let serviceSockFD = DNSServiceRefSockFD(serviceRefPointer.pointee)
guard serviceSockFD != -1 else {
DNSSD.deallocatePointers(serviceRefPointer: serviceRefPointer, replyHandlerPointer: replyHandlerPointer)
return continuation.finish(throwing: AsyncDNSResolver.Error(code: .internalError, message: "Failed to access the DNSSD service socket"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aren't we leaking the serviceRefPointer and replyHandlerPointer in this branch?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed the code so that the deallocates are also called in this branch and the other error branch just above.

}

let readSource = DispatchSource.makeReadSource(fileDescriptor: serviceSockFD)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we be passing our own queue here rather than using the default?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't this file descriptor leaked?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I close it now in the cancellation handler

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you'll need .setCancelHandler here and manage the resources from there.

DispatchSources that use file descriptors and don't use setCancelHandler are guaranteed to be incorrect. Also see https://developer.apple.com/documentation/dispatch/1385648-dispatch_source_set_cancel_handl?language=objc#discussion

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, thanks for the feedback! I implemented it in the latest commit

readSource.setEventHandler {
// Read reply from the socket (blocking) then call reply handler
DNSServiceProcessResult(serviceRefPointer.pointee)

readSource.cancel()
continuation.finish()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should cancel the read source

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied in latest commit

}

readSource.setCancelHandler {
DNSSD.deallocatePointers(serviceRefPointer: serviceRefPointer, replyHandlerPointer: replyHandlerPointer)
close(serviceSockFD)
}
readSource.resume()

// Streaming done
continuation.finish()
continuation.onTermination = { _ in
readSource.cancel()
}
}

// Build reply using records received
Expand All @@ -173,6 +180,12 @@ struct DNSSD {

return try replyHandler.generateReply(records: records)
}

private static func deallocatePointers(serviceRefPointer: UnsafeMutablePointer<DNSServiceRef?>, replyHandlerPointer: UnsafeMutablePointer<DNSSD.QueryReplyHandler>) {
DNSServiceRefDeallocate(serviceRefPointer.pointee)
serviceRefPointer.deallocate()
replyHandlerPointer.deallocate()
}
}

// MARK: - dnssd query reply handler
Expand Down