Fix a memory leak in DispatchData.withUnsafeBytes by ahoppen · Pull Request #850 · swiftlang/swift-corelibs-libdispatch (original) (raw)

DispatchData.withUnsafeBytes created a new dispatch_data_t by calling dispatch_data_create_map. I assume that the intention was that this memory was freed when data is destroyed, based on the presence of _fixLifetime(data) but data was just a plain dispatch_data_t C struct, that doesn’t have any cleanup operations associated with it when destroyed.

To fix the leak, wrap the dispatch_data_t in a DispatchData, which takes over the ownership of the dispatch_data_t and releases it when data gets destroyed.

Alternatively, _fixLifetime could have been replaced by _swift_dispatch_release(unsafeBitCast(data, to: dispatch_object_t.self)) but I think using DispatchData is the cleaner solution.


For future reference, a minimal reproducer for this issue was

import Dispatch import Foundation

func run() { let queue = DispatchQueue(label: "jsonrpc-queue", qos: .userInitiated)

let receiveIO = DispatchIO( type: .stream, fileDescriptor: FileHandle.standardInput.fileDescriptor, queue: queue, cleanupHandler: { _ in } )

receiveIO.setLimit(lowWater: 1) receiveIO.setLimit(highWater: Int.max)

receiveIO.read(offset: 0, length: Int.max, queue: queue) { done, data, errorCode in guard let data, !data.isEmpty else { return }

data.withUnsafeBytes { (pointer: UnsafePointer<UInt8>) in }

} }

try await Task.sleep(for: .seconds(2)) print("start") run() try await Task.sleep(for: .seconds(10))

Then run it as

seq 1 100000000 | .build/debug/repro

and attach heaptrack to it during the initial 2s waiting period by running

heaptrack --pid -$(pidof repro)

Heaptrack will list multiple GB worth of leaks without this fix.