Fix premature garbage collection of subscriber#1008
Fix premature garbage collection of subscriber#1008samueltardieu wants to merge 1 commit intoReactiveX:masterfrom samueltardieu:android-bindings
Conversation
Keeping a weak binding onto the subscriber makes it possible to prematurely get the subscriber garbage collected if there are no other references to it. Here, we chose to pass around the Android component to which the subscriber is tied in form of a pair.
|
I would like @mttkay to review this as I'm not very involved in the Android side so am not the right person for this. |
|
RxJava-pull-requests #935 FAILURE |
|
The failure from CloudBees is about rx.operators.OperatorPivotTest.testConcurrencyAndSerialization which is untouched and doesn't used any modified code. Please ignore it. |
|
sorry for not responding, too much going on right now, I hope I'll get to |
|
The tests pass, so the observable is correctly unsubscribed when it becomes invalid or unreferenced. I've run a modified version of cgeo in the field using this patch and haven't noticed any problem. |
|
So, I finally had a chance to look into this. I see your idea with this, but this implementation does not work either (as in, it regresses on what this operator sets out to fix: not leaking context). It leaks the activity in every rotation change, and it leaks it too when backing out of the Activity while the sequence is still in progress. At a quick glance, this is simply because of the fact that it's keeping a strong reference to the subscriber, and the subscriber is keeping a strong reference to the Activity (since it's an inner class), so the weak reference is never cleared. In essence, this operator now does nothing :-) You could as well have simply subscribed the activity to it without using it and would wind up in the same situation. Unfortunately, it's very difficult to unit test this, but it's very easy to verify in practice: create a sequence that outlives your activity, use this operator, turn StrictMode on and send it through a few config changes. Even when hitting the back button, the subscriber is not released and keeps a reference to the activity and keeps emitting notifications to it. In fact, I've created the android-samples module as a sandbox for such things. Frankly, at SoundCloud we've stopped using any of these operators. We simply unsubscribe manually at the appropriate time (again, have a look at the samples module, it contains examples using managed subscriptions that achieve the same goals.) I've went back and forth with these implementations here and no one has found a solution that covers everyone's needs. That said, I think unless someone is confident to have found a solution that works for everyone, my vote is to remove of all these operators from the core library and simply encourage people to manage their subscription references themselves. (Although I remember @benjchristensen saying that this, too, is discouraged outside an operator implementation.) |
|
For the record, here's the code I used (thrown together quickly:) Activity: Output after 7 rotation changes: |
|
I just thought of another problem (don't we love solving problems!) Binding a context reference to an Rx notification (in this case through That's a problem, since Android stops processing the message loop when going through a rotation change: it literally ignores your messages that queue up until after For more information, Square has written an article about this a while ago: |
|
I spent the afternoon investigating other approaches using
One other possibility could be to leverage |
|
I finally removed my calls to |
|
Thanks @samueltardieu for your feedback. Would you agree to recommend reverting I just spent some more time inspecting in which order Android processes these messages in different scenarios and found some interesting patterns (unfortunately, all this is undocumented and I had to resort to experimentation/observation, logging, and reading source code...) I found that the following seems to be true: For Activities:
For Fragments:
So, my suggestion is:
the two helper methods still add value, even if they force you to manage subscriptions, since they stop messages from being forwarded in the above mentioned cases, but it sort of brings us back to where we were a few weeks ago... |
|
Sorry just to clarify: I meant to say we should keep the I will also add samples and more documentation to clarify these shortcomings. |
|
Please have a look at #1021 We can understand this as a middle ground between the deprecated
I think this is far as we can get with this for now. I personally don't think it's a big deal to have manual subscription management, I actually like that level of control. You often need it anyway (sometimes you want to always stop listening when the component is paused, but other times only when it gets destroyed.) |
|
I like your PR (I didn't try it, I just read it). I agree that manual subscription is more appropriate. I often have a "resumeSubscriptions" in my components as well as a "createSubscriptions", and I add subscriptions there (respectively in |
Keeping a weak binding onto the subscriber makes it possible to prematurely get the subscriber garbage collected if there are no other references to it.
Here, we chose to pass around the component to which the subscriber is tied in form of a pair (
BoundPayload). This allows the subscribers to get a reference on the (guaranteed non-collected) target without keeping it in the closure.This introduces an interface change, but the current implementation is wrong and should never be used (see issues #979 and #1006).
An example usage can be seen at https://github.com/samueltardieu/cgeo/blob/bound-payload/main/src/cgeo/geocaching/PocketQueryList.java#L50 where
selectFromPocketQueriesreferences the target activity throughpocketQueryLists.targetin the subscriber.cc @mttkay