-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Fix] Update key generation for adding handlers #13
base: master
Are you sure you want to change the base?
[Fix] Update key generation for adding handlers #13
Conversation
Previous strategy was failing with Swift 5. It seems that Dictionary.keys is somehow not atomic. I have not checked the diffs to get to the root cause, however this now passes tests and I added a performance test just to check that it does not add any overhead.
Previous solution did not take into account disposing subscriptions. This fixes this, using `Int.random` for key generation. Also added unsubscribing (disposing) to the performance test.
Update: I quickly saw that my first solution -
let receiver = Receiver<Int>.make()
let d1 = receiver.listen(h1) // 1
let d2 = receiver.listen(h2) // 2
d1.dispose() // 3
d3 = receiver.listen(h3) // 4
// handler mutations
// 1 - adding h1
_key = _handlers.count // _key = 0
_handlers[_key] = handle
// _handlers = [ 0: h1 ]
// 2 - adding h2
_key = _handlers.count // _key = 1
_handlers[_key] = handle
// _handlers = [ 0: h1 , 1: h2 ]
// 3 - removing h1
_handlers[_key] = nil // _key = 0 as obtained from d1
// _handlers = [ 1: h2 ]
// 4 - adding h3
_key = _handlers.count // _key = 1
_handlers[_key] = handle
// _handlers = [ 1: h3 ] ❌
// expected [ 1: h2, 2: h3 ] or anything that does not overwrite h2 It turns out this is essentially the same reason for the failure of the original implementation (i.e generating a key that already exists), it was just less obvious and I falsely assumed was some kind of race issue (which I thought was odd since it was done inside an atomic apply) Original implementation (current master): (failure case) let receiver = Receiver<Int>.make()
let d1 = receiver.listen(h1) // 1
let d2 = receiver.listen(h2) // 2
d1.dispose() // 3
d3 = receiver.listen(h3) // 4
// handler mutations
// 1 - adding h1
_key = (_handlers.keys.map { $0.hashValue }.max() ?? -1) + 1
// _key == ([] ->(hashValue)-> [] -(max)-> nil ?? -1) + 1 == 0
_handlers[_key] = handle
// _handlers = [ 0: h1 ]
// 2 - adding h2 // e.g 0.hashValue == 100
_key = (_handlers.keys.map { $0.hashValue }.max() ?? -1) + 1
// _key == ([0] ->(hashValue)-> [100] -(max)-> 100) + 1 == 101
_handlers[_key] = handle
// _handlers = [ 0: h1 , 101: h2 ]
// 3 - removing h1 - unlike `be8ee36` this does not have any affect on correctness
_handlers[_key] = nil // _key = 0 as obtained from d1
// _handlers = [ 101: h2 ]
// 4 - adding h3 // e.g 101.hashValue == 30
_key = (_handlers.keys.map { $0.hashValue }.max() ?? -1) + 1
// [0,101] ->(hashValue)-> [100, 33] -(max)-> 100
// _key == ([0,101] ->(hashValue)-> [100,33] -(max)-> 100) + 1 == 101
_handlers[_key] = handle
// _handlers = [ 101: h3 ] ❌
// expected [ 101: h2, `X`: h3 ] for some X != 101 The new Implementation simply uses _key = Int.random(in: Int.min...Int.max)
while _handlers[_key] != nil {
_key = Int.random(in: Int.min...Int.max)
}
_handlers[_key] = handle As mentioned in this comment in #11 by @kevincador (also apologies for stepping on your toes a bit here, I should have reached out before proposing a solution)... Thanks for reading 😊 |
Nice @Daniel1of1 ! Note: I didn't investigate it yet but iOS 13 will introduce a new framework (https://developer.apple.com/documentation/combine). I don't know if it will replace Receiver (I'd like to have @RuiAAPeres input on this one 😇). |
Oh wow, thanks for all this work guys! I need to see if this is working as intended. @kevincador are you using Receiver in production? |
Yes, I'm using Receiver in production since 1.0 of Rippple (https://itunes.apple.com/us/app/rippple-tv-movie-comments/id1309894528) launched in early 2018. |
@kevincador regarding Combine, it doesn't support iOS 12. It's slightly more complex than Receiver as well: it has more functionality. |
@Daniel1of1 I am checking how ReactiveSwift does it, and I think it would provide a more robust solution: https://github.com/ReactiveCocoa/ReactiveSwift/blob/master/Sources/Bag.swift#L45 What do you think? |
@RuiAAPeres This seems perfectly suitable, I can essentially just lift it from there. And I'll update accordingly. Do you have a preference on whether to wrap the key in a |
I am ok with either. |
@RuiAAPeres done |
Previous strategy was failing with newer swift versions, this is the same issue as #11.
[Edit]
It seems thatThis is not true, see below [/Edit]Dictionary.keys
is somehow not atomic.I have not checked the diffs to get to the root cause, however this now passes tests and I added
a performance test just to check that it does not add any overhead.