Can't use unsafe pointer from second thread?


#1

Hi,

I’m porting some C to Swift and I need to pass a Swift instance through a `void *` (ie, UnsafeMutableRawPointer). It works, in one thread, but when a second thread accesses the exact same pointer, it fails with memory errors like EXC_BAD_ACCESS

The code below could be pasted into an AppDelegate.swift in a new single view iOS project.

Anyone know what’s going on here? The second call to `tryUsingUnsafePointer`, in the new thread, crashes.

Rob

class PointerTest {
    
    let str = "Hello"
    
    init() { print("PointerTest.init") }
    deinit { print("PointerTest.deinit") }
    
    func start() {
        var mSelf = self
        let unsafePtr = UnsafeMutableRawPointer(&mSelf)
        tryUsingUnsafePointer(unsafePtr)
        print("Passing unsafe pointer to another thread: \(unsafePtr)")
        Thread.detachNewThread {
            tryUsingUnsafePointer(unsafePtr)
        }
    }
}

func tryUsingUnsafePointer(_ ptr: UnsafeMutableRawPointer) {
    print("Using unsafe pointer:")
    let typedPtr = ptr.assumingMemoryBound(to: PointerTest.self)
    // let typedPtr = ptr.bindMemory(to: PointerTest.self, capacity: 1)
    print(" typedPtr: \(typedPtr)")
    let obj = typedPtr.pointee
    print(" obj.str: \(obj.str)") // Memory error happening here, or sometimes line above
}

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    var ptrTest: PointerTest?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

        ptrTest = PointerTest()
        ptrTest?.start()

        return true
    }

[...]


(Michel Fortin) #2

The problem is that the main thread is deallocating the object before the other thread has a chance to see it. Ideally you'd pass a managed object reference to the block that runs in the other thread so the reference count does not fall to zero. If you absolutely need to pass it as an unsafe pointer then you must manage the reference count manually, like this:

  let unsafePtr = Unmanaged.passRetained(self).toOpaque()
  let safe = Unmanaged<PointerTest>.fromOpaque(unsafePtr).takeRetainedValue()
  useSafeObject(safe)

Make sure the calls to passRetained and takeRetainedValue are balanced. (If this is a callback that gets called multiple times, use takeRetainedValue only once at the end.)

···

Le 18 juin 2017 à 9:23, Robert Nikander via swift-users <swift-users@swift.org> a écrit :

Hi,

I’m porting some C to Swift and I need to pass a Swift instance through a `void *` (ie, UnsafeMutableRawPointer). It works, in one thread, but when a second thread accesses the exact same pointer, it fails with memory errors like EXC_BAD_ACCESS

The code below could be pasted into an AppDelegate.swift in a new single view iOS project.

Anyone know what’s going on here? The second call to `tryUsingUnsafePointer`, in the new thread, crashes.

Rob

class PointerTest {
    
    let str = "Hello"
    
    init() { print("PointerTest.init") }
    deinit { print("PointerTest.deinit") }
    
    func start() {
        var mSelf = self
        let unsafePtr = UnsafeMutableRawPointer(&mSelf)
        tryUsingUnsafePointer(unsafePtr)
        print("Passing unsafe pointer to another thread: \(unsafePtr)")
        Thread.detachNewThread {
            tryUsingUnsafePointer(unsafePtr)
        }
    }
}

func tryUsingUnsafePointer(_ ptr: UnsafeMutableRawPointer) {
    print("Using unsafe pointer:")
    let typedPtr = ptr.assumingMemoryBound(to: PointerTest.self)
    // let typedPtr = ptr.bindMemory(to: PointerTest.self, capacity: 1)
    print(" typedPtr: \(typedPtr)")
    let obj = typedPtr.pointee
    print(" obj.str: \(obj.str)") // Memory error happening here, or sometimes line above
}

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    var ptrTest: PointerTest?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

        ptrTest = PointerTest()
        ptrTest?.start()

        return true
    }

[...]

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users

--
Michel Fortin
https://michelf.ca


#3

The main thread isn’t deallocating the PointerTest object, because it’s saved in the AppDelegate.

Rob

···

On Jun 18, 2017, at 10:45 AM, Michel Fortin <michel.fortin@michelf.ca> wrote:

The problem is that the main thread is deallocating the object before the other thread has a chance to see it. Ideally you'd pass a managed object reference to the block that runs in the other thread so the reference count does not fall to zero. If you absolutely need to pass it as an unsafe pointer then you must manage the reference count manually, like this:

  let unsafePtr = Unmanaged.passRetained(self).toOpaque()
  let safe = Unmanaged<PointerTest>.fromOpaque(unsafePtr).takeRetainedValue()
  useSafeObject(safe)

Make sure the calls to passRetained and takeRetainedValue are balanced. (If this is a callback that gets called multiple times, use takeRetainedValue only once at the end.)

Le 18 juin 2017 à 9:23, Robert Nikander via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> a écrit :

Hi,

I’m porting some C to Swift and I need to pass a Swift instance through a `void *` (ie, UnsafeMutableRawPointer). It works, in one thread, but when a second thread accesses the exact same pointer, it fails with memory errors like EXC_BAD_ACCESS

The code below could be pasted into an AppDelegate.swift in a new single view iOS project.

Anyone know what’s going on here? The second call to `tryUsingUnsafePointer`, in the new thread, crashes.

Rob

class PointerTest {
    
    let str = "Hello"
    
    init() { print("PointerTest.init") }
    deinit { print("PointerTest.deinit") }
    
    func start() {
        var mSelf = self
        let unsafePtr = UnsafeMutableRawPointer(&mSelf)
        tryUsingUnsafePointer(unsafePtr)
        print("Passing unsafe pointer to another thread: \(unsafePtr)")
        Thread.detachNewThread {
            tryUsingUnsafePointer(unsafePtr)
        }
    }
}

func tryUsingUnsafePointer(_ ptr: UnsafeMutableRawPointer) {
    print("Using unsafe pointer:")
    let typedPtr = ptr.assumingMemoryBound(to: PointerTest.self)
    // let typedPtr = ptr.bindMemory(to: PointerTest.self, capacity: 1)
    print(" typedPtr: \(typedPtr)")
    let obj = typedPtr.pointee
    print(" obj.str: \(obj.str)") // Memory error happening here, or sometimes line above
}

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    var ptrTest: PointerTest?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

        ptrTest = PointerTest()
        ptrTest?.start()

        return true
    }

[...]

_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users

--
Michel Fortin
https://michelf.ca <https://michelf.ca/>


(Michel Fortin) #4

Oh, right, my mistake. I think the problem is that UnsafeMutableRawPointer(&mSelf) creates a pointer to the local variable mSelf on the stack. When mSelf goes out of scope, your raw pointer to an object reference pointer on the stack becomes invalid. (And it could become invalid even earlier with optimisations).

The correct way to pass your pointer is the one I described earlier. You can use passUnretained and takeUnretainedValue if you want to avoid touching the retain count since the AppDelegate will retain the object anyway.

···

Le 18 juin 2017 à 11:21, Robert Nikander via swift-users <swift-users@swift.org> a écrit :

The main thread isn’t deallocating the PointerTest object, because it’s saved in the AppDelegate.

Rob

On Jun 18, 2017, at 10:45 AM, Michel Fortin <michel.fortin@michelf.ca <mailto:michel.fortin@michelf.ca>> wrote:

The problem is that the main thread is deallocating the object before the other thread has a chance to see it. Ideally you'd pass a managed object reference to the block that runs in the other thread so the reference count does not fall to zero. If you absolutely need to pass it as an unsafe pointer then you must manage the reference count manually, like this:

  let unsafePtr = Unmanaged.passRetained(self).toOpaque()
  let safe = Unmanaged<PointerTest>.fromOpaque(unsafePtr).takeRetainedValue()
  useSafeObject(safe)

Make sure the calls to passRetained and takeRetainedValue are balanced. (If this is a callback that gets called multiple times, use takeRetainedValue only once at the end.)

Le 18 juin 2017 à 9:23, Robert Nikander via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> a écrit :

Hi,

I’m porting some C to Swift and I need to pass a Swift instance through a `void *` (ie, UnsafeMutableRawPointer). It works, in one thread, but when a second thread accesses the exact same pointer, it fails with memory errors like EXC_BAD_ACCESS

The code below could be pasted into an AppDelegate.swift in a new single view iOS project.

Anyone know what’s going on here? The second call to `tryUsingUnsafePointer`, in the new thread, crashes.

Rob

class PointerTest {
    
    let str = "Hello"
    
    init() { print("PointerTest.init") }
    deinit { print("PointerTest.deinit") }
    
    func start() {
        var mSelf = self
        let unsafePtr = UnsafeMutableRawPointer(&mSelf)
        tryUsingUnsafePointer(unsafePtr)
        print("Passing unsafe pointer to another thread: \(unsafePtr)")
        Thread.detachNewThread {
            tryUsingUnsafePointer(unsafePtr)
        }
    }
}

func tryUsingUnsafePointer(_ ptr: UnsafeMutableRawPointer) {
    print("Using unsafe pointer:")
    let typedPtr = ptr.assumingMemoryBound(to: PointerTest.self)
    // let typedPtr = ptr.bindMemory(to: PointerTest.self, capacity: 1)
    print(" typedPtr: \(typedPtr)")
    let obj = typedPtr.pointee
    print(" obj.str: \(obj.str)") // Memory error happening here, or sometimes line above
}

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    var ptrTest: PointerTest?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

        ptrTest = PointerTest()
        ptrTest?.start()

        return true
    }

[...]

_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users

--
Michel Fortin
https://michelf.ca <https://michelf.ca/>

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users

--
Michel Fortin
https://michelf.ca