Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: node::Environment self-cleanup #39628

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 8 additions & 9 deletions shell/browser/electron_browser_main_parts.cc
Original file line number Diff line number Diff line change
Expand Up @@ -266,26 +266,25 @@ void ElectronBrowserMainParts::PostEarlyInitialization() {

node_bindings_->Initialize(js_env_->isolate()->GetCurrentContext());
// Create the global environment.
node::Environment* env = node_bindings_->CreateEnvironment(
node_env_ = node_bindings_->CreateEnvironment(
js_env_->isolate()->GetCurrentContext(), js_env_->platform());
node_env_ = std::make_unique<NodeEnvironment>(env);

env->set_trace_sync_io(env->options()->trace_sync_io);
node_env_->set_trace_sync_io(node_env_->options()->trace_sync_io);

// We do not want to crash the main process on unhandled rejections.
env->options()->unhandled_rejections = "warn-with-error-code";
node_env_->options()->unhandled_rejections = "warn-with-error-code";

// Add Electron extended APIs.
electron_bindings_->BindTo(js_env_->isolate(), env->process_object());
electron_bindings_->BindTo(js_env_->isolate(), node_env_->process_object());

// Create explicit microtasks runner.
js_env_->CreateMicrotasksRunner();

// Wrap the uv loop with global env.
node_bindings_->set_uv_env(env);
node_bindings_->set_uv_env(node_env_.get());

// Load everything.
node_bindings_->LoadEnvironment(env);
node_bindings_->LoadEnvironment(node_env_.get());

// We already initialized the feature list in PreEarlyInitialization(), but
// the user JS script would not have had a chance to alter the command-line
Expand Down Expand Up @@ -627,9 +626,9 @@ void ElectronBrowserMainParts::PostMainMessageLoopRun() {

// Destroy node platform after all destructors_ are executed, as they may
// invoke Node/V8 APIs inside them.
node_env_->env()->set_trace_sync_io(false);
node_env_->set_trace_sync_io(false);
js_env_->DestroyMicrotasksRunner();
node::Stop(node_env_->env(), node::StopFlags::kDoNotTerminateIsolate);
node::Stop(node_env_.get(), node::StopFlags::kDoNotTerminateIsolate);
node_env_.reset();

auto default_context_key = ElectronBrowserContext::PartitionKey("", false);
Expand Down
7 changes: 5 additions & 2 deletions shell/browser/electron_browser_main_parts.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ class Screen;
}
#endif

namespace node {
class Environment;
}

namespace ui {
class LinuxUiGetter;
}
Expand All @@ -47,7 +51,6 @@ class Browser;
class ElectronBindings;
class JavascriptEnvironment;
class NodeBindings;
class NodeEnvironment;

#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
class ElectronExtensionsClient;
Expand Down Expand Up @@ -166,7 +169,7 @@ class ElectronBrowserMainParts : public content::BrowserMainParts {
std::unique_ptr<JavascriptEnvironment> js_env_;

// depends-on: js_env_'s isolate
std::unique_ptr<NodeEnvironment> node_env_;
std::shared_ptr<node::Environment> node_env_;

// depends-on: js_env_'s isolate
std::unique_ptr<Browser> browser_;
Expand Down
8 changes: 0 additions & 8 deletions shell/browser/javascript_environment.cc
Original file line number Diff line number Diff line change
Expand Up @@ -340,12 +340,4 @@ void JavascriptEnvironment::DestroyMicrotasksRunner() {
base::CurrentThread::Get()->RemoveTaskObserver(microtasks_runner_.get());
}

NodeEnvironment::NodeEnvironment(node::Environment* env) : env_(env) {}

NodeEnvironment::~NodeEnvironment() {
auto* isolate_data = env_->isolate_data();
node::FreeEnvironment(env_);
node::FreeIsolateData(isolate_data);
}

} // namespace electron
16 changes: 0 additions & 16 deletions shell/browser/javascript_environment.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,22 +54,6 @@ class JavascriptEnvironment {
std::unique_ptr<MicrotasksRunner> microtasks_runner_;
};

// Manage the Node Environment automatically.
class NodeEnvironment {
public:
explicit NodeEnvironment(node::Environment* env);
~NodeEnvironment();

// disable copy
NodeEnvironment(const NodeEnvironment&) = delete;
NodeEnvironment& operator=(const NodeEnvironment&) = delete;

node::Environment* env() { return env_; }

private:
raw_ptr<node::Environment> env_;
};

} // namespace electron

#endif // ELECTRON_SHELL_BROWSER_JAVASCRIPT_ENVIRONMENT_H_
21 changes: 18 additions & 3 deletions shell/common/node_bindings.cc
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,7 @@ void NodeBindings::Initialize(v8::Local<v8::Context> context) {
g_is_initialized = true;
}

node::Environment* NodeBindings::CreateEnvironment(
std::shared_ptr<node::Environment> NodeBindings::CreateEnvironment(
v8::Handle<v8::Context> context,
node::MultiIsolatePlatform* platform,
std::vector<std::string> args,
Expand Down Expand Up @@ -644,10 +644,25 @@ node::Environment* NodeBindings::CreateEnvironment(
base::PathService::Get(content::CHILD_PROCESS_EXE, &helper_exec_path);
process.Set("helperExecPath", helper_exec_path);

return env;
auto env_deleter = [isolate, isolate_data,
context = v8::Global<v8::Context>{isolate, context}](
node::Environment* nenv) mutable {
// When `isolate_data` was created above, a pointer to it was kept
// in context's embedder_data[kElectronContextEmbedderDataIndex].
// Since we're about to free `isolate_data`, clear that entry
v8::HandleScope handle_scope{isolate};
context.Get(isolate)->SetAlignedPointerInEmbedderData(
kElectronContextEmbedderDataIndex, nullptr);
context.Reset();

node::FreeEnvironment(nenv);
node::FreeIsolateData(isolate_data);
};

return {env, std::move(env_deleter)};
}

node::Environment* NodeBindings::CreateEnvironment(
std::shared_ptr<node::Environment> NodeBindings::CreateEnvironment(
v8::Handle<v8::Context> context,
node::MultiIsolatePlatform* platform) {
#if BUILDFLAG(IS_WIN)
Expand Down
34 changes: 16 additions & 18 deletions shell/common/node_bindings.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#ifndef ELECTRON_SHELL_COMMON_NODE_BINDINGS_H_
#define ELECTRON_SHELL_COMMON_NODE_BINDINGS_H_

#include <memory>
#include <string>
#include <type_traits>
#include <vector>
Expand All @@ -25,12 +26,6 @@ class SingleThreadTaskRunner;

namespace electron {

// Choose a reasonable unique index that's higher than any Blink uses
// and thus unlikely to collide with an existing index.
static constexpr int kElectronContextEmbedderDataIndex =
static_cast<int>(gin::kPerContextDataStartIndex) +
static_cast<int>(gin::kEmbedderElectron);

// A helper class to manage uv_handle_t types, e.g. uv_async_t.
//
// As per the uv docs: "uv_close() MUST be called on each handle before
Expand Down Expand Up @@ -95,12 +90,15 @@ class NodeBindings {
std::vector<std::string> ParseNodeCliFlags();

// Create the environment and load node.js.
node::Environment* CreateEnvironment(v8::Handle<v8::Context> context,
node::MultiIsolatePlatform* platform,
std::vector<std::string> args,
std::vector<std::string> exec_args);
node::Environment* CreateEnvironment(v8::Handle<v8::Context> context,
node::MultiIsolatePlatform* platform);
std::shared_ptr<node::Environment> CreateEnvironment(
v8::Handle<v8::Context> context,
node::MultiIsolatePlatform* platform,
std::vector<std::string> args,
std::vector<std::string> exec_args);

std::shared_ptr<node::Environment> CreateEnvironment(
v8::Handle<v8::Context> context,
node::MultiIsolatePlatform* platform);

// Load node.js in the environment.
void LoadEnvironment(node::Environment* env);
Expand All @@ -111,12 +109,6 @@ class NodeBindings {
// Notify embed thread to start polling after environment is loaded.
void StartPolling();

// Clears the PerIsolateData.
void clear_isolate_data(v8::Local<v8::Context> context) {
context->SetAlignedPointerInEmbedderData(kElectronContextEmbedderDataIndex,
nullptr);
}

node::IsolateData* isolate_data(v8::Local<v8::Context> context) const {
if (context->GetNumberOfEmbedderDataFields() <=
kElectronContextEmbedderDataIndex) {
Expand Down Expand Up @@ -167,6 +159,12 @@ class NodeBindings {
raw_ptr<uv_loop_t> uv_loop_;

private:
// Choose a reasonable unique index that's higher than any Blink uses
// and thus unlikely to collide with an existing index.
static constexpr int kElectronContextEmbedderDataIndex =
static_cast<int>(gin::kPerContextDataStartIndex) +
static_cast<int>(gin::kEmbedderElectron);

// Thread to poll uv events.
static void EmbedThreadRunner(void* arg);

Expand Down
20 changes: 12 additions & 8 deletions shell/renderer/electron_renderer_client.cc
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ void ElectronRendererClient::DidCreateScriptContext(
v8::Maybe<bool> initialized = node::InitializeContext(renderer_context);
CHECK(!initialized.IsNothing() && initialized.FromJust());

node::Environment* env =
std::shared_ptr<node::Environment> env =
node_bindings_->CreateEnvironment(renderer_context, nullptr);

// If we have disabled the site instance overrides we should prevent loading
Expand All @@ -106,11 +106,11 @@ void ElectronRendererClient::DidCreateScriptContext(
BindProcess(env->isolate(), &process_dict, render_frame);

// Load everything.
node_bindings_->LoadEnvironment(env);
node_bindings_->LoadEnvironment(env.get());

if (node_bindings_->uv_env() == nullptr) {
// Make uv loop being wrapped by window context.
node_bindings_->set_uv_env(env);
node_bindings_->set_uv_env(env.get());

// Give the node loop a run to make sure everything is ready.
node_bindings_->StartPolling();
Expand All @@ -124,7 +124,9 @@ void ElectronRendererClient::WillReleaseScriptContext(
return;

node::Environment* env = node::Environment::GetCurrent(context);
if (environments_.erase(env) == 0)
const auto iter = base::ranges::find_if(
environments_, [env](auto& item) { return env == item.get(); });
if (iter == environments_.end())
return;

gin_helper::EmitEvent(env->isolate(), env->process_object(), "exit");
Expand All @@ -143,9 +145,7 @@ void ElectronRendererClient::WillReleaseScriptContext(
DCHECK_EQ(microtask_queue->GetMicrotasksScopeDepth(), 0);
microtask_queue->set_microtasks_policy(v8::MicrotasksPolicy::kExplicit);

node::FreeEnvironment(env);
node::FreeIsolateData(node_bindings_->isolate_data(context));
node_bindings_->clear_isolate_data(context);
environments_.erase(iter);

microtask_queue->set_microtasks_policy(old_policy);

Expand Down Expand Up @@ -201,7 +201,11 @@ node::Environment* ElectronRendererClient::GetEnvironment(
auto context =
GetContext(render_frame->GetWebFrame(), v8::Isolate::GetCurrent());
node::Environment* env = node::Environment::GetCurrent(context);
return base::Contains(environments_, env) ? env : nullptr;

return base::Contains(environments_, env,
[](auto const& item) { return item.get(); })
? env
: nullptr;
}

} // namespace electron
2 changes: 1 addition & 1 deletion shell/renderer/electron_renderer_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class ElectronRendererClient : public RendererClientBase {
// The node::Environment::GetCurrent API does not return nullptr when it
// is called for a context without node::Environment, so we have to keep
// a book of the environments created.
std::set<node::Environment*> environments_;
std::set<std::shared_ptr<node::Environment>> environments_;

// Getting main script context from web frame would lazily initializes
// its script context. Doing so in a web page without scripts would trigger
Expand Down
16 changes: 10 additions & 6 deletions shell/renderer/web_worker_observer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

#include <utility>

#include "base/containers/cxx20_erase_set.h"
#include "base/no_destructor.h"
#include "base/ranges/algorithm.h"
#include "base/threading/thread_local.h"
#include "shell/common/api/electron_bindings.h"
#include "shell/common/gin_helper/event_emitter_caller.h"
Expand Down Expand Up @@ -61,20 +63,23 @@ void WebWorkerObserver::WorkerScriptReadyForEvaluation(
// Setup node environment for each window.
v8::Maybe<bool> initialized = node::InitializeContext(worker_context);
CHECK(!initialized.IsNothing() && initialized.FromJust());
node::Environment* env =
std::shared_ptr<node::Environment> env =
node_bindings_->CreateEnvironment(worker_context, nullptr);

// Add Electron extended APIs.
electron_bindings_->BindTo(env->isolate(), env->process_object());

// Load everything.
node_bindings_->LoadEnvironment(env);
node_bindings_->LoadEnvironment(env.get());

// Make uv loop being wrapped by window context.
node_bindings_->set_uv_env(env);
node_bindings_->set_uv_env(env.get());

// Give the node loop a run to make sure everything is ready.
node_bindings_->StartPolling();

// Keep the environment alive until we free it in ContextWillDestroy()
environments_.insert(std::move(env));
}

void WebWorkerObserver::ContextWillDestroy(v8::Local<v8::Context> context) {
Expand All @@ -91,9 +96,8 @@ void WebWorkerObserver::ContextWillDestroy(v8::Local<v8::Context> context) {
DCHECK_EQ(microtask_queue->GetMicrotasksScopeDepth(), 0);
microtask_queue->set_microtasks_policy(v8::MicrotasksPolicy::kExplicit);

node::FreeEnvironment(env);
node::FreeIsolateData(node_bindings_->isolate_data(context));
node_bindings_->clear_isolate_data(context);
base::EraseIf(environments_,
[env](auto const& item) { return item.get() == env; });

microtask_queue->set_microtasks_policy(old_policy);

Expand Down
8 changes: 8 additions & 0 deletions shell/renderer/web_worker_observer.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,16 @@
#define ELECTRON_SHELL_RENDERER_WEB_WORKER_OBSERVER_H_

#include <memory>
#include <set>

#include "v8/include/v8.h"

namespace node {

class Environment;

} // namespace node

namespace electron {

class ElectronBindings;
Expand All @@ -35,6 +42,7 @@ class WebWorkerObserver {
private:
std::unique_ptr<NodeBindings> node_bindings_;
std::unique_ptr<ElectronBindings> electron_bindings_;
std::set<std::shared_ptr<node::Environment>> environments_;
};

} // namespace electron
Expand Down