Fix CallbackSource registrations outliving their sources
Callback-based sound sources are kept alive in a static registry while the C side may still invoke their trampoline, but nothing ever removed them: every callback source leaked its wrapper and closure permanently, and a source retained only by a transient Channel.default wrapper relied on that leak for safety. The registry is now the single source of truth and is purged on Sound.removeSource, Channel.removeSource, and when an owned channel is freed (its sources can no longer be pulled). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -78,7 +78,9 @@ extension Sound {
|
||||
/// Removes a source from its channel.
|
||||
@discardableResult
|
||||
public static func removeSource(_ source: Source) -> Bool {
|
||||
snd.removeSource.unsafelyUnwrapped(source.pointer) != 0
|
||||
let removed = snd.removeSource.unsafelyUnwrapped(source.pointer) != 0
|
||||
CallbackSource.release(source)
|
||||
return removed
|
||||
}
|
||||
|
||||
/// Sets a callback that records microphone input. Return `false` from the
|
||||
@@ -196,6 +198,11 @@ extension Sound {
|
||||
deinit {
|
||||
if isOwned {
|
||||
Channel.api.freeChannel.unsafelyUnwrapped(pointer)
|
||||
// The freed channel no longer pulls its callback sources, so
|
||||
// their trampoline registrations can be released too.
|
||||
for source in retainedSources where source is CallbackSource {
|
||||
CallbackSource.release(source)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,6 +247,7 @@ extension Sound {
|
||||
public func removeSource(_ source: Source) -> Bool {
|
||||
let removed = Channel.api.removeSource.unsafelyUnwrapped(pointer, source.pointer) != 0
|
||||
retainedSources.removeAll { $0 === source }
|
||||
CallbackSource.release(source)
|
||||
return removed
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user