Fix reentrancy bugs in Subscribers.Assign
This commit is contained in:
committed by
Sergej Jaskiewicz
parent
adcee8c14d
commit
925bee4af9
@@ -112,8 +112,17 @@ extension Subscribers {
|
||||
lock.assertOwner()
|
||||
#endif
|
||||
status = .terminal
|
||||
object = nil
|
||||
lock.unlock()
|
||||
|
||||
// We MUST release the object AFTER unlocking the lock,
|
||||
// since releasing it may trigger execution of arbitrary code,
|
||||
// for example, if the object has a deinit.
|
||||
// When the object deallocates, its deinit is called, and holding
|
||||
// the lock at that moment can lead to deadlocks.
|
||||
|
||||
withExtendedLifetime(object) {
|
||||
object = nil
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,3 +35,10 @@ final class AutomaticallyFinish<Output, Failure: Error> {
|
||||
receiveValue: receiveValue)
|
||||
}
|
||||
}
|
||||
|
||||
extension AutomaticallyFinish where Failure == Never {
|
||||
func assign<Root>(to keyPath: ReferenceWritableKeyPath<Root, Output>,
|
||||
on object: Root) -> AnyCancellable {
|
||||
return publisher.assign(to: keyPath, on: object)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,4 +137,40 @@ final class AssignTests: XCTestCase {
|
||||
publisher.send(100)
|
||||
XCTAssertEqual(object.value, 42)
|
||||
}
|
||||
|
||||
func testReceiveCompletionWhileCancelling() {
|
||||
let cancellable: AnyCancellable
|
||||
|
||||
do {
|
||||
let object = ObjectToModify()
|
||||
cancellable = object.autofinish.assign(to: \.value, on: object)
|
||||
}
|
||||
|
||||
// autofinish is deallocated here, a completion is sent to the sink
|
||||
cancellable.cancel()
|
||||
}
|
||||
|
||||
func testReceiveCompletionWhileCompleting() {
|
||||
let cancellable: AnyCancellable
|
||||
|
||||
let finish: () -> Void
|
||||
|
||||
do {
|
||||
let object = ObjectToModify()
|
||||
cancellable = object.autofinish.assign(to: \.value, on: object)
|
||||
|
||||
let underlyingPublisher = object.autofinish.publisher
|
||||
|
||||
finish = { underlyingPublisher.send(completion: .finished) }
|
||||
}
|
||||
|
||||
finish() // autofinish is deallocated here, a completion is sent to the sink
|
||||
|
||||
cancellable.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
final class ObjectToModify {
|
||||
let autofinish = AutomaticallyFinish<Int, Never>()
|
||||
var value = 0
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user