Fabric: Exposing EventEmitter's ownership model as a shared_ptr

Summary:
As we did in the previous diff, here we implemented `EventEmitter`'s ownership model as a `shared_ptr`. This change fixes problem with leaking `WeakObject`s which happens on hot-reload.

So, in short:
 * `EventTargetWrapper` object owns `jsi::WeakObject` that can be converted to actual `jsi::Object` that represent event target in JavaScript realm;
 * `EventTargetWrapper` and `jsi::WeakObject` objects must be deallocated as soon as native part does not need them anymore;
 * `EventEmitter` objects retain `EventTarget` objects;
 * `EventEmitter` can loose event target object in case if assosiated `ShadowNode` got unmounted (not deallocated); in this case `EventEmitter` is loosing possibility to dispatch event even if some mounting-layer code is still retaining it.

Reviewed By: mdvacca

Differential Revision: D9762755

fbshipit-source-id: 96e989767a32914db9f4627fce51b044c71f257a
This commit is contained in:
Valentin Shergin 2018-09-13 22:56:00 -07:00 коммит произвёл Facebook Github Bot
Родитель a089df3f8b
Коммит c25d5948a5
14 изменённых файлов: 76 добавлений и 71 удалений

Просмотреть файл

@ -80,7 +80,7 @@ public:
* shadow nodes. * shadow nodes.
*/ */
virtual SharedEventEmitter createEventEmitter( virtual SharedEventEmitter createEventEmitter(
const EventTarget &eventTarget, SharedEventTarget eventTarget,
const Tag &tag const Tag &tag
) const = 0; ) const = 0;
}; };

Просмотреть файл

@ -92,10 +92,10 @@ public:
}; };
virtual SharedEventEmitter createEventEmitter( virtual SharedEventEmitter createEventEmitter(
const EventTarget &eventTarget, SharedEventTarget eventTarget,
const Tag &tag const Tag &tag
) const override { ) const override {
return std::make_shared<ConcreteEventEmitter>(eventTarget, tag, eventDispatcher_); return std::make_shared<ConcreteEventEmitter>(std::move(eventTarget), tag, eventDispatcher_);
} }
protected: protected:

Просмотреть файл

@ -18,6 +18,7 @@ namespace react {
class EventDispatcher; class EventDispatcher;
using SharedEventDispatcher = std::shared_ptr<const EventDispatcher>; using SharedEventDispatcher = std::shared_ptr<const EventDispatcher>;
using WeakEventDispatcher = std::weak_ptr<const EventDispatcher>;
/* /*
* Represents event-delivery infrastructure. * Represents event-delivery infrastructure.

Просмотреть файл

@ -31,10 +31,14 @@ std::recursive_mutex &EventEmitter::DispatchMutex() {
return mutex; return mutex;
} }
EventEmitter::EventEmitter(const EventTarget &eventTarget, const Tag &tag, const std::shared_ptr<const EventDispatcher> &eventDispatcher): EventEmitter::EventEmitter(
eventTarget_(eventTarget), SharedEventTarget eventTarget,
Tag tag,
WeakEventDispatcher eventDispatcher
):
eventTarget_(std::move(eventTarget)),
tag_(tag), tag_(tag),
eventDispatcher_(eventDispatcher) {} eventDispatcher_(std::move(eventDispatcher)) {}
void EventEmitter::dispatchEvent( void EventEmitter::dispatchEvent(
const std::string &type, const std::string &type,
@ -73,6 +77,9 @@ void EventEmitter::dispatchEvent(
void EventEmitter::setEnabled(bool enabled) const { void EventEmitter::setEnabled(bool enabled) const {
enabled_ = enabled; enabled_ = enabled;
if (!enabled) {
eventTarget_ = nullptr;
}
} }
bool EventEmitter::getEnabled() const { bool EventEmitter::getEnabled() const {

Просмотреть файл

@ -22,8 +22,16 @@ using SharedEventEmitter = std::shared_ptr<const EventEmitter>;
/* /*
* Base class for all particular typed event handlers. * Base class for all particular typed event handlers.
* Stores `InstanceHandle` identifying a particular component and the pointer * Stores a pointer to `EventTarget` identifying a particular component and
* to `EventDispatcher` which is responsible for delivering the event. * a weak pointer to `EventDispatcher` which is responsible for delivering the event.
*
* Note: Retaining an `EventTarget` does *not* guarantee that actual event target
* exists and/or valid in JavaScript realm. The `EventTarget` retains an `EventTargetWrapper`
* which wraps JavaScript object in `unsafe-unretained` manner. Retaining
* the `EventTarget` *does* indicate that we can use that to get an actual
* JavaScript object from that in the future *ensuring safety beforehand somehow*;
* JSI maintains `WeakObject` object as long as we retain the `EventTarget`.
* All `EventTarget` instances must be deallocated before stopping JavaScript machine.
*/ */
class EventEmitter: class EventEmitter:
public std::enable_shared_from_this<EventEmitter> { public std::enable_shared_from_this<EventEmitter> {
@ -37,7 +45,12 @@ class EventEmitter:
public: public:
static std::recursive_mutex &DispatchMutex(); static std::recursive_mutex &DispatchMutex();
EventEmitter(const EventTarget &eventTarget, const Tag &tag, const std::shared_ptr<const EventDispatcher> &eventDispatcher); EventEmitter(
SharedEventTarget eventTarget,
Tag tag,
WeakEventDispatcher eventDispatcher
);
virtual ~EventEmitter() = default; virtual ~EventEmitter() = default;
/* /*
@ -59,9 +72,9 @@ protected:
) const; ) const;
private: private:
EventTarget eventTarget_; mutable SharedEventTarget eventTarget_;
Tag tag_; Tag tag_;
std::weak_ptr<const EventDispatcher> eventDispatcher_; WeakEventDispatcher eventDispatcher_;
mutable bool enabled_; // Protected by `DispatchMutex`. mutable bool enabled_; // Protected by `DispatchMutex`.
}; };

Просмотреть файл

@ -12,8 +12,8 @@
namespace facebook { namespace facebook {
namespace react { namespace react {
EventQueue::EventQueue(const EventPipe &eventPipe, std::unique_ptr<EventBeat> eventBeat): EventQueue::EventQueue(EventPipe eventPipe, std::unique_ptr<EventBeat> eventBeat):
eventPipe_(eventPipe), eventPipe_(std::move(eventPipe)),
eventBeat_(std::move(eventBeat)) { eventBeat_(std::move(eventBeat)) {
eventBeat_->setBeatCallback(std::bind(&EventQueue::onBeat, this)); eventBeat_->setBeatCallback(std::bind(&EventQueue::onBeat, this));
} }
@ -40,8 +40,9 @@ void EventQueue::onBeat() const {
{ {
std::lock_guard<std::recursive_mutex> lock(EventEmitter::DispatchMutex()); std::lock_guard<std::recursive_mutex> lock(EventEmitter::DispatchMutex());
for (const auto &event : queue) { for (const auto &event : queue) {
auto eventTarget = event.eventTarget.lock();
eventPipe_( eventPipe_(
event.isDispachable() ? event.eventTarget : EmptyEventTarget, eventTarget && event.isDispatchable() ? eventTarget.get() : nullptr,
event.type, event.type,
event.payload event.payload
); );

Просмотреть файл

@ -25,7 +25,7 @@ namespace react {
class EventQueue { class EventQueue {
public: public:
EventQueue(const EventPipe &eventPipe, std::unique_ptr<EventBeat> eventBeat); EventQueue(EventPipe eventPipe, std::unique_ptr<EventBeat> eventBeat);
virtual ~EventQueue() = default; virtual ~EventQueue() = default;
/* /*

Просмотреть файл

@ -11,18 +11,18 @@ namespace facebook {
namespace react { namespace react {
RawEvent::RawEvent( RawEvent::RawEvent(
const std::string &type, std::string type,
const folly::dynamic &payload, folly::dynamic payload,
const EventTarget &eventTarget, WeakEventTarget eventTarget,
const std::function<bool()> &isDispatchable RawEventDispatchable isDispatchable
): ):
type(type), type(std::move(type)),
payload(payload), payload(std::move(payload)),
eventTarget(eventTarget), eventTarget(std::move(eventTarget)),
isDispachable_(isDispatchable) {} isDispatchable_(std::move(isDispatchable)) {}
bool RawEvent::isDispachable() const { bool RawEvent::isDispatchable() const {
return isDispachable_(); return isDispatchable_();
} }
} // namespace react } // namespace react

Просмотреть файл

@ -23,25 +23,25 @@ public:
using RawEventDispatchable = std::function<bool()>; using RawEventDispatchable = std::function<bool()>;
RawEvent( RawEvent(
const std::string &type, std::string type,
const folly::dynamic &payload, folly::dynamic payload,
const EventTarget &eventTarget, WeakEventTarget eventTarget,
const RawEventDispatchable &isDispachable RawEventDispatchable isDispatchable
); );
const std::string type; const std::string type;
const folly::dynamic payload; const folly::dynamic payload;
const EventTarget eventTarget; const WeakEventTarget eventTarget;
/* /*
* Returns `true` if event can be dispatched to `eventTarget`. * Returns `true` if event can be dispatched to `eventTarget`.
* Events that associated with unmounted or deallocated `ShadowNode`s * Events that associated with unmounted or deallocated `ShadowNode`s
* must not be dispatched. * must not be dispatched.
*/ */
bool isDispachable() const; bool isDispatchable() const;
private: private:
const RawEventDispatchable isDispachable_; const RawEventDispatchable isDispatchable_;
}; };
} // namespace react } // namespace react

Просмотреть файл

@ -24,28 +24,17 @@ enum class EventPriority: int {
Deferred = AsynchronousBatched Deferred = AsynchronousBatched
}; };
/* `InstanceHandler`, `EventTarget`, and `EventHandler` are all opaque
* raw pointers. We use `struct {} *` trick to differentiate them in compiler's
* eyes to ensure type safety.
* These structs must have names (and the names must be exported)
* to allow consistent template (e.g. `std::function`) instantiating
* across different modules.
*/
using EventTarget = struct EventTargetDummyStruct {} *;
/* /*
* We need this types only to ensure type-safety when we deal with them. Conceptually, * We need this types only to ensure type-safety when we deal with them. Conceptually,
* they are opaque pointers to some types that derived from those classes. * they are opaque pointers to some types that derived from those classes.
*/ */
class EventHandler {}; class EventHandler {};
class EventTarget {};
using SharedEventHandler = std::shared_ptr<const EventHandler>;
using SharedEventTarget = std::shared_ptr<const EventTarget>;
using WeakEventTarget = std::weak_ptr<const EventTarget>;
/* using EventPipe = std::function<void(const EventTarget *eventTarget, const std::string &type, const folly::dynamic &payload)>;
* EmptyEventTarget is used when some event cannot be dispatched to an original
* event target but still has to be dispatched to preserve consistency of event flow.
*/
static const EventTarget EmptyEventTarget = nullptr;
using EventPipe = std::function<void(const EventTarget &eventTarget, const std::string &type, const folly::dynamic &payload)>;
} // namespace react } // namespace react
} // namespace facebook } // namespace facebook

Просмотреть файл

@ -105,20 +105,15 @@ void FabricUIManager::setDispatchEventToTargetFunction(std::function<DispatchEve
dispatchEventToTargetFunction_ = dispatchEventFunction; dispatchEventToTargetFunction_ = dispatchEventFunction;
} }
void FabricUIManager::setReleaseEventTargetFunction(std::function<ReleaseEventTargetFunction> releaseEventTargetFunction) { void FabricUIManager::dispatchEventToTarget(const EventTarget *eventTarget, const std::string &type, const folly::dynamic &payload) const {
releaseEventTargetFunction_ = releaseEventTargetFunction; if (eventTarget) {
}
void FabricUIManager::dispatchEventToTarget(const EventTarget &eventTarget, const std::string &type, const folly::dynamic &payload) const {
if (eventTarget != EmptyEventTarget) {
dispatchEventToTargetFunction_( dispatchEventToTargetFunction_(
*eventHandler_, *eventHandler_,
eventTarget, *eventTarget,
const_cast<std::string &>(type), const_cast<std::string &>(type),
const_cast<folly::dynamic &>(payload) const_cast<folly::dynamic &>(payload)
); );
} } else {
else {
dispatchEventToEmptyTargetFunction_( dispatchEventToEmptyTargetFunction_(
*eventHandler_, *eventHandler_,
const_cast<std::string &>(type), const_cast<std::string &>(type),
@ -127,11 +122,7 @@ void FabricUIManager::dispatchEventToTarget(const EventTarget &eventTarget, cons
} }
} }
void FabricUIManager::releaseEventTarget(const EventTarget &eventTarget) const { SharedShadowNode FabricUIManager::createNode(int tag, std::string viewName, int rootTag, folly::dynamic props, SharedEventTarget eventTarget) {
releaseEventTargetFunction_(eventTarget);
}
SharedShadowNode FabricUIManager::createNode(int tag, std::string viewName, int rootTag, folly::dynamic props, EventTarget eventTarget) {
isLoggingEnabled && LOG(INFO) << "FabricUIManager::createNode(tag: " << tag << ", name: " << viewName << ", rootTag: " << rootTag << ", props: " << props << ")"; isLoggingEnabled && LOG(INFO) << "FabricUIManager::createNode(tag: " << tag << ", name: " << viewName << ", rootTag: " << rootTag << ", props: " << props << ")";
ComponentName componentName = componentNameByReactViewName(viewName); ComponentName componentName = componentNameByReactViewName(viewName);
@ -142,7 +133,7 @@ SharedShadowNode FabricUIManager::createNode(int tag, std::string viewName, int
componentDescriptor->createShadowNode({ componentDescriptor->createShadowNode({
.tag = tag, .tag = tag,
.rootTag = rootTag, .rootTag = rootTag,
.eventEmitter = componentDescriptor->createEventEmitter(eventTarget, tag), .eventEmitter = componentDescriptor->createEventEmitter(std::move(eventTarget), tag),
.props = componentDescriptor->cloneProps(nullptr, rawProps) .props = componentDescriptor->cloneProps(nullptr, rawProps)
}); });
@ -246,7 +237,7 @@ void FabricUIManager::completeRoot(int rootTag, const SharedShadowNodeUnsharedLi
void FabricUIManager::registerEventHandler(std::shared_ptr<EventHandler> eventHandler) { void FabricUIManager::registerEventHandler(std::shared_ptr<EventHandler> eventHandler) {
isLoggingEnabled && LOG(INFO) << "FabricUIManager::registerEventHandler(eventHandler: " << eventHandler.get() << ")"; isLoggingEnabled && LOG(INFO) << "FabricUIManager::registerEventHandler(eventHandler: " << eventHandler.get() << ")";
eventHandler_ = eventHandler; eventHandler_ = std::move(eventHandler);
} }
} // namespace react } // namespace react

Просмотреть файл

@ -19,7 +19,7 @@ namespace facebook {
namespace react { namespace react {
using DispatchEventToEmptyTargetFunction = void (const EventHandler &eventHandler, std::string type, folly::dynamic payload); using DispatchEventToEmptyTargetFunction = void (const EventHandler &eventHandler, std::string type, folly::dynamic payload);
using DispatchEventToTargetFunction = void (const EventHandler &eventHandler, EventTarget eventTarget, std::string type, folly::dynamic payload); using DispatchEventToTargetFunction = void (const EventHandler &eventHandler, const EventTarget &eventTarget, std::string type, folly::dynamic payload);
using ReleaseEventTargetFunction = void (EventTarget eventTarget); using ReleaseEventTargetFunction = void (EventTarget eventTarget);
class FabricUIManager { class FabricUIManager {
@ -44,16 +44,14 @@ public:
*/ */
void setDispatchEventToEmptyTargetFunction(std::function<DispatchEventToEmptyTargetFunction> dispatchEventFunction); void setDispatchEventToEmptyTargetFunction(std::function<DispatchEventToEmptyTargetFunction> dispatchEventFunction);
void setDispatchEventToTargetFunction(std::function<DispatchEventToTargetFunction> dispatchEventFunction); void setDispatchEventToTargetFunction(std::function<DispatchEventToTargetFunction> dispatchEventFunction);
void setReleaseEventTargetFunction(std::function<ReleaseEventTargetFunction> releaseEventTargetFunction);
#pragma mark - Native-facing Interface #pragma mark - Native-facing Interface
void dispatchEventToTarget(const EventTarget &eventTarget, const std::string &type, const folly::dynamic &payload) const; void dispatchEventToTarget(const EventTarget *eventTarget, const std::string &type, const folly::dynamic &payload) const;
void releaseEventTarget(const EventTarget &eventTarget) const;
#pragma mark - JavaScript/React-facing Interface #pragma mark - JavaScript/React-facing Interface
SharedShadowNode createNode(Tag reactTag, std::string viewName, Tag rootTag, folly::dynamic props, EventTarget eventTarget); SharedShadowNode createNode(Tag reactTag, std::string viewName, Tag rootTag, folly::dynamic props, SharedEventTarget eventTarget);
SharedShadowNode cloneNode(const SharedShadowNode &node); SharedShadowNode cloneNode(const SharedShadowNode &node);
SharedShadowNode cloneNodeWithNewChildren(const SharedShadowNode &node); SharedShadowNode cloneNodeWithNewChildren(const SharedShadowNode &node);
SharedShadowNode cloneNodeWithNewProps(const SharedShadowNode &node, folly::dynamic props); SharedShadowNode cloneNodeWithNewProps(const SharedShadowNode &node, folly::dynamic props);
@ -71,7 +69,6 @@ private:
std::shared_ptr<EventHandler> eventHandler_; std::shared_ptr<EventHandler> eventHandler_;
std::function<DispatchEventToEmptyTargetFunction> dispatchEventToEmptyTargetFunction_; std::function<DispatchEventToEmptyTargetFunction> dispatchEventToEmptyTargetFunction_;
std::function<DispatchEventToTargetFunction> dispatchEventToTargetFunction_; std::function<DispatchEventToTargetFunction> dispatchEventToTargetFunction_;
std::function<ReleaseEventTargetFunction> releaseEventTargetFunction_;
}; };
} // namespace react } // namespace react

Просмотреть файл

@ -18,7 +18,7 @@ namespace react {
ShadowTree::ShadowTree(Tag rootTag): ShadowTree::ShadowTree(Tag rootTag):
rootTag_(rootTag) { rootTag_(rootTag) {
const auto noopEventEmitter = std::make_shared<const ViewEventEmitter>(nullptr, rootTag, nullptr); const auto noopEventEmitter = std::make_shared<const ViewEventEmitter>(nullptr, rootTag, std::shared_ptr<const EventDispatcher>());
rootShadowNode_ = std::make_shared<RootShadowNode>( rootShadowNode_ = std::make_shared<RootShadowNode>(
ShadowNodeFragment { ShadowNodeFragment {
.tag = rootTag, .tag = rootTag,
@ -30,6 +30,10 @@ ShadowTree::ShadowTree(Tag rootTag):
); );
} }
ShadowTree::~ShadowTree() {
complete(std::make_shared<SharedShadowNodeList>(SharedShadowNodeList {}));
}
Tag ShadowTree::getRootTag() const { Tag ShadowTree::getRootTag() const {
return rootTag_; return rootTag_;
} }

Просмотреть файл

@ -30,6 +30,8 @@ public:
*/ */
ShadowTree(Tag rootTag); ShadowTree(Tag rootTag);
~ShadowTree();
/* /*
* Returns the rootTag associated with the shadow tree (the tag of the * Returns the rootTag associated with the shadow tree (the tag of the
* root shadow node). * root shadow node).