So the idea is to maintain a cache of shared Observables - accessible by a key - which if not present will produce one using the given factory.
The underlying shared Observable is accessed via an Observable created on each invocation of get - this is in order to be able to maintain ownership of the shared Observable and enforce when and how subscribe/unsubscribe is called on it.
When a subscriber is subscribed to an external Observable, then it looks up or created the underlying Observable, and subscribes to it.
When a subscriber is unsubscribed, if there are no more subscriptions on the underlying Observable, then it is removed from the cache.
If the underlying shared Observable terminates, then it is removed from the cache. I believe doOnTerminate is called before the onComplete/onError methods of subscribers, meaning that it will not be possible to subscribe to an Observable which has just terminated, and receive nothing in response.
It's written in Kotlin, but if need be I can port to Java.
I'd appreciate validation that what I've said above is true, and any edge cases I may have missed. Additionally, if there are any suggestions for making this non-blocking, without the chance of duplicating Observables for the same key, or having unmanaged references, then I'd be keen to listen.
package streams
import rx.Observable
import rx.observers.Subscribers.from
import rx.subscriptions.Subscriptions
import java.util.*
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
/**
* Shares a number of underlying observables
* Unsubscribes from and removes the source observable from the cache if there are no binding subscriptions to it,
* or if the source observable terminates
*/
class SharedObservableCache<K, T>(private val factory: (K) -> Observable<T>) {
private val sharedObservables = HashMap<K, Observable<T>>()
private val lock = ReentrantLock()
operator fun get(key: K): Observable<T> {
return Observable.create<T>(
{ subscriber ->
val bindingSubscriber = from(subscriber)
lock.withLock {
getSharedObservable(key).subscribe(bindingSubscriber)
}
subscriber.add(Subscriptions.create(
{
lock.withLock {
bindingSubscriber.unsubscribe()
}
}))
})
}
private fun getSharedObservable(key: K): Observable<T> {
return sharedObservables.getOrPut(key,
{
factory.invoke(key)
// Occurs before calls to onComplete/onError to subscribers
// Removed at this point so as not to allow a subscriber to subscribe to a terminating observable
.doOnTerminate { lock.withLock { sharedObservables.remove(key) } }
// Locked by unsubscription of binding subscriber
.doOnUnsubscribe { sharedObservables.remove(key) }
.share()
})
}
}