src: clean up MultiIsolatePlatform interface

- Since this was introduced, V8 has effectively started requiring
  that the platform knows of the `Isolate*` before we (or an embedder)
  create our `IsolateData` structure; therefore, (un)registering it
  from the `IsolateData` constructor/destructor doesn’t make much
  sense anymore.
- Instead, we can require that the register/unregister functions
  are only called once, simplifying the implementation a bit.
- Add a callback that we can use to know when the platform has
  cleaned up its resources associated with a given `Isolate`.
  In particular, this means that in the Worker code, we don’t need
  to rely on what are essentially guesses about the number of event
  loop turns that we need in order to have everything cleaned up.

PR-URL: https://github.com/nodejs/node/pull/26384
Reviewed-By: Daniel Bevenius <daniel.bevenius@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
This commit is contained in:
Anna Henningsen 2019-03-01 23:35:54 +01:00
parent 377c5835e8
commit 6d9aa73b1f
No known key found for this signature in database
GPG Key ID: 9C63F3A6CD2AD8F9
6 changed files with 59 additions and 37 deletions

View File

@ -84,8 +84,6 @@ IsolateData::IsolateData(Isolate* isolate,
uses_node_allocator_(allocator_ == node_allocator_), uses_node_allocator_(allocator_ == node_allocator_),
platform_(platform) { platform_(platform) {
CHECK_NOT_NULL(allocator_); CHECK_NOT_NULL(allocator_);
if (platform_ != nullptr)
platform_->RegisterIsolate(isolate_, event_loop);
options_.reset( options_.reset(
new PerIsolateOptions(*(per_process::cli_options->per_isolate))); new PerIsolateOptions(*(per_process::cli_options->per_isolate)));
@ -137,12 +135,6 @@ IsolateData::IsolateData(Isolate* isolate,
#undef V #undef V
} }
IsolateData::~IsolateData() {
if (platform_ != nullptr)
platform_->UnregisterIsolate(isolate_);
}
void InitThreadLocalOnce() { void InitThreadLocalOnce() {
CHECK_EQ(0, uv_key_create(&Environment::thread_local_env)); CHECK_EQ(0, uv_key_create(&Environment::thread_local_env));
} }

View File

@ -401,7 +401,6 @@ class IsolateData {
uv_loop_t* event_loop, uv_loop_t* event_loop,
MultiIsolatePlatform* platform = nullptr, MultiIsolatePlatform* platform = nullptr,
ArrayBufferAllocator* node_allocator = nullptr); ArrayBufferAllocator* node_allocator = nullptr);
~IsolateData();
inline uv_loop_t* event_loop() const; inline uv_loop_t* event_loop() const;
inline MultiIsolatePlatform* platform() const; inline MultiIsolatePlatform* platform() const;
inline std::shared_ptr<PerIsolateOptions> options(); inline std::shared_ptr<PerIsolateOptions> options();

View File

@ -228,10 +228,22 @@ class NODE_EXTERN MultiIsolatePlatform : public v8::Platform {
virtual void DrainTasks(v8::Isolate* isolate) = 0; virtual void DrainTasks(v8::Isolate* isolate) = 0;
virtual void CancelPendingDelayedTasks(v8::Isolate* isolate) = 0; virtual void CancelPendingDelayedTasks(v8::Isolate* isolate) = 0;
// These will be called by the `IsolateData` creation/destruction functions. // This needs to be called between the calls to `Isolate::Allocate()` and
// `Isolate::Initialize()`, so that initialization can already start
// using the platform.
// When using `NewIsolate()`, this is taken care of by that function.
// This function may only be called once per `Isolate`.
virtual void RegisterIsolate(v8::Isolate* isolate, virtual void RegisterIsolate(v8::Isolate* isolate,
struct uv_loop_s* loop) = 0; struct uv_loop_s* loop) = 0;
// This needs to be called right before calling `Isolate::Dispose()`.
// This function may only be called once per `Isolate`.
virtual void UnregisterIsolate(v8::Isolate* isolate) = 0; virtual void UnregisterIsolate(v8::Isolate* isolate) = 0;
// The platform should call the passed function once all state associated
// with the given isolate has been cleaned up. This can, but does not have to,
// happen asynchronously.
virtual void AddIsolateFinishedCallback(v8::Isolate* isolate,
void (*callback)(void*),
void* data) = 0;
}; };
// Creates a new isolate with Node.js-specific settings. // Creates a new isolate with Node.js-specific settings.

View File

@ -261,6 +261,11 @@ PerIsolatePlatformData::~PerIsolatePlatformData() {
Shutdown(); Shutdown();
} }
void PerIsolatePlatformData::AddShutdownCallback(void (*callback)(void*),
void* data) {
shutdown_callbacks_.emplace_back(ShutdownCallback { callback, data });
}
void PerIsolatePlatformData::Shutdown() { void PerIsolatePlatformData::Shutdown() {
if (flush_tasks_ == nullptr) if (flush_tasks_ == nullptr)
return; return;
@ -269,21 +274,19 @@ void PerIsolatePlatformData::Shutdown() {
CHECK_NULL(foreground_tasks_.Pop()); CHECK_NULL(foreground_tasks_.Pop());
CancelPendingDelayedTasks(); CancelPendingDelayedTasks();
ShutdownCbList* copy = new ShutdownCbList(std::move(shutdown_callbacks_));
flush_tasks_->data = copy;
uv_close(reinterpret_cast<uv_handle_t*>(flush_tasks_), uv_close(reinterpret_cast<uv_handle_t*>(flush_tasks_),
[](uv_handle_t* handle) { [](uv_handle_t* handle) {
std::unique_ptr<ShutdownCbList> callbacks(
static_cast<ShutdownCbList*>(handle->data));
for (const auto& callback : *callbacks)
callback.cb(callback.data);
delete reinterpret_cast<uv_async_t*>(handle); delete reinterpret_cast<uv_async_t*>(handle);
}); });
flush_tasks_ = nullptr; flush_tasks_ = nullptr;
} }
void PerIsolatePlatformData::ref() {
ref_count_++;
}
int PerIsolatePlatformData::unref() {
return --ref_count_;
}
NodePlatform::NodePlatform(int thread_pool_size, NodePlatform::NodePlatform(int thread_pool_size,
TracingController* tracing_controller) { TracingController* tracing_controller) {
if (tracing_controller) { if (tracing_controller) {
@ -298,23 +301,29 @@ NodePlatform::NodePlatform(int thread_pool_size,
void NodePlatform::RegisterIsolate(Isolate* isolate, uv_loop_t* loop) { void NodePlatform::RegisterIsolate(Isolate* isolate, uv_loop_t* loop) {
Mutex::ScopedLock lock(per_isolate_mutex_); Mutex::ScopedLock lock(per_isolate_mutex_);
std::shared_ptr<PerIsolatePlatformData> existing = per_isolate_[isolate]; std::shared_ptr<PerIsolatePlatformData> existing = per_isolate_[isolate];
if (existing) { CHECK(!existing);
CHECK_EQ(loop, existing->event_loop()); per_isolate_[isolate] =
existing->ref(); std::make_shared<PerIsolatePlatformData>(isolate, loop);
} else {
per_isolate_[isolate] =
std::make_shared<PerIsolatePlatformData>(isolate, loop);
}
} }
void NodePlatform::UnregisterIsolate(Isolate* isolate) { void NodePlatform::UnregisterIsolate(Isolate* isolate) {
Mutex::ScopedLock lock(per_isolate_mutex_); Mutex::ScopedLock lock(per_isolate_mutex_);
std::shared_ptr<PerIsolatePlatformData> existing = per_isolate_[isolate]; std::shared_ptr<PerIsolatePlatformData> existing = per_isolate_[isolate];
CHECK(existing); CHECK(existing);
if (existing->unref() == 0) { existing->Shutdown();
existing->Shutdown(); per_isolate_.erase(isolate);
per_isolate_.erase(isolate); }
void NodePlatform::AddIsolateFinishedCallback(Isolate* isolate,
void (*cb)(void*), void* data) {
Mutex::ScopedLock lock(per_isolate_mutex_);
auto it = per_isolate_.find(isolate);
if (it == per_isolate_.end()) {
CHECK(it->second);
cb(data);
return;
} }
it->second->AddShutdownCallback(cb, data);
} }
void NodePlatform::Shutdown() { void NodePlatform::Shutdown() {

View File

@ -64,11 +64,9 @@ class PerIsolatePlatformData :
double delay_in_seconds) override; double delay_in_seconds) override;
bool IdleTasksEnabled() override { return false; } bool IdleTasksEnabled() override { return false; }
void AddShutdownCallback(void (*callback)(void*), void* data);
void Shutdown(); void Shutdown();
void ref();
int unref();
// Returns true if work was dispatched or executed. New tasks that are // Returns true if work was dispatched or executed. New tasks that are
// posted during flushing of the queue are postponed until the next // posted during flushing of the queue are postponed until the next
// flushing. // flushing.
@ -84,7 +82,13 @@ class PerIsolatePlatformData :
static void RunForegroundTask(std::unique_ptr<v8::Task> task); static void RunForegroundTask(std::unique_ptr<v8::Task> task);
static void RunForegroundTask(uv_timer_t* timer); static void RunForegroundTask(uv_timer_t* timer);
int ref_count_ = 1; struct ShutdownCallback {
void (*cb)(void*);
void* data;
};
typedef std::vector<ShutdownCallback> ShutdownCbList;
ShutdownCbList shutdown_callbacks_;
uv_loop_t* const loop_; uv_loop_t* const loop_;
uv_async_t* flush_tasks_ = nullptr; uv_async_t* flush_tasks_ = nullptr;
TaskQueue<v8::Task> foreground_tasks_; TaskQueue<v8::Task> foreground_tasks_;
@ -145,6 +149,8 @@ class NodePlatform : public MultiIsolatePlatform {
void RegisterIsolate(v8::Isolate* isolate, uv_loop_t* loop) override; void RegisterIsolate(v8::Isolate* isolate, uv_loop_t* loop) override;
void UnregisterIsolate(v8::Isolate* isolate) override; void UnregisterIsolate(v8::Isolate* isolate) override;
void AddIsolateFinishedCallback(v8::Isolate* isolate,
void (*callback)(void*), void* data) override;
std::shared_ptr<v8::TaskRunner> GetForegroundTaskRunner( std::shared_ptr<v8::TaskRunner> GetForegroundTaskRunner(
v8::Isolate* isolate) override; v8::Isolate* isolate) override;

View File

@ -188,16 +188,20 @@ class WorkerThreadData {
w_->platform_->CancelPendingDelayedTasks(isolate); w_->platform_->CancelPendingDelayedTasks(isolate);
bool platform_finished = false;
isolate_data_.reset(); isolate_data_.reset();
w_->platform_->AddIsolateFinishedCallback(isolate, [](void* data) {
*static_cast<bool*>(data) = true;
}, &platform_finished);
w_->platform_->UnregisterIsolate(isolate); w_->platform_->UnregisterIsolate(isolate);
isolate->Dispose(); isolate->Dispose();
// Need to run the loop twice more to close the platform's uv_async_t // Wait until the platform has cleaned up all relevant resources.
// TODO(addaleax): It would be better for the platform itself to provide while (!platform_finished)
// some kind of notification when it has fully cleaned up. uv_run(&loop_, UV_RUN_ONCE);
uv_run(&loop_, UV_RUN_ONCE);
uv_run(&loop_, UV_RUN_ONCE);
CheckedUvLoopClose(&loop_); CheckedUvLoopClose(&loop_);
} }