[shared-struct] Use PersistentHandles to convey shared values

Shared values need to be kept alive while being conveyed across threads (i.e. by
postMessage). Currently they are meant to be conveyed through the
serializer/deserializer by the embedder via API. This both clunky and
the embedder has no good choice for what to do, because the most natural
choice is v8::Global and it is not designed to be threadsafe.

This CL removes the API and transparently handles conveying shared
values by using a wrapper around PersistentHandles called
SharedValueConveyors. Any isolate can own the conveyor provided it
outlives the receipt of the message by the receiving isolate. For
simpler lifetime management, the shared isolate currently owns all
conveyors.

Bug: v8:12547
Change-Id: I8f71b2faa0f8a1973f8b97ffccf4f5ad230f4e16
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3857453
Reviewed-by: Jakob Linke <jgruber@chromium.org>
Reviewed-by: Dominik Inführ <dinfuehr@chromium.org>
Commit-Queue: Shu-yu Guo <syg@chromium.org>
Reviewed-by: Michael Lippautz <mlippautz@chromium.org>
Reviewed-by: Camillo Bruni <cbruni@chromium.org>
Cr-Commit-Position: refs/heads/main@{#82795}
This commit is contained in:
Shu-yu Guo 2022-08-29 11:13:50 -07:00 committed by V8 LUCI CQ
parent 76d61b2195
commit 7524089264
13 changed files with 241 additions and 103 deletions

View File

@ -1389,6 +1389,8 @@ filegroup(
"src/handles/maybe-handles.h",
"src/handles/persistent-handles.cc",
"src/handles/persistent-handles.h",
"src/handles/shared-object-conveyors.cc",
"src/handles/shared-object-conveyors.h",
"src/heap/base/active-system-pages.cc",
"src/heap/base/active-system-pages.h",
"src/heap/allocation-observer.cc",

View File

@ -3083,6 +3083,7 @@ v8_header_set("v8_internal_headers") {
"src/handles/maybe-handles-inl.h",
"src/handles/maybe-handles.h",
"src/handles/persistent-handles.h",
"src/handles/shared-object-conveyors.h",
"src/heap/allocation-observer.h",
"src/heap/allocation-result.h",
"src/heap/allocation-stats.h",
@ -4487,6 +4488,7 @@ v8_source_set("v8_base_without_compiler") {
"src/handles/handles.cc",
"src/handles/local-handles.cc",
"src/handles/persistent-handles.cc",
"src/handles/shared-object-conveyors.cc",
"src/heap/allocation-observer.cc",
"src/heap/array-buffer-sweeper.cc",
"src/heap/base-space.cc",

View File

@ -69,21 +69,10 @@ class V8_EXPORT ValueSerializer {
Isolate* isolate, Local<WasmModuleObject> module);
/**
* Returns whether shared values are supported. GetSharedValueId is only
* called if SupportsSharedValues() returns true.
* Returns whether conveying shared values are supported.
*/
virtual bool SupportsSharedValues() const;
/**
* Called when the ValueSerializer serializes a value that is shared across
* Isolates. The embedder must return an ID for the object. This function
* must be idempotent for the same object. When deserializing, the ID will
* be passed to ValueDeserializer::Delegate::GetSharedValueFromId as
* |shared_value_id|.
*/
virtual Maybe<uint32_t> GetSharedValueId(Isolate* isolate,
Local<Value> shared_value);
/**
* Allocates memory for the buffer of at least the size provided. The actual
* size (which may be greater or equal) is written to |actual_size|. If no
@ -196,17 +185,9 @@ class V8_EXPORT ValueDeserializer {
Isolate* isolate, uint32_t clone_id);
/**
* Returns whether shared values are supported. GetSharedValueFromId is only
* called if SupportsSharedValues() returns true.
* Returns whether conveying shared values are supported.
*/
virtual bool SupportsSharedValues() const;
/**
* Get a value shared across Isolates given a shared_value_id provided by
* ValueSerializer::Delegate::GetSharedValueId.
*/
virtual MaybeLocal<Value> GetSharedValueFromId(Isolate* isolate,
uint32_t shared_value_id);
};
ValueDeserializer(Isolate* isolate, const uint8_t* data, size_t size);

View File

@ -3355,15 +3355,6 @@ Maybe<uint32_t> ValueSerializer::Delegate::GetWasmModuleTransferId(
bool ValueSerializer::Delegate::SupportsSharedValues() const { return false; }
Maybe<uint32_t> ValueSerializer::Delegate::GetSharedValueId(
Isolate* v8_isolate, Local<Value> shared_value) {
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(v8_isolate);
i_isolate->ScheduleThrow(*i_isolate->factory()->NewError(
i_isolate->error_function(), i::MessageTemplate::kDataCloneError,
Utils::OpenHandle(*shared_value)));
return Nothing<uint32_t>();
}
void* ValueSerializer::Delegate::ReallocateBufferMemory(void* old_buffer,
size_t size,
size_t* actual_size) {
@ -3455,15 +3446,6 @@ MaybeLocal<WasmModuleObject> ValueDeserializer::Delegate::GetWasmModuleFromId(
bool ValueDeserializer::Delegate::SupportsSharedValues() const { return false; }
MaybeLocal<Value> ValueDeserializer::Delegate::GetSharedValueFromId(
Isolate* v8_isolate, uint32_t shared_value_id) {
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(v8_isolate);
i_isolate->ScheduleThrow(*i_isolate->factory()->NewError(
i_isolate->error_function(),
i::MessageTemplate::kDataCloneDeserializationError));
return MaybeLocal<Value>();
}
MaybeLocal<SharedArrayBuffer>
ValueDeserializer::Delegate::GetSharedArrayBufferFromId(Isolate* v8_isolate,
uint32_t id) {

View File

@ -5252,28 +5252,6 @@ class Serializer : public ValueSerializer::Delegate {
bool SupportsSharedValues() const override { return true; }
Maybe<uint32_t> GetSharedValueId(Isolate* isolate,
Local<Value> shared_value) override {
DCHECK_NOT_NULL(data_);
for (size_t index = 0; index < data_->shared_values_.size(); ++index) {
if (data_->shared_values_[index] == shared_value) {
return Just<uint32_t>(static_cast<uint32_t>(index));
}
}
size_t index = data_->shared_values_.size();
// Shared values in transit are kept alive by global handles in the shared
// isolate. No code ever runs in the shared Isolate, so locking it does not
// contend with long-running tasks.
{
Isolate* shared_isolate = reinterpret_cast<Isolate*>(
reinterpret_cast<i::Isolate*>(isolate)->shared_isolate());
v8::Locker locker(shared_isolate);
data_->shared_values_.emplace_back(shared_isolate, shared_value);
}
return Just<uint32_t>(static_cast<uint32_t>(index));
}
private:
Maybe<bool> PrepareTransfer(Local<Context> context, Local<Value> transfer) {
if (transfer->IsArray()) {
@ -5341,13 +5319,6 @@ class Serializer : public ValueSerializer::Delegate {
size_t current_memory_usage_;
};
void SerializationData::ClearSharedValuesUnderLockIfNeeded(
Isolate* shared_isolate) {
if (shared_values_.empty()) return;
v8::Locker locker(shared_isolate);
shared_values_.clear();
}
class Deserializer : public ValueDeserializer::Delegate {
public:
Deserializer(Isolate* isolate, std::unique_ptr<SerializationData> data)
@ -5357,12 +5328,6 @@ class Deserializer : public ValueDeserializer::Delegate {
deserializer_.SetSupportsLegacyWireFormat(true);
}
~Deserializer() {
Isolate* shared_isolate = reinterpret_cast<Isolate*>(
reinterpret_cast<i::Isolate*>(isolate_)->shared_isolate());
data_->ClearSharedValuesUnderLockIfNeeded(shared_isolate);
}
Deserializer(const Deserializer&) = delete;
Deserializer& operator=(const Deserializer&) = delete;
@ -5402,15 +5367,6 @@ class Deserializer : public ValueDeserializer::Delegate {
bool SupportsSharedValues() const override { return true; }
MaybeLocal<Value> GetSharedValueFromId(Isolate* isolate,
uint32_t id) override {
DCHECK_NOT_NULL(data_);
if (id < data_->shared_values().size()) {
return data_->shared_values().at(id).Get(isolate);
}
return MaybeLocal<Value>();
}
private:
Isolate* isolate_;
ValueDeserializer deserializer_;

View File

@ -149,11 +149,6 @@ class SerializationData {
const std::vector<CompiledWasmModule>& compiled_wasm_modules() {
return compiled_wasm_modules_;
}
const std::vector<v8::Global<v8::Value>>& shared_values() {
return shared_values_;
}
void ClearSharedValuesUnderLockIfNeeded(Isolate* shared_isolate);
private:
struct DataDeleter {
@ -165,7 +160,6 @@ class SerializationData {
std::vector<std::shared_ptr<v8::BackingStore>> backing_stores_;
std::vector<std::shared_ptr<v8::BackingStore>> sab_backing_stores_;
std::vector<CompiledWasmModule> compiled_wasm_modules_;
std::vector<v8::Global<v8::Value>> shared_values_;
private:
friend class Serializer;

View File

@ -61,6 +61,7 @@
#include "src/execution/vm-state-inl.h"
#include "src/handles/global-handles-inl.h"
#include "src/handles/persistent-handles.h"
#include "src/handles/shared-object-conveyors.h"
#include "src/heap/heap-inl.h"
#include "src/heap/heap.h"
#include "src/heap/local-heap.h"
@ -3428,6 +3429,8 @@ Isolate::Isolate(std::unique_ptr<i::IsolateAllocator> isolate_allocator,
#endif
next_module_async_evaluating_ordinal_(
SourceTextModule::kFirstAsyncEvaluatingOrdinal),
shared_object_conveyors_(is_shared ? new SharedObjectConveyors(this)
: nullptr),
cancelable_task_manager_(new CancelableTaskManager()) {
TRACE_ISOLATE(constructor);
CheckIsolateLayout();

View File

@ -134,6 +134,7 @@ class ReadOnlyArtifacts;
class RegExpStack;
class RootVisitor;
class SetupIsolateDelegate;
class SharedObjectConveyors;
class Simulator;
class SnapshotData;
class StringForwardingTable;
@ -1999,6 +2000,11 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory {
bool owns_shareable_data() { return owns_shareable_data_; }
SharedObjectConveyors* GetSharedObjectConveyors() const {
if (is_shared()) return shared_object_conveyors_.get();
return shared_isolate()->shared_object_conveyors_.get();
}
bool log_object_relocation() const { return log_object_relocation_; }
// TODO(pthier): Unify with owns_shareable_data() once the flag
@ -2389,6 +2395,12 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory {
// Otherwise this is populated for all Isolates.
std::vector<Object> shared_heap_object_cache_;
// When sharing data among isolates, an isolate can send and receive shared
// objects with the ValueSerializer and ValueDeserializer. Shared objects that
// are in transit use PersistentHandles owned by this data structure to keep
// them alive.
std::unique_ptr<SharedObjectConveyors> shared_object_conveyors_;
// Used during builtins compilation to build the builtins constants table,
// which is stored on the root list prior to serialization.
BuiltinsConstantsTableBuilder* builtins_constants_table_builder_ = nullptr;

View File

@ -45,6 +45,8 @@ class PersistentHandles {
return NewHandle(*obj);
}
Isolate* isolate() const { return isolate_; }
#ifdef DEBUG
V8_EXPORT_PRIVATE bool Contains(Address* location);
#endif

View File

@ -0,0 +1,78 @@
// Copyright 2022 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/handles/shared-object-conveyors.h"
#include "src/objects/objects-inl.h"
namespace v8 {
namespace internal {
SharedObjectConveyorHandles::SharedObjectConveyorHandles(Isolate* isolate,
uint32_t id)
: id(id), persistent_handles_(isolate->NewPersistentHandles()) {}
uint32_t SharedObjectConveyorHandles::Persist(HeapObject shared_object) {
DCHECK(shared_object.IsShared());
uint32_t id = static_cast<uint32_t>(shared_objects_.size());
shared_objects_.push_back(persistent_handles_->NewHandle(shared_object));
return id;
}
HeapObject SharedObjectConveyorHandles::GetPersisted(uint32_t object_id) {
DCHECK_LT(object_id, shared_objects_.size());
return *shared_objects_[object_id];
}
void SharedObjectConveyorHandles::Delete() {
persistent_handles_->isolate()->GetSharedObjectConveyors()->DeleteConveyor(
id);
}
SharedObjectConveyorHandles* SharedObjectConveyors::NewConveyor() {
base::MutexGuard guard(&conveyors_mutex_);
uint32_t id;
if (conveyors_.empty()) {
id = 0;
} else {
size_t i;
for (i = 0; i < conveyors_.size(); i++) {
if (conveyors_[i] == nullptr) break;
}
id = static_cast<uint32_t>(i);
}
auto handles = std::make_unique<SharedObjectConveyorHandles>(isolate_, id);
if (id < conveyors_.size()) {
conveyors_[id] = std::move(handles);
} else {
DCHECK_EQ(id, conveyors_.size());
conveyors_.push_back(std::move(handles));
}
return conveyors_[id].get();
}
SharedObjectConveyorHandles* SharedObjectConveyors::GetConveyor(
uint32_t conveyor_id) {
base::MutexGuard guard(&conveyors_mutex_);
DcheckIsValidConveyorId(conveyor_id);
return conveyors_[conveyor_id].get();
}
void SharedObjectConveyors::DeleteConveyor(uint32_t conveyor_id) {
base::MutexGuard guard(&conveyors_mutex_);
DcheckIsValidConveyorId(conveyor_id);
conveyors_[conveyor_id].reset(nullptr);
}
void SharedObjectConveyors::DcheckIsValidConveyorId(uint32_t conveyor_id) {
DCHECK_LT(conveyor_id, conveyors_.size());
DCHECK_NOT_NULL(conveyors_[conveyor_id].get());
DCHECK_EQ(conveyors_[conveyor_id]->id, conveyor_id);
}
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,80 @@
// Copyright 2022 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef V8_HANDLES_SHARED_OBJECT_CONVEYORS_H_
#define V8_HANDLES_SHARED_OBJECT_CONVEYORS_H_
#include <memory>
#include <vector>
#include "src/handles/persistent-handles.h"
namespace v8 {
namespace internal {
class PersistentHandles;
// Wrapper around PersistentHandles that is used to convey shared objects
// (i.e. keep them alive) from a ValueSerializer to a ValueDeserializer for APIs
// like postMessage.
//
// The conveyor must be allocated in an isolate that remains alive until the
// ValueDeserializer in the receiving isolate finishes processing the message.
//
// Each conveyor has an id that is stable across GCs. Each shared object that is
// conveyed is gets an id pair (conveyor_id, object_id). Once all objects in a
// conveyor are received, the conveyor is deleted and its id may be reused for
// future conveyance.
//
// TODO(v8:12547): Currently the shared isolate owns all the conveyors. Change
// the owner to the main isolate once the shared isolate is removed.
class SharedObjectConveyorHandles {
public:
SharedObjectConveyorHandles(Isolate* isolate, uint32_t id);
SharedObjectConveyorHandles(const SharedObjectConveyorHandles&) = delete;
SharedObjectConveyorHandles& operator=(const SharedObjectConveyorHandles&) =
delete;
// Persist and GetPersisted are not threadsafe. A particular conveyor is used
// by a single thread at a time, either during sending a message or receiving
// a message.
uint32_t Persist(HeapObject shared_object);
HeapObject GetPersisted(uint32_t object_id);
// Deleting conveyors is threadsafe and may be called from multiple threads.
void Delete();
const uint32_t id;
private:
std::unique_ptr<PersistentHandles> persistent_handles_;
std::vector<Handle<HeapObject>> shared_objects_;
};
// A class to own and manage conveyors. All methods are threadsafe and may be
// called from multiple threads.
class SharedObjectConveyors {
public:
explicit SharedObjectConveyors(Isolate* isolate) : isolate_(isolate) {}
SharedObjectConveyorHandles* NewConveyor();
SharedObjectConveyorHandles* GetConveyor(uint32_t conveyor_id);
private:
friend class SharedObjectConveyorHandles;
void DeleteConveyor(uint32_t conveyor_id);
void DcheckIsValidConveyorId(uint32_t conveyor_id);
Isolate* isolate_;
base::Mutex conveyors_mutex_;
std::vector<std::unique_ptr<SharedObjectConveyorHandles>> conveyors_;
};
} // namespace internal
} // namespace v8
#endif // V8_HANDLES_SHARED_OBJECT_CONVEYORS_H_

View File

@ -18,6 +18,7 @@
#include "src/handles/global-handles-inl.h"
#include "src/handles/handles-inl.h"
#include "src/handles/maybe-handles-inl.h"
#include "src/handles/shared-object-conveyors.h"
#include "src/heap/factory.h"
#include "src/numbers/conversions.h"
#include "src/objects/heap-number-inl.h"
@ -170,6 +171,8 @@ enum class SerializationTag : uint8_t {
kSharedArrayBuffer = 'u',
// A HeapObject shared across Isolates. sharedValueID:uint32_t
kSharedObject = 'p',
// The SharedObjectConveyor used to get shared objects. conveyorID:uint32_t
kSharedObjectConveyor = 'q',
// A wasm module object transfer. next value is its index.
kWasmModuleTransfer = 'w',
// The delegate is responsible for processing all following data.
@ -1105,13 +1108,20 @@ Maybe<bool> ValueSerializer::WriteSharedObject(Handle<HeapObject> object) {
DCHECK_NOT_NULL(delegate_);
DCHECK(delegate_->SupportsSharedValues());
v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate_);
Maybe<uint32_t> index =
delegate_->GetSharedValueId(v8_isolate, Utils::ToLocal(object));
RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate_, Nothing<bool>());
// The first time a shared object is serialized, a new conveyor is made and
// its id is written. This conveyor is used for every shared object in this
// serialization and subsequent deserialization session. Once deserialization
// is complete, the conveyor is deleted.
if (!shared_object_conveyor_) {
shared_object_conveyor_ =
isolate_->GetSharedObjectConveyors()->NewConveyor();
WriteTag(SerializationTag::kSharedObjectConveyor);
WriteVarint(shared_object_conveyor_->id);
}
WriteTag(SerializationTag::kSharedObject);
WriteVarint(index.FromJust());
WriteVarint(shared_object_conveyor_->Persist(*object));
return ThrowIfOutOfMemory();
}
@ -1214,6 +1224,11 @@ ValueDeserializer::~ValueDeserializer() {
if (array_buffer_transfer_map_.ToHandle(&transfer_map_handle)) {
GlobalHandles::Destroy(transfer_map_handle.location());
}
if (shared_object_conveyor_) {
shared_object_conveyor_->Delete();
shared_object_conveyor_ = nullptr;
}
}
Maybe<bool> ValueDeserializer::ReadHeader() {
@ -1554,8 +1569,13 @@ MaybeHandle<Object> ValueDeserializer::ReadObjectInternal() {
case SerializationTag::kHostObject:
return ReadHostObject();
case SerializationTag::kSharedObject:
case SerializationTag::kSharedObjectConveyor:
if (version_ >= 15 && supports_shared_values_) {
return ReadSharedObject();
if (tag == SerializationTag::kSharedObject) {
return ReadSharedObject();
}
if (!ReadSharedObjectConveyor()) return MaybeHandle<Object>();
return ReadObject();
}
// If the delegate doesn't support shared values (e.g. older version, or
// is for deserializing from storage), treat the tag as unknown.
@ -2243,21 +2263,39 @@ MaybeHandle<HeapObject> ValueDeserializer::ReadSharedObject() {
DCHECK(supports_shared_values_);
DCHECK_NOT_NULL(delegate_);
DCHECK(delegate_->SupportsSharedValues());
v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate_);
uint32_t shared_value_id;
Local<Value> shared_value;
if (!ReadVarint<uint32_t>().To(&shared_value_id) ||
!delegate_->GetSharedValueFromId(v8_isolate, shared_value_id)
.ToLocal(&shared_value)) {
uint32_t shared_object_id;
if (!ReadVarint<uint32_t>().To(&shared_object_id)) {
RETURN_EXCEPTION_IF_SCHEDULED_EXCEPTION(isolate_, HeapObject);
return MaybeHandle<HeapObject>();
}
Handle<HeapObject> shared_object =
Handle<HeapObject>::cast(Utils::OpenHandle(*shared_value));
// The conveyor must have already been gotten via the kSharedObjectConveyor
// tag.
DCHECK_NOT_NULL(shared_object_conveyor_);
Handle<HeapObject> shared_object(
shared_object_conveyor_->GetPersisted(shared_object_id), isolate_);
DCHECK(shared_object->IsShared());
return shared_object;
}
bool ValueDeserializer::ReadSharedObjectConveyor() {
STACK_CHECK(isolate_, false);
DCHECK_GE(version_, 15);
DCHECK(supports_shared_values_);
DCHECK_NOT_NULL(delegate_);
DCHECK(delegate_->SupportsSharedValues());
// This tag appears at most once per deserialization data.
DCHECK_NULL(shared_object_conveyor_);
uint32_t conveyor_id;
if (!ReadVarint<uint32_t>().To(&conveyor_id)) {
RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate_, false);
return false;
}
shared_object_conveyor_ =
isolate_->GetSharedObjectConveyors()->GetConveyor(conveyor_id);
return true;
}
MaybeHandle<JSObject> ValueDeserializer::ReadHostObject() {
if (!delegate_) return MaybeHandle<JSObject>();
STACK_CHECK(isolate_, MaybeHandle<JSObject>());

View File

@ -34,6 +34,7 @@ class JSSharedArray;
class JSSharedStruct;
class Object;
class Oddball;
class SharedObjectConveyorHandles;
class Smi;
class WasmMemoryObject;
class WasmModuleObject;
@ -186,6 +187,9 @@ class ValueSerializer {
// A similar map, for transferred array buffers.
IdentityMap<uint32_t, ZoneAllocationPolicy> array_buffer_transfer_map_;
// The conveyor used to keep shared objects alive.
SharedObjectConveyorHandles* shared_object_conveyor_ = nullptr;
};
/*
@ -309,6 +313,7 @@ class ValueDeserializer {
MaybeHandle<WasmMemoryObject> ReadWasmMemory() V8_WARN_UNUSED_RESULT;
#endif // V8_ENABLE_WEBASSEMBLY
MaybeHandle<HeapObject> ReadSharedObject() V8_WARN_UNUSED_RESULT;
bool ReadSharedObjectConveyor() V8_WARN_UNUSED_RESULT;
MaybeHandle<JSObject> ReadHostObject() V8_WARN_UNUSED_RESULT;
/*
@ -337,6 +342,9 @@ class ValueDeserializer {
// Always global handles.
Handle<FixedArray> id_map_;
MaybeHandle<SimpleNumberDictionary> array_buffer_transfer_map_;
// The conveyor used to keep shared objects alive.
SharedObjectConveyorHandles* shared_object_conveyor_ = nullptr;
};
} // namespace internal