The following code allocates a bunch of big structs in dispatch queues, and it crashes consistently for me when using DispatchQueue.concurrentPerform or NSArray.enumerateObjects, but not when just executing on a background queue.
Is there some queue-specific stack-size limit that can be adjusted?
import Dispatch
import Foundation
import XCTest
class QueueMemoryAllocCrashTest : XCTestCase {
/// Eight is enough
struct OctoThing {
let t1, t2, t3, t4, t5, t6, t7, t8: T
}
/// A 32K block of memory
struct MemoryChunk {
let chunk: OctoThing<octothing<octothing<octothing>>>? = nil // 32,768 bytes
}
/// This function does nothing but waste stack space (491,520 bytes to be specific)
func wasteMemory() {
// any fewer than 15 of these and the test will pass
let _ = MemoryChunk()
let _ = MemoryChunk()
let _ = MemoryChunk()
let _ = MemoryChunk()
let _ = MemoryChunk()
let _ = MemoryChunk()
let _ = MemoryChunk()
let _ = MemoryChunk()
let _ = MemoryChunk()
let _ = MemoryChunk()
let _ = MemoryChunk()
let _ = MemoryChunk()
let _ = MemoryChunk()
let _ = MemoryChunk()
let _ = MemoryChunk()
}
func testWasteOnQueue() {
// this passes without any problems
DispatchQueue.global(qos: .userInteractive).sync(execute: {
wasteMemory()
wasteMemory()
wasteMemory()
})
}
func testWasteWithConcurrentPerform() {
// this crashes with 2 iterations or more with a EXC_BAD_ACCESS
DispatchQueue.concurrentPerform(iterations: 2, execute: { _ in
wasteMemory()
})
}
func testWasteWithEnumerateObjects() {
// this crashes with 17 iterations or more with a EXC_BAD_ACCESS
(Array(1...17) as NSArray).enumerateObjects(options: [.concurrent]) { _, _, _ in
wasteMemory()
}
}
}
Yes, you're exceeding the default stack size limit for secondary threads. The main thread's default stack size is 8MB, but secondary threads are typically created with a 512KB stack limit. https://developer.apple.com/library/archive/qa/qa1419/_index.html
testWasteOnQueue() doesn't have the problem because synchronous dispatch usually runs on the current thread. The others crash beyond certain number of iterations because that's, apparently, the point at which they bother to shunt work to a secondary thread.
You should either use heap-allocated memory instead of putting that much data on the stack or you need to use your own threads, which you can configure with larger stack sizes, instead of GCD.