@@ -556,9 +556,9 @@ import Synchronization
556556 unsafe XCTAssertFalse( monitor. hasActiveRefreshTimer)
557557 }
558558
559- // MARK: - Refresh Skip / Register
559+ // MARK: - Refresh Teardown / Re-register
560560
561- func testRefreshSkipsWhenDeviceCountUnchanged ( ) {
561+ func testRefreshTearsDownOldHandlesBeforeRegistering ( ) {
562562 let enumerator = unsafe MockDeviceEnumerator( )
563563 let device = unsafe makeFakeDevice( )
564564 defer { unsafe device. deallocate ( ) }
@@ -569,12 +569,62 @@ import Synchronization
569569 defer { unsafe monitor. stop ( ) }
570570 unsafe monitor. start ( )
571571
572- let callsBefore = unsafe enum erator. registeredCallbackDevices . count
572+ // After start: 1 register, 1 start
573+ unsafe XCTAssertEqual( enumerator. registeredCallbackDevices. count, 1 )
574+
575+ // Refresh should unregister the old handle, then re-register it
573576 unsafe monitor. refreshConnectedDevices ( )
574- let callsAfter = unsafe enum erator. registeredCallbackDevices . count
575577
576- XCTAssertEqual ( callsBefore, callsAfter,
577- " Refresh should skip registration when device count is unchanged " )
578+ unsafe XCTAssertTrue( enumerator. unregisteredCallbackDevices. contains ( device) ,
579+ " Old handle should be unregistered during refresh " )
580+ // Total registrations: 1 (start) + 1 (refresh re-register)
581+ unsafe XCTAssertEqual( enumerator. registeredCallbackDevices. count, 2 )
582+ }
583+
584+ func testRefreshWithUnstablePointersDoesNotLeakCallbacks( ) {
585+ // Simulates the core pointer-instability bug:
586+ // MTDeviceCreateList() returns a different pointer for the same
587+ // physical device on each call. Without the teardown step, this
588+ // would orphan the old callback and register a duplicate.
589+ let enumerator = unsafe MockDeviceEnumerator( )
590+ let ptr1 = unsafe makeFakeDevice( )
591+ let ptr2 = unsafe makeFakeDevice( )
592+ let ptr3 = unsafe makeFakeDevice( )
593+ defer {
594+ unsafe ptr1. deallocate ( )
595+ unsafe ptr2 . deallocate ( )
596+ unsafe ptr3 . deallocate ( )
597+ }
598+ unsafe enumerator. devices = [ ptr1]
599+ unsafe enumerator. defaultDevice = ptr1
600+
601+ let monitor = unsafe makeMonitor( enumerator: enumerator)
602+ defer { unsafe monitor. stop ( ) }
603+ unsafe monitor. start ( )
604+
605+ // Simulate framework returning different pointer for same device
606+ unsafe enumerator. devices = [ ptr2]
607+ unsafe enumerator. defaultDevice = ptr2
608+ unsafe monitor. refreshConnectedDevices ( )
609+
610+ // ptr1 must have been torn down
611+ unsafe XCTAssertTrue( enumerator. unregisteredCallbackDevices. contains ( ptr1) ,
612+ " Old pointer should have its callback unregistered " )
613+ unsafe XCTAssertTrue( enumerator. stoppedDevices. contains ( ptr1) ,
614+ " Old pointer should be stopped " )
615+
616+ // Do it again with a third pointer
617+ unsafe enumerator. devices = [ ptr3]
618+ unsafe enumerator. defaultDevice = ptr3
619+ unsafe monitor. refreshConnectedDevices ( )
620+
621+ unsafe XCTAssertTrue( enumerator. unregisteredCallbackDevices. contains ( ptr2) )
622+ unsafe XCTAssertTrue( enumerator. stoppedDevices. contains ( ptr2) )
623+
624+ // Total unregisters should match: ptr1, ptr2 (not ptr3 — still active)
625+ let unregCount = unsafe enum erator. unregisteredCallbackDevices . count
626+ XCTAssertEqual ( unregCount, 2 ,
627+ " Exactly the old handles should be unregistered, no leaks " )
578628 }
579629
580630 func testRefreshRegistersWhenDeviceCountIncreases( ) {
@@ -591,12 +641,12 @@ import Synchronization
591641
592642 // Simulate connecting a second device
593643 unsafe enumerator. devices = [ device1, device2]
594- let callsBefore = unsafe enum erator. registeredCallbackDevices . count
595644 unsafe monitor. refreshConnectedDevices ( )
596- let callsAfter = unsafe enum erator. registeredCallbackDevices . count
597645
598- XCTAssertGreaterThan ( callsAfter, callsBefore,
599- " Refresh should register devices when count increases " )
646+ // Both devices should have been registered in the refresh pass
647+ unsafe XCTAssertEqual( monitor. lastKnownDeviceCount, 2 )
648+ unsafe XCTAssertTrue( enumerator. registeredCallbackDevices. contains ( device2) ,
649+ " Newly connected device should be registered " )
600650 }
601651
602652 func testRefreshHandlesDeviceDisconnect( ) {
@@ -618,6 +668,9 @@ import Synchronization
618668
619669 unsafe XCTAssertEqual( monitor. lastKnownDeviceCount, 1 ,
620670 " Count should update after device disconnect " )
671+ // device2 should have been torn down
672+ unsafe XCTAssertTrue( enumerator. unregisteredCallbackDevices. contains ( device2) )
673+ unsafe XCTAssertTrue( enumerator. stoppedDevices. contains ( device2) )
621674 }
622675
623676 func testRefreshUpdatesLastKnownDeviceCount( ) {
0 commit comments