refactor: Clean up the implementation of the registerStreamProtocol (#11357)
* Use weak pointer to avoid race condition * Use DeleteSoon to delete pointer across threads * Simplify EventSubscriber * No need to manually mange V8 convertions * Fix cpplint warning We should update cpplint for this, but let's do it in other PR. * Move UI thread operations to EventSubscriber * Less and more assertions Some methods are now private so no more need to assert threads. * Fix cpplint warnings * No longer needs the EventEmitted * EventSubscriber => StreamSubscriber * Reduce the copies when passing data * Fix cpplint warnings
This commit is contained in:
parent
3805c5f538
commit
d3ae541397
9 changed files with 314 additions and 373 deletions
|
@ -1,124 +0,0 @@
|
|||
// Copyright (c) 2017 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/api/event_subscriber.h"
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "atom/common/native_mate_converters/callback.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// A FunctionTemplate lifetime is bound to the v8 context, so it can be safely
|
||||
// stored as a global here since there's only one for the main process.
|
||||
v8::Global<v8::FunctionTemplate> g_cached_template;
|
||||
|
||||
struct JSHandlerData {
|
||||
JSHandlerData(v8::Isolate* isolate,
|
||||
mate::internal::EventSubscriberBase* subscriber)
|
||||
: handle_(isolate, v8::External::New(isolate, this)),
|
||||
subscriber_(subscriber) {
|
||||
handle_.SetWeak(this, GC, v8::WeakCallbackType::kFinalizer);
|
||||
}
|
||||
|
||||
static void GC(const v8::WeakCallbackInfo<JSHandlerData>& data) {
|
||||
delete data.GetParameter();
|
||||
}
|
||||
|
||||
v8::Global<v8::External> handle_;
|
||||
mate::internal::EventSubscriberBase* subscriber_;
|
||||
};
|
||||
|
||||
void InvokeCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
|
||||
v8::Locker locker(info.GetIsolate());
|
||||
v8::HandleScope handle_scope(info.GetIsolate());
|
||||
v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext();
|
||||
v8::Context::Scope context_scope(context);
|
||||
mate::Arguments args(info);
|
||||
v8::Local<v8::Value> handler, event;
|
||||
args.GetNext(&handler);
|
||||
args.GetNext(&event);
|
||||
DCHECK(handler->IsExternal());
|
||||
DCHECK(event->IsString());
|
||||
JSHandlerData* handler_data = static_cast<JSHandlerData*>(
|
||||
v8::Local<v8::External>::Cast(handler)->Value());
|
||||
handler_data->subscriber_->EventEmitted(mate::V8ToString(event), &args);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace mate {
|
||||
|
||||
namespace internal {
|
||||
|
||||
EventSubscriberBase::EventSubscriberBase(v8::Isolate* isolate,
|
||||
v8::Local<v8::Object> emitter)
|
||||
: isolate_(isolate), emitter_(isolate, emitter) {
|
||||
if (g_cached_template.IsEmpty()) {
|
||||
g_cached_template = v8::Global<v8::FunctionTemplate>(
|
||||
isolate_, v8::FunctionTemplate::New(isolate_, InvokeCallback));
|
||||
}
|
||||
}
|
||||
|
||||
EventSubscriberBase::~EventSubscriberBase() {
|
||||
if (!isolate_) {
|
||||
return;
|
||||
}
|
||||
RemoveAllListeners();
|
||||
emitter_.Reset();
|
||||
DCHECK_EQ(js_handlers_.size(), 0u);
|
||||
}
|
||||
|
||||
void EventSubscriberBase::On(const std::string& event_name) {
|
||||
DCHECK(js_handlers_.find(event_name) == js_handlers_.end());
|
||||
v8::Locker locker(isolate_);
|
||||
v8::Isolate::Scope isolate_scope(isolate_);
|
||||
v8::HandleScope handle_scope(isolate_);
|
||||
auto fn_template = g_cached_template.Get(isolate_);
|
||||
auto event = mate::StringToV8(isolate_, event_name);
|
||||
auto* js_handler_data = new JSHandlerData(isolate_, this);
|
||||
v8::Local<v8::Value> fn = internal::BindFunctionWith(
|
||||
isolate_, isolate_->GetCurrentContext(), fn_template->GetFunction(),
|
||||
js_handler_data->handle_.Get(isolate_), event);
|
||||
js_handlers_.insert(
|
||||
std::make_pair(event_name, v8::Global<v8::Value>(isolate_, fn)));
|
||||
internal::ValueVector converted_args = {event, fn};
|
||||
internal::CallMethodWithArgs(isolate_, emitter_.Get(isolate_), "on",
|
||||
&converted_args);
|
||||
}
|
||||
|
||||
void EventSubscriberBase::Off(const std::string& event_name) {
|
||||
v8::Locker locker(isolate_);
|
||||
v8::Isolate::Scope isolate_scope(isolate_);
|
||||
v8::HandleScope handle_scope(isolate_);
|
||||
auto js_handler = js_handlers_.find(event_name);
|
||||
DCHECK(js_handler != js_handlers_.end());
|
||||
RemoveListener(js_handler);
|
||||
}
|
||||
|
||||
void EventSubscriberBase::RemoveAllListeners() {
|
||||
v8::Locker locker(isolate_);
|
||||
v8::Isolate::Scope isolate_scope(isolate_);
|
||||
v8::HandleScope handle_scope(isolate_);
|
||||
while (!js_handlers_.empty()) {
|
||||
RemoveListener(js_handlers_.begin());
|
||||
}
|
||||
}
|
||||
|
||||
std::map<std::string, v8::Global<v8::Value>>::iterator
|
||||
EventSubscriberBase::RemoveListener(
|
||||
std::map<std::string, v8::Global<v8::Value>>::iterator it) {
|
||||
internal::ValueVector args = {StringToV8(isolate_, it->first),
|
||||
it->second.Get(isolate_)};
|
||||
internal::CallMethodWithArgs(
|
||||
isolate_, v8::Local<v8::Object>::Cast(emitter_.Get(isolate_)),
|
||||
"removeListener", &args);
|
||||
it->second.Reset();
|
||||
return js_handlers_.erase(it);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
||||
} // namespace mate
|
|
@ -1,141 +0,0 @@
|
|||
// Copyright (c) 2017 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_API_EVENT_SUBSCRIBER_H_
|
||||
#define ATOM_BROWSER_API_EVENT_SUBSCRIBER_H_
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "atom/common/api/event_emitter_caller.h"
|
||||
#include "base/synchronization/lock.h"
|
||||
#include "content/public/browser/browser_thread.h"
|
||||
#include "native_mate/arguments.h"
|
||||
|
||||
namespace mate {
|
||||
|
||||
namespace internal {
|
||||
|
||||
class EventSubscriberBase {
|
||||
public:
|
||||
EventSubscriberBase(v8::Isolate* isolate, v8::Local<v8::Object> emitter);
|
||||
virtual ~EventSubscriberBase();
|
||||
virtual void EventEmitted(const std::string& event_name,
|
||||
mate::Arguments* args) = 0;
|
||||
|
||||
protected:
|
||||
void On(const std::string& event_name);
|
||||
void Off(const std::string& event_name);
|
||||
void RemoveAllListeners();
|
||||
|
||||
private:
|
||||
std::map<std::string, v8::Global<v8::Value>>::iterator RemoveListener(
|
||||
std::map<std::string, v8::Global<v8::Value>>::iterator it);
|
||||
|
||||
v8::Isolate* isolate_;
|
||||
v8::Global<v8::Object> emitter_;
|
||||
std::map<std::string, v8::Global<v8::Value>> js_handlers_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(EventSubscriberBase);
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
||||
template <typename HandlerType>
|
||||
class EventSubscriber : internal::EventSubscriberBase {
|
||||
public:
|
||||
using EventCallback = void (HandlerType::*)(mate::Arguments* args);
|
||||
// Alias to unique_ptr with deleter.
|
||||
using unique_ptr = std::unique_ptr<EventSubscriber<HandlerType>,
|
||||
void (*)(EventSubscriber<HandlerType>*)>;
|
||||
// EventSubscriber should only be created/deleted in the main thread since it
|
||||
// communicates with the V8 engine. This smart pointer makes it simpler to
|
||||
// bind the lifetime of EventSubscriber with a class whose lifetime is managed
|
||||
// by a non-UI thread.
|
||||
class SafePtr : public unique_ptr {
|
||||
public:
|
||||
SafePtr() : SafePtr(nullptr) {}
|
||||
explicit SafePtr(EventSubscriber<HandlerType>* ptr)
|
||||
: unique_ptr(ptr, Deleter) {}
|
||||
|
||||
private:
|
||||
// Custom deleter that schedules destructor invocation to the main thread.
|
||||
static void Deleter(EventSubscriber<HandlerType>* ptr) {
|
||||
DCHECK(
|
||||
!::content::BrowserThread::CurrentlyOn(::content::BrowserThread::UI));
|
||||
DCHECK(ptr);
|
||||
// Acquire handler lock and reset handler_ to ensure that any new events
|
||||
// emitted will be ignored after this function returns
|
||||
base::AutoLock auto_lock(ptr->handler_lock_);
|
||||
ptr->handler_ = nullptr;
|
||||
content::BrowserThread::PostTask(
|
||||
content::BrowserThread::UI, FROM_HERE,
|
||||
base::BindOnce(
|
||||
[](EventSubscriber<HandlerType>* subscriber) {
|
||||
{
|
||||
// It is possible that this function will execute in the UI
|
||||
// thread before the outer function has returned and destroyed
|
||||
// its auto_lock. We need to acquire the lock before deleting
|
||||
// or risk a crash.
|
||||
base::AutoLock auto_lock(subscriber->handler_lock_);
|
||||
}
|
||||
delete subscriber;
|
||||
},
|
||||
ptr));
|
||||
}
|
||||
};
|
||||
|
||||
EventSubscriber(HandlerType* handler,
|
||||
v8::Isolate* isolate,
|
||||
v8::Local<v8::Object> emitter)
|
||||
: EventSubscriberBase(isolate, emitter), handler_(handler) {
|
||||
DCHECK_CURRENTLY_ON(::content::BrowserThread::UI);
|
||||
}
|
||||
|
||||
void On(const std::string& event, EventCallback callback) {
|
||||
DCHECK_CURRENTLY_ON(::content::BrowserThread::UI);
|
||||
EventSubscriberBase::On(event);
|
||||
callbacks_.insert(std::make_pair(event, callback));
|
||||
}
|
||||
|
||||
void Off(const std::string& event) {
|
||||
DCHECK_CURRENTLY_ON(::content::BrowserThread::UI);
|
||||
EventSubscriberBase::Off(event);
|
||||
DCHECK(callbacks_.find(event) != callbacks_.end());
|
||||
callbacks_.erase(callbacks_.find(event));
|
||||
}
|
||||
|
||||
void RemoveAllListeners() {
|
||||
DCHECK_CURRENTLY_ON(::content::BrowserThread::UI);
|
||||
EventSubscriberBase::RemoveAllListeners();
|
||||
callbacks_.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
void EventEmitted(const std::string& event_name,
|
||||
mate::Arguments* args) override {
|
||||
DCHECK_CURRENTLY_ON(::content::BrowserThread::UI);
|
||||
base::AutoLock auto_lock(handler_lock_);
|
||||
if (!handler_) {
|
||||
// handler_ was probably destroyed by another thread and we should not
|
||||
// access it.
|
||||
return;
|
||||
}
|
||||
auto it = callbacks_.find(event_name);
|
||||
if (it != callbacks_.end()) {
|
||||
auto method = it->second;
|
||||
(handler_->*method)(args);
|
||||
}
|
||||
}
|
||||
|
||||
HandlerType* handler_;
|
||||
base::Lock handler_lock_;
|
||||
std::map<std::string, EventCallback> callbacks_;
|
||||
};
|
||||
|
||||
} // namespace mate
|
||||
|
||||
#endif // ATOM_BROWSER_API_EVENT_SUBSCRIBER_H_
|
113
atom/browser/api/stream_subscriber.cc
Normal file
113
atom/browser/api/stream_subscriber.cc
Normal file
|
@ -0,0 +1,113 @@
|
|||
// Copyright (c) 2017 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/browser/api/stream_subscriber.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "atom/browser/net/url_request_stream_job.h"
|
||||
#include "atom/common/api/event_emitter_caller.h"
|
||||
#include "atom/common/native_mate_converters/callback.h"
|
||||
|
||||
#include "atom/common/node_includes.h"
|
||||
|
||||
namespace mate {
|
||||
|
||||
StreamSubscriber::StreamSubscriber(
|
||||
v8::Isolate* isolate,
|
||||
v8::Local<v8::Object> emitter,
|
||||
base::WeakPtr<atom::URLRequestStreamJob> url_job)
|
||||
: isolate_(isolate),
|
||||
emitter_(isolate, emitter),
|
||||
url_job_(url_job),
|
||||
weak_factory_(this) {
|
||||
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
||||
auto weak_self = weak_factory_.GetWeakPtr();
|
||||
On("data", base::Bind(&StreamSubscriber::OnData, weak_self));
|
||||
On("end", base::Bind(&StreamSubscriber::OnEnd, weak_self));
|
||||
On("error", base::Bind(&StreamSubscriber::OnError, weak_self));
|
||||
}
|
||||
|
||||
StreamSubscriber::~StreamSubscriber() {
|
||||
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
||||
RemoveAllListeners();
|
||||
}
|
||||
|
||||
void StreamSubscriber::On(const std::string& event, EventCallback&& callback) { // NOLINT
|
||||
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
||||
DCHECK(js_handlers_.find(event) == js_handlers_.end());
|
||||
|
||||
v8::Locker locker(isolate_);
|
||||
v8::Isolate::Scope isolate_scope(isolate_);
|
||||
v8::HandleScope handle_scope(isolate_);
|
||||
// emitter.on(event, EventEmitted)
|
||||
auto fn = CallbackToV8(isolate_, callback);
|
||||
js_handlers_[event] = v8::Global<v8::Value>(isolate_, fn);
|
||||
internal::ValueVector args = { StringToV8(isolate_, event), fn };
|
||||
internal::CallMethodWithArgs(isolate_, emitter_.Get(isolate_), "on", &args);
|
||||
}
|
||||
|
||||
void StreamSubscriber::Off(const std::string& event) {
|
||||
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
||||
DCHECK(js_handlers_.find(event) != js_handlers_.end());
|
||||
|
||||
v8::Locker locker(isolate_);
|
||||
v8::Isolate::Scope isolate_scope(isolate_);
|
||||
v8::HandleScope handle_scope(isolate_);
|
||||
auto js_handler = js_handlers_.find(event);
|
||||
DCHECK(js_handler != js_handlers_.end());
|
||||
RemoveListener(js_handler);
|
||||
}
|
||||
|
||||
void StreamSubscriber::OnData(mate::Arguments* args) {
|
||||
v8::Local<v8::Value> buf;
|
||||
args->GetNext(&buf);
|
||||
if (!node::Buffer::HasInstance(buf)) {
|
||||
args->ThrowError("data must be Buffer");
|
||||
return;
|
||||
}
|
||||
|
||||
const char* data = node::Buffer::Data(buf);
|
||||
size_t length = node::Buffer::Length(buf);
|
||||
if (length == 0)
|
||||
return;
|
||||
|
||||
// Pass the data to the URLJob in IO thread.
|
||||
std::vector<char> buffer(data, data + length);
|
||||
content::BrowserThread::PostTask(
|
||||
content::BrowserThread::IO, FROM_HERE,
|
||||
base::Bind(&atom::URLRequestStreamJob::OnData,
|
||||
url_job_, base::Passed(&buffer)));
|
||||
}
|
||||
|
||||
void StreamSubscriber::OnEnd(mate::Arguments* args) {
|
||||
content::BrowserThread::PostTask(
|
||||
content::BrowserThread::IO, FROM_HERE,
|
||||
base::Bind(&atom::URLRequestStreamJob::OnEnd, url_job_));
|
||||
}
|
||||
|
||||
void StreamSubscriber::OnError(mate::Arguments* args) {
|
||||
content::BrowserThread::PostTask(
|
||||
content::BrowserThread::IO, FROM_HERE,
|
||||
base::Bind(&atom::URLRequestStreamJob::OnError, url_job_));
|
||||
}
|
||||
|
||||
void StreamSubscriber::RemoveAllListeners() {
|
||||
v8::Locker locker(isolate_);
|
||||
v8::Isolate::Scope isolate_scope(isolate_);
|
||||
v8::HandleScope handle_scope(isolate_);
|
||||
while (!js_handlers_.empty()) {
|
||||
RemoveListener(js_handlers_.begin());
|
||||
}
|
||||
}
|
||||
|
||||
void StreamSubscriber::RemoveListener(JSHandlersMap::iterator it) {
|
||||
internal::ValueVector args = { StringToV8(isolate_, it->first),
|
||||
it->second.Get(isolate_) };
|
||||
internal::CallMethodWithArgs(isolate_, emitter_.Get(isolate_),
|
||||
"removeListener", &args);
|
||||
js_handlers_.erase(it);
|
||||
}
|
||||
|
||||
} // namespace mate
|
58
atom/browser/api/stream_subscriber.h
Normal file
58
atom/browser/api/stream_subscriber.h
Normal file
|
@ -0,0 +1,58 @@
|
|||
// Copyright (c) 2017 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_BROWSER_API_STREAM_SUBSCRIBER_H_
|
||||
#define ATOM_BROWSER_API_STREAM_SUBSCRIBER_H_
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/callback.h"
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "content/public/browser/browser_thread.h"
|
||||
#include "v8/include/v8.h"
|
||||
|
||||
namespace atom {
|
||||
class URLRequestStreamJob;
|
||||
}
|
||||
|
||||
namespace mate {
|
||||
|
||||
class Arguments;
|
||||
|
||||
class StreamSubscriber {
|
||||
public:
|
||||
StreamSubscriber(v8::Isolate* isolate,
|
||||
v8::Local<v8::Object> emitter,
|
||||
base::WeakPtr<atom::URLRequestStreamJob> url_job);
|
||||
~StreamSubscriber();
|
||||
|
||||
private:
|
||||
using JSHandlersMap = std::map<std::string, v8::Global<v8::Value>>;
|
||||
using EventCallback = base::Callback<void(mate::Arguments* args)>;
|
||||
|
||||
void On(const std::string& event, EventCallback&& callback); // NOLINT
|
||||
void Off(const std::string& event);
|
||||
|
||||
void OnData(mate::Arguments* args);
|
||||
void OnEnd(mate::Arguments* args);
|
||||
void OnError(mate::Arguments* args);
|
||||
|
||||
void RemoveAllListeners();
|
||||
void RemoveListener(JSHandlersMap::iterator it);
|
||||
|
||||
v8::Isolate* isolate_;
|
||||
v8::Global<v8::Object> emitter_;
|
||||
base::WeakPtr<atom::URLRequestStreamJob> url_job_;
|
||||
|
||||
JSHandlersMap js_handlers_;
|
||||
|
||||
base::WeakPtrFactory<StreamSubscriber> weak_factory_;
|
||||
};
|
||||
|
||||
} // namespace mate
|
||||
|
||||
#endif // ATOM_BROWSER_API_STREAM_SUBSCRIBER_H_
|
Loading…
Add table
Add a link
Reference in a new issue