// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. #pragma once #if !defined(RXCPP_RX_SUBSCRIPTION_HPP) #define RXCPP_RX_SUBSCRIPTION_HPP #include "rx-includes.hpp" namespace rxcpp { namespace detail { template struct is_unsubscribe_function { struct not_void {}; template static auto check(int) -> decltype((*(CF*)nullptr)()); template static not_void check(...); static const bool value = std::is_same>(0)), void>::value; }; } struct tag_subscription {}; struct subscription_base {typedef tag_subscription subscription_tag;}; template class is_subscription { template static typename C::subscription_tag* check(int); template static void check(...); public: static const bool value = std::is_convertible>(0)), tag_subscription*>::value; }; template class static_subscription { typedef rxu::decay_t unsubscribe_call_type; unsubscribe_call_type unsubscribe_call; static_subscription() { } public: static_subscription(const static_subscription& o) : unsubscribe_call(o.unsubscribe_call) { } static_subscription(static_subscription&& o) : unsubscribe_call(std::move(o.unsubscribe_call)) { } static_subscription(unsubscribe_call_type s) : unsubscribe_call(std::move(s)) { } void unsubscribe() const { unsubscribe_call(); } }; class subscription : public subscription_base { class base_subscription_state : public std::enable_shared_from_this { base_subscription_state(); public: explicit base_subscription_state(bool initial) : issubscribed(initial) { } virtual ~base_subscription_state() {} virtual void unsubscribe() { } std::atomic issubscribed; }; public: typedef std::weak_ptr weak_state_type; private: template struct subscription_state : public base_subscription_state { typedef rxu::decay_t inner_t; subscription_state(inner_t i) : base_subscription_state(true) , inner(std::move(i)) { } virtual void unsubscribe() { if (issubscribed.exchange(false)) { trace_activity().unsubscribe_enter(*this); inner.unsubscribe(); trace_activity().unsubscribe_return(*this); } } inner_t inner; }; protected: std::shared_ptr state; friend bool operator<(const subscription&, const subscription&); friend bool operator==(const subscription&, const subscription&); private: subscription(weak_state_type w) : state(w.lock()) { if (!state) { std::terminate(); } } explicit subscription(std::shared_ptr s) : state(std::move(s)) { if (!state) { std::terminate(); } } public: subscription() : state(std::make_shared(false)) { if (!state) { std::terminate(); } } template explicit subscription(U u, typename std::enable_if::value, void**>::type = nullptr) : state(std::make_shared>(std::move(u))) { if (!state) { std::terminate(); } } template explicit subscription(U u, typename std::enable_if::value && is_subscription::value, void**>::type = nullptr) // intentionally slice : state(std::move((*static_cast(&u)).state)) { if (!state) { std::terminate(); } } subscription(const subscription& o) : state(o.state) { if (!state) { std::terminate(); } } subscription(subscription&& o) : state(std::move(o.state)) { if (!state) { std::terminate(); } } subscription& operator=(subscription o) { state = std::move(o.state); return *this; } bool is_subscribed() const { if (!state) { std::terminate(); } return state->issubscribed; } void unsubscribe() const { if (!state) { std::terminate(); } auto keepAlive = state; state->unsubscribe(); } weak_state_type get_weak() { return state; } // Atomically promote weak subscription to strong. // Calls std::terminate if w has already expired. static subscription lock(weak_state_type w) { return subscription(w); } // Atomically try to promote weak subscription to strong. // Returns an empty maybe<> if w has already expired. static rxu::maybe maybe_lock(weak_state_type w) { auto strong_subscription = w.lock(); if (!strong_subscription) { return rxu::detail::maybe{}; } else { return rxu::detail::maybe{subscription{std::move(strong_subscription)}}; } } }; inline bool operator<(const subscription& lhs, const subscription& rhs) { return lhs.state < rhs.state; } inline bool operator==(const subscription& lhs, const subscription& rhs) { return lhs.state == rhs.state; } inline bool operator!=(const subscription& lhs, const subscription& rhs) { return !(lhs == rhs); } inline auto make_subscription() -> subscription { return subscription(); } template auto make_subscription(I&& i) -> typename std::enable_if::value && !detail::is_unsubscribe_function::value, subscription>::type { return subscription(std::forward(i)); } template auto make_subscription(Unsubscribe&& u) -> typename std::enable_if::value, subscription>::type { return subscription(static_subscription(std::forward(u))); } class composite_subscription; namespace detail { struct tag_composite_subscription_empty {}; class composite_subscription_inner { private: typedef subscription::weak_state_type weak_subscription; struct composite_subscription_state : public std::enable_shared_from_this { // invariant: cannot access this data without the lock held. std::set subscriptions; // double checked locking: // issubscribed must be loaded again after each lock acquisition. // invariant: // never call subscription::unsubscribe with lock held. std::mutex lock; // invariant: transitions from 'true' to 'false' exactly once, at any time. std::atomic issubscribed; ~composite_subscription_state() { std::unique_lock guard(lock); subscriptions.clear(); } composite_subscription_state() : issubscribed(true) { } composite_subscription_state(tag_composite_subscription_empty) : issubscribed(false) { } // Atomically add 's' to the set of subscriptions. // // If unsubscribe() has already occurred, this immediately // calls s.unsubscribe(). // // cs.unsubscribe() [must] happens-before s.unsubscribe() // // Due to the un-atomic nature of calling 's.unsubscribe()', // it is possible to observe the unintuitive // add(s)=>s.unsubscribe() prior // to any of the unsubscribe()=>sN.unsubscribe(). inline weak_subscription add(subscription s) { if (!issubscribed) { // load.acq [seq_cst] s.unsubscribe(); } else if (s.is_subscribed()) { std::unique_lock guard(lock); if (!issubscribed) { // load.acq [seq_cst] // unsubscribe was called concurrently. guard.unlock(); // invariant: do not call unsubscribe with lock held. s.unsubscribe(); } else { subscriptions.insert(s); } } return s.get_weak(); } // Atomically remove 'w' from the set of subscriptions. // // This does nothing if 'w' was already previously removed, // or refers to an expired value. inline void remove(weak_subscription w) { if (issubscribed) { // load.acq [seq_cst] rxu::maybe maybe_subscription = subscription::maybe_lock(w); if (maybe_subscription.empty()) { // Do nothing if the subscription has already expired. return; } std::unique_lock guard(lock); // invariant: subscriptions must be accessed under the lock. if (issubscribed) { // load.acq [seq_cst] subscription& s = maybe_subscription.get(); subscriptions.erase(std::move(s)); } // else unsubscribe() was called concurrently; this becomes a no-op. } } // Atomically clear all subscriptions that were observably added // (and not subsequently observably removed). // // Un-atomically call unsubscribe on those subscriptions. // // forall subscriptions in {add(s1),add(s2),...} // - {remove(s3), remove(s4), ...}: // cs.unsubscribe() || cs.clear() happens before s.unsubscribe() // // cs.unsubscribe() observed-before cs.clear ==> do nothing. inline void clear() { if (issubscribed) { // load.acq [seq_cst] std::unique_lock guard(lock); if (!issubscribed) { // load.acq [seq_cst] // unsubscribe was called concurrently. return; } std::set v(std::move(subscriptions)); // invariant: do not call unsubscribe with lock held. guard.unlock(); std::for_each(v.begin(), v.end(), [](const subscription& s) { s.unsubscribe(); }); } } // Atomically clear all subscriptions that were observably added // (and not subsequently observably removed). // // Un-atomically call unsubscribe on those subscriptions. // // Switches to an 'unsubscribed' state, all subsequent // adds are immediately unsubscribed. // // cs.unsubscribe() [must] happens-before // cs.add(s) ==> s.unsubscribe() // // forall subscriptions in {add(s1),add(s2),...} // - {remove(s3), remove(s4), ...}: // cs.unsubscribe() || cs.clear() happens before s.unsubscribe() inline void unsubscribe() { if (issubscribed.exchange(false)) { // cas.acq_rel [seq_cst] std::unique_lock guard(lock); // is_subscribed can only transition to 'false' once, // does not need an extra atomic access here. std::set v(std::move(subscriptions)); // invariant: do not call unsubscribe with lock held. guard.unlock(); std::for_each(v.begin(), v.end(), [](const subscription& s) { s.unsubscribe(); }); } } }; public: typedef std::shared_ptr shared_state_type; protected: mutable shared_state_type state; public: composite_subscription_inner() : state(std::make_shared()) { } composite_subscription_inner(tag_composite_subscription_empty et) : state(std::make_shared(et)) { } composite_subscription_inner(const composite_subscription_inner& o) : state(o.state) { if (!state) { std::terminate(); } } composite_subscription_inner(composite_subscription_inner&& o) : state(std::move(o.state)) { if (!state) { std::terminate(); } } composite_subscription_inner& operator=(composite_subscription_inner o) { state = std::move(o.state); if (!state) { std::terminate(); } return *this; } inline weak_subscription add(subscription s) const { if (!state) { std::terminate(); } return state->add(std::move(s)); } inline void remove(weak_subscription w) const { if (!state) { std::terminate(); } state->remove(std::move(w)); } inline void clear() const { if (!state) { std::terminate(); } state->clear(); } inline void unsubscribe() { if (!state) { std::terminate(); } state->unsubscribe(); } }; inline composite_subscription shared_empty(); } /*! \brief controls lifetime for scheduler::schedule and observable::subscribe. \ingroup group-core */ class composite_subscription : protected detail::composite_subscription_inner , public subscription { typedef detail::composite_subscription_inner inner_type; public: typedef subscription::weak_state_type weak_subscription; composite_subscription(detail::tag_composite_subscription_empty et) : inner_type(et) , subscription() // use empty base { } public: composite_subscription() : inner_type() , subscription(*static_cast(this)) { } composite_subscription(const composite_subscription& o) : inner_type(o) , subscription(static_cast(o)) { } composite_subscription(composite_subscription&& o) : inner_type(std::move(o)) , subscription(std::move(static_cast(o))) { } composite_subscription& operator=(composite_subscription o) { inner_type::operator=(std::move(o)); subscription::operator=(std::move(*static_cast(&o))); return *this; } static inline composite_subscription empty() { return detail::shared_empty(); } using subscription::is_subscribed; using subscription::unsubscribe; using inner_type::clear; inline weak_subscription add(subscription s) const { if (s == static_cast(*this)) { // do not nest the same subscription std::terminate(); //return s.get_weak(); } auto that = this->subscription::state.get(); trace_activity().subscription_add_enter(*that, s); auto w = inner_type::add(std::move(s)); trace_activity().subscription_add_return(*that); return w; } template auto add(F f) const -> typename std::enable_if::value, weak_subscription>::type { return add(make_subscription(std::move(f))); } inline void remove(weak_subscription w) const { auto that = this->subscription::state.get(); trace_activity().subscription_remove_enter(*that, w); inner_type::remove(w); trace_activity().subscription_remove_return(*that); } }; inline bool operator<(const composite_subscription& lhs, const composite_subscription& rhs) { return static_cast(lhs) < static_cast(rhs); } inline bool operator==(const composite_subscription& lhs, const composite_subscription& rhs) { return static_cast(lhs) == static_cast(rhs); } inline bool operator!=(const composite_subscription& lhs, const composite_subscription& rhs) { return !(lhs == rhs); } namespace detail { inline composite_subscription shared_empty() { static composite_subscription shared_empty = composite_subscription(tag_composite_subscription_empty()); return shared_empty; } } template class resource : public subscription_base { public: typedef typename composite_subscription::weak_subscription weak_subscription; resource() : lifetime(composite_subscription()) , value(std::make_shared>()) { } explicit resource(T t, composite_subscription cs = composite_subscription()) : lifetime(std::move(cs)) , value(std::make_shared>(rxu::detail::maybe(std::move(t)))) { auto localValue = value; lifetime.add( [localValue](){ localValue->reset(); } ); } T& get() { return value.get()->get(); } composite_subscription& get_subscription() { return lifetime; } bool is_subscribed() const { return lifetime.is_subscribed(); } weak_subscription add(subscription s) const { return lifetime.add(std::move(s)); } template auto add(F f) const -> typename std::enable_if::value, weak_subscription>::type { return lifetime.add(make_subscription(std::move(f))); } void remove(weak_subscription w) const { return lifetime.remove(std::move(w)); } void clear() const { return lifetime.clear(); } void unsubscribe() const { return lifetime.unsubscribe(); } protected: composite_subscription lifetime; std::shared_ptr> value; }; } #endif