Commit cce33f37 authored by tzik's avatar tzik Committed by Commit Bot

Expose MicrotaskQueue as a V8 API

This introduces v8::MicrotaskQueue backed by v8::internal::MicrotaskQueue.

The embedder will get an option to use non-default MicrotaskQueue by creating
the instance by v8::MicrotaskQueue::New(). The instance can be attached to
a Context by passing it to Context::New().

Bug: v8:8124
Change-Id: Iee0711785d5748860eb94e30a8d83199a743ffaa
Reviewed-on: https://chromium-review.googlesource.com/c/1414950
Commit-Queue: Taiju Tsuiki <tzik@chromium.org>
Reviewed-by: 's avatarBenedikt Meurer <bmeurer@chromium.org>
Reviewed-by: 's avatarYang Guo <yangguo@chromium.org>
Reviewed-by: 's avatarAdam Klein <adamk@chromium.org>
Cr-Commit-Position: refs/heads/master@{#59933}
parent b2f8280e
......@@ -54,6 +54,7 @@ class Integer;
class Isolate;
template <class T>
class Maybe;
class MicrotaskQueue;
class Name;
class Number;
class NumberObject;
......@@ -6736,6 +6737,7 @@ typedef void (*PromiseRejectCallback)(PromiseRejectMessage message);
// --- Microtasks Callbacks ---
typedef void (*MicrotasksCompletedCallback)(Isolate*);
typedef void (*MicrotasksCompletedCallbackWithData)(Isolate*, void*);
typedef void (*MicrotaskCallback)(void* data);
......@@ -6748,6 +6750,80 @@ typedef void (*MicrotaskCallback)(void* data);
*/
enum class MicrotasksPolicy { kExplicit, kScoped, kAuto };
/**
* Represents the microtask queue, where microtasks are stored and processed.
* https://html.spec.whatwg.org/multipage/webappapis.html#microtask-queue
* https://html.spec.whatwg.org/multipage/webappapis.html#enqueuejob(queuename,-job,-arguments)
* https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint
*
* A MicrotaskQueue instance may be associated to multiple Contexts by passing
* it to Context::New(), and they can be detached by Context::DetachGlobal().
* The embedder must keep the MicrotaskQueue instance alive until all associated
* Contexts are gone or detached.
*
* Use the same instance of MicrotaskQueue for all Contexts that may access each
* other synchronously. E.g. for Web embedding, use the same instance for all
* origins that share the same URL scheme and eTLD+1.
*/
class V8_EXPORT MicrotaskQueue {
public:
/**
* Creates an empty MicrotaskQueue instance.
*/
static std::unique_ptr<MicrotaskQueue> New();
virtual ~MicrotaskQueue() = default;
/**
* Enqueues the callback to the queue.
*/
virtual void EnqueueMicrotask(Isolate* isolate,
Local<Function> microtask) = 0;
/**
* Enqueues the callback to the queue.
*/
virtual void EnqueueMicrotask(v8::Isolate* isolate,
MicrotaskCallback callback,
void* data = nullptr) = 0;
/**
* Adds a callback to notify the embedder after microtasks were run. The
* callback is triggered by explicit RunMicrotasks call or automatic
* microtasks execution (see Isolate::SetMicrotasksPolicy).
*
* Callback will trigger even if microtasks were attempted to run,
* but the microtasks queue was empty and no single microtask was actually
* executed.
*
* Executing scripts inside the callback will not re-trigger microtasks and
* the callback.
*/
virtual void AddMicrotasksCompletedCallback(
MicrotasksCompletedCallbackWithData callback, void* data = nullptr) = 0;
/**
* Removes callback that was installed by AddMicrotasksCompletedCallback.
*/
virtual void RemoveMicrotasksCompletedCallback(
MicrotasksCompletedCallbackWithData callback, void* data = nullptr) = 0;
/**
* Runs microtasks if no microtask is running on this MicrotaskQueue instance.
*/
virtual void PerformCheckpoint(Isolate* isolate) = 0;
/**
* Returns true if a microtask is running on this MicrotaskQueue instance.
*/
virtual bool IsRunningMicrotasks() const = 0;
private:
friend class internal::MicrotaskQueue;
MicrotaskQueue() = default;
MicrotaskQueue(const MicrotaskQueue&) = delete;
MicrotaskQueue& operator=(const MicrotaskQueue&) = delete;
};
/**
* This scope is used to control microtasks when kScopeMicrotasksInvocation
......@@ -6763,6 +6839,7 @@ class V8_EXPORT MicrotasksScope {
enum Type { kRunMicrotasks, kDoNotRunMicrotasks };
MicrotasksScope(Isolate* isolate, Type type);
MicrotasksScope(Isolate* isolate, MicrotaskQueue* microtask_queue, Type type);
~MicrotasksScope();
/**
......@@ -7432,6 +7509,7 @@ class V8_EXPORT Isolate {
class V8_EXPORT SuppressMicrotaskExecutionScope {
public:
explicit SuppressMicrotaskExecutionScope(Isolate* isolate);
explicit SuppressMicrotaskExecutionScope(MicrotaskQueue* microtask_queue);
~SuppressMicrotaskExecutionScope();
// Prevent copying of Scope objects.
......@@ -8107,18 +8185,18 @@ class V8_EXPORT Isolate {
void SetPromiseRejectCallback(PromiseRejectCallback callback);
/**
* Runs the Microtask Work Queue until empty
* Runs the default MicrotaskQueue until it gets empty.
* Any exceptions thrown by microtask callbacks are swallowed.
*/
void RunMicrotasks();
/**
* Enqueues the callback to the Microtask Work Queue
* Enqueues the callback to the default MicrotaskQueue
*/
void EnqueueMicrotask(Local<Function> microtask);
/**
* Enqueues the callback to the Microtask Work Queue
* Enqueues the callback to the default MicrotaskQueue
*/
void EnqueueMicrotask(MicrotaskCallback callback, void* data = nullptr);
......@@ -8134,14 +8212,15 @@ class V8_EXPORT Isolate {
/**
* Adds a callback to notify the host application after
* microtasks were run. The callback is triggered by explicit RunMicrotasks
* call or automatic microtasks execution (see SetAutorunMicrotasks).
* microtasks were run on the default MicrotaskQueue. The callback is
* triggered by explicit RunMicrotasks call or automatic microtasks execution
* (see SetMicrotaskPolicy).
*
* Callback will trigger even if microtasks were attempted to run,
* but the microtasks queue was empty and no single microtask was actually
* executed.
*
* Executing scriptsinside the callback will not re-trigger microtasks and
* Executing scripts inside the callback will not re-trigger microtasks and
* the callback.
*/
void AddMicrotasksCompletedCallback(MicrotasksCompletedCallback callback);
......@@ -9172,7 +9251,8 @@ class V8_EXPORT Context {
MaybeLocal<ObjectTemplate> global_template = MaybeLocal<ObjectTemplate>(),
MaybeLocal<Value> global_object = MaybeLocal<Value>(),
DeserializeInternalFieldsCallback internal_fields_deserializer =
DeserializeInternalFieldsCallback());
DeserializeInternalFieldsCallback(),
MicrotaskQueue* microtask_queue = nullptr);
/**
* Create a new context from a (non-default) context snapshot. There
......@@ -9192,13 +9272,13 @@ class V8_EXPORT Context {
*
* \param global_object See v8::Context::New.
*/
static MaybeLocal<Context> FromSnapshot(
Isolate* isolate, size_t context_snapshot_index,
DeserializeInternalFieldsCallback embedder_fields_deserializer =
DeserializeInternalFieldsCallback(),
ExtensionConfiguration* extensions = nullptr,
MaybeLocal<Value> global_object = MaybeLocal<Value>());
MaybeLocal<Value> global_object = MaybeLocal<Value>(),
MicrotaskQueue* microtask_queue = nullptr);
/**
* Returns an global object that isn't backed by an actual context.
......
This diff is collapsed.
......@@ -21,6 +21,7 @@
#include "src/heap/heap-inl.h"
#include "src/isolate-inl.h"
#include "src/math-random.h"
#include "src/microtask-queue.h"
#include "src/objects/api-callbacks.h"
#include "src/objects/arguments.h"
#include "src/objects/builtin-function-id.h"
......@@ -139,7 +140,8 @@ class Genesis {
Genesis(Isolate* isolate, MaybeHandle<JSGlobalProxy> maybe_global_proxy,
v8::Local<v8::ObjectTemplate> global_proxy_template,
size_t context_snapshot_index,
v8::DeserializeEmbedderFieldsCallback embedder_fields_deserializer);
v8::DeserializeEmbedderFieldsCallback embedder_fields_deserializer,
v8::MicrotaskQueue* microtask_queue);
Genesis(Isolate* isolate, MaybeHandle<JSGlobalProxy> maybe_global_proxy,
v8::Local<v8::ObjectTemplate> global_proxy_template);
~Genesis() = default;
......@@ -302,12 +304,14 @@ Handle<Context> Bootstrapper::CreateEnvironment(
MaybeHandle<JSGlobalProxy> maybe_global_proxy,
v8::Local<v8::ObjectTemplate> global_proxy_template,
v8::ExtensionConfiguration* extensions, size_t context_snapshot_index,
v8::DeserializeEmbedderFieldsCallback embedder_fields_deserializer) {
v8::DeserializeEmbedderFieldsCallback embedder_fields_deserializer,
v8::MicrotaskQueue* microtask_queue) {
HandleScope scope(isolate_);
Handle<Context> env;
{
Genesis genesis(isolate_, maybe_global_proxy, global_proxy_template,
context_snapshot_index, embedder_fields_deserializer);
context_snapshot_index, embedder_fields_deserializer,
microtask_queue);
env = genesis.result();
if (env.is_null() || !InstallExtensions(env, extensions)) {
return Handle<Context>();
......@@ -5504,7 +5508,8 @@ Genesis::Genesis(
Isolate* isolate, MaybeHandle<JSGlobalProxy> maybe_global_proxy,
v8::Local<v8::ObjectTemplate> global_proxy_template,
size_t context_snapshot_index,
v8::DeserializeEmbedderFieldsCallback embedder_fields_deserializer)
v8::DeserializeEmbedderFieldsCallback embedder_fields_deserializer,
v8::MicrotaskQueue* microtask_queue)
: isolate_(isolate), active_(isolate->bootstrapper()) {
RuntimeCallTimerScope rcs_timer(isolate, RuntimeCallCounterId::kGenesis);
result_ = Handle<Context>::null();
......@@ -5600,7 +5605,9 @@ Genesis::Genesis(
}
}
native_context()->set_microtask_queue(isolate->default_microtask_queue());
native_context()->set_microtask_queue(
microtask_queue ? static_cast<MicrotaskQueue*>(microtask_queue)
: isolate->default_microtask_queue());
// Install experimental natives. Do not include them into the
// snapshot as we should be able to turn them off at runtime. Re-installing
......
......@@ -57,7 +57,8 @@ class Bootstrapper final {
MaybeHandle<JSGlobalProxy> maybe_global_proxy,
v8::Local<v8::ObjectTemplate> global_object_template,
v8::ExtensionConfiguration* extensions, size_t context_snapshot_index,
v8::DeserializeEmbedderFieldsCallback embedder_fields_deserializer);
v8::DeserializeEmbedderFieldsCallback embedder_fields_deserializer,
v8::MicrotaskQueue* microtask_queue);
Handle<JSGlobalProxy> NewRemoteContext(
MaybeHandle<JSGlobalProxy> maybe_global_proxy,
......
......@@ -7,7 +7,7 @@
#include <stddef.h>
#include <algorithm>
#include "src/api.h"
#include "src/api-inl.h"
#include "src/base/logging.h"
#include "src/handles-inl.h"
#include "src/isolate.h"
......@@ -77,6 +77,26 @@ Address MicrotaskQueue::CallEnqueueMicrotask(Isolate* isolate,
return ReadOnlyRoots(isolate).undefined_value().ptr();
}
void MicrotaskQueue::EnqueueMicrotask(v8::Isolate* v8_isolate,
v8::Local<Function> function) {
Isolate* isolate = reinterpret_cast<Isolate*>(v8_isolate);
HandleScope scope(isolate);
Handle<CallableTask> microtask = isolate->factory()->NewCallableTask(
Utils::OpenHandle(*function), isolate->native_context());
EnqueueMicrotask(*microtask);
}
void MicrotaskQueue::EnqueueMicrotask(v8::Isolate* v8_isolate,
v8::MicrotaskCallback callback,
void* data) {
Isolate* isolate = reinterpret_cast<Isolate*>(v8_isolate);
HandleScope scope(isolate);
Handle<CallbackTask> microtask = isolate->factory()->NewCallbackTask(
isolate->factory()->NewForeign(reinterpret_cast<Address>(callback)),
isolate->factory()->NewForeign(reinterpret_cast<Address>(data)));
EnqueueMicrotask(*microtask);
}
void MicrotaskQueue::EnqueueMicrotask(Microtask microtask) {
if (size_ == capacity_) {
// Keep the capacity of |ring_buffer_| power of 2, so that the JIT
......@@ -90,6 +110,14 @@ void MicrotaskQueue::EnqueueMicrotask(Microtask microtask) {
++size_;
}
void MicrotaskQueue::PerformCheckpoint(v8::Isolate* v8_isolate) {
if (!IsRunningMicrotasks() && !GetMicrotasksScopeDepth() &&
!HasMicrotasksSuppressions()) {
Isolate* isolate = reinterpret_cast<Isolate*>(v8_isolate);
RunMicrotasks(isolate);
}
}
namespace {
class SetIsRunningMicrotasks {
......@@ -184,26 +212,29 @@ void MicrotaskQueue::IterateMicrotasks(RootVisitor* visitor) {
}
void MicrotaskQueue::AddMicrotasksCompletedCallback(
MicrotasksCompletedCallback callback) {
auto pos = std::find(microtasks_completed_callbacks_.begin(),
microtasks_completed_callbacks_.end(), callback);
MicrotasksCompletedCallbackWithData callback, void* data) {
CallbackWithData callback_with_data(callback, data);
auto pos =
std::find(microtasks_completed_callbacks_.begin(),
microtasks_completed_callbacks_.end(), callback_with_data);
if (pos != microtasks_completed_callbacks_.end()) return;
microtasks_completed_callbacks_.push_back(callback);
microtasks_completed_callbacks_.push_back(callback_with_data);
}
void MicrotaskQueue::RemoveMicrotasksCompletedCallback(
MicrotasksCompletedCallback callback) {
auto pos = std::find(microtasks_completed_callbacks_.begin(),
microtasks_completed_callbacks_.end(), callback);
MicrotasksCompletedCallbackWithData callback, void* data) {
CallbackWithData callback_with_data(callback, data);
auto pos =
std::find(microtasks_completed_callbacks_.begin(),
microtasks_completed_callbacks_.end(), callback_with_data);
if (pos == microtasks_completed_callbacks_.end()) return;
microtasks_completed_callbacks_.erase(pos);
}
void MicrotaskQueue::FireMicrotasksCompletedCallback(Isolate* isolate) const {
std::vector<MicrotasksCompletedCallback> callbacks(
microtasks_completed_callbacks_);
std::vector<CallbackWithData> callbacks(microtasks_completed_callbacks_);
for (auto& callback : callbacks) {
callback(reinterpret_cast<v8::Isolate*>(isolate));
callback.first(reinterpret_cast<v8::Isolate*>(isolate), callback.second);
}
}
......
......@@ -21,7 +21,7 @@ class Microtask;
class Object;
class RootVisitor;
class V8_EXPORT_PRIVATE MicrotaskQueue {
class V8_EXPORT_PRIVATE MicrotaskQueue final : public v8::MicrotaskQueue {
public:
static void SetUpDefaultMicrotaskQueue(Isolate* isolate);
static std::unique_ptr<MicrotaskQueue> New(Isolate* isolate);
......@@ -35,7 +35,19 @@ class V8_EXPORT_PRIVATE MicrotaskQueue {
intptr_t microtask_queue_pointer,
Address raw_microtask);
// v8::MicrotaskQueue implementations.
void EnqueueMicrotask(v8::Isolate* isolate,
v8::Local<Function> microtask) override;
void EnqueueMicrotask(v8::Isolate* isolate, v8::MicrotaskCallback callback,
void* data) override;
void PerformCheckpoint(v8::Isolate* isolate) override;
void EnqueueMicrotask(Microtask microtask);
void AddMicrotasksCompletedCallback(
MicrotasksCompletedCallbackWithData callback, void* data) override;
void RemoveMicrotasksCompletedCallback(
MicrotasksCompletedCallbackWithData callback, void* data) override;
bool IsRunningMicrotasks() const override { return is_running_microtasks_; }
// Runs all queued Microtasks.
// Returns -1 if the execution is terminating, otherwise, returns the number
......@@ -78,7 +90,6 @@ class V8_EXPORT_PRIVATE MicrotaskQueue {
void AddMicrotasksCompletedCallback(MicrotasksCompletedCallback callback);
void RemoveMicrotasksCompletedCallback(MicrotasksCompletedCallback callback);
void FireMicrotasksCompletedCallback(Isolate* isolate) const;
bool IsRunningMicrotasks() const { return is_running_microtasks_; }
intptr_t capacity() const { return capacity_; }
intptr_t size() const { return size_; }
......@@ -128,7 +139,9 @@ class V8_EXPORT_PRIVATE MicrotaskQueue {
v8::MicrotasksPolicy microtasks_policy_ = v8::MicrotasksPolicy::kAuto;
bool is_running_microtasks_ = false;
std::vector<MicrotasksCompletedCallback> microtasks_completed_callbacks_;
using CallbackWithData =
std::pair<MicrotasksCompletedCallbackWithData, void*>;
std::vector<CallbackWithData> microtasks_completed_callbacks_;
};
} // namespace internal
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment