#include "external_copy/external_copy.h" #include "isolate/class_handle.h" #include "isolate/util.h" #include "lib/lockable.h" #include "callback.h" #include "reference_handle.h" #include "transferable.h" #include "transferable.h" #include "external_copy_handle.h" #include using namespace v8; namespace ivm { namespace { // Shared state for `TransferablePromise` and `TransferablePromiseHolder` struct TransferablePromiseStateStruct { std::shared_ptr value; // This would be a good case for a unique_ptr version of RemoteHandle std::deque> waiting; bool did_throw = false; bool resolved = false; }; using TransferablePromiseState = lockable_t; // Responsible for waiting for the promise in the isolate in which it was created class TransferablePromiseHolder final : public ClassHandle { public: explicit TransferablePromiseHolder( std::shared_ptr state, TransferOptions transfer_options ) : state{std::move(state)}, transfer_options{transfer_options} {} TransferablePromiseHolder(const TransferablePromiseHolder&) = delete; ~TransferablePromiseHolder() final { Save(true, [&]() { return std::make_shared(ExternalCopyError::ErrorType::Error, "Promise was abandoned"); }); } auto operator=(const TransferablePromiseHolder&) = delete; static auto Definition() -> v8::Local { return MakeClass("PromiseHolder", nullptr); } void Accept(Local promise) { auto state = promise->State(); if (state == Promise::PromiseState::kPending) { auto* isolate = Isolate::GetCurrent(); auto context = isolate->GetCurrentContext(); auto handle = This(); promise = Unmaybe(promise->Then(context, Unmaybe( Function::New(context, FreeFunctionWithData{}.callback, handle) ))); Unmaybe(promise->Catch(context, Unmaybe( Function::New(context, FreeFunctionWithData{}.callback, handle) ))); } else { if (state == Promise::PromiseState::kFulfilled) { Resolved(*this, promise->Result()); } else { IsolateEnvironment::GetCurrent().PromiseWasHandled(promise); Rejected(*this, promise->Result()); } } } static void Resolved(TransferablePromiseHolder& that, Local value) { that.Save(false, [&]() { return TransferOut(value, that.transfer_options); }); } private: template void Save(bool did_throw, Function callback) { std::shared_ptr resolved_value; auto pending_tasks = [&]() -> std::deque> { auto lock = state->write(); if (!lock->resolved) { lock->resolved = true; FunctorRunners::RunCatchExternal(Isolate::GetCurrent()->GetCurrentContext(), [&]() { lock->value = callback(); lock->did_throw = did_throw; }, [&](std::unique_ptr error) { lock->value = std::move(error); did_throw = lock->did_throw = true; }); } resolved_value = lock->value; return std::exchange(lock->waiting, {}); }(); for (auto& resolver : pending_tasks) { auto holder = resolver.GetIsolateHolder(); holder->ScheduleTask( std::make_unique(std::move(resolver), resolved_value, did_throw), false, true); } } static void Rejected(TransferablePromiseHolder& that, Local value) { that.Save(true, [&]() { return ExternalCopy::CopyThrownValue(value); }); } struct ResolveTask : Runnable { ResolveTask(RemoteTuple resolver, std::shared_ptr value, bool did_throw) : resolver{std::move(resolver)}, value{std::move(value)}, did_throw{did_throw} {} void Run() final { auto context = this->resolver.Deref<1>(); Context::Scope context_scope{context}; auto resolver = this->resolver.Deref<0>(); if (did_throw) { Unmaybe(resolver->Reject(context, value->TransferIn())); } else { Unmaybe(resolver->Resolve(context, value->TransferIn())); } } RemoteTuple resolver; std::shared_ptr value; bool did_throw; }; std::shared_ptr state; TransferOptions transfer_options; }; // Internal ivm promise transferable class TransferablePromise : public Transferable { public: TransferablePromise(Local promise, TransferOptions transfer_options) : state{std::make_shared()} { MakeHolder(transfer_options).Accept(promise); } TransferablePromise(Local value, TransferOptions transfer_options) : state{std::make_shared()} { TransferablePromiseHolder::Resolved(MakeHolder(transfer_options), value); } auto TransferIn() -> Local final { auto* isolate = Isolate::GetCurrent(); auto context = isolate->GetCurrentContext(); auto resolver = Unmaybe(Promise::Resolver::New(context)); auto lock = state->write(); if (lock->resolved) { if (lock->did_throw) { Unmaybe(resolver->Reject(context, lock->value->TransferIn())); } else { Unmaybe(resolver->Resolve(context, lock->value->TransferIn())); } } else { lock->waiting.emplace_back(resolver, context); } return resolver->GetPromise(); } private: auto MakeHolder(TransferOptions transfer_options) -> TransferablePromiseHolder& { transfer_options.promise = false; auto holder = ClassHandle::NewInstance(state, transfer_options); auto* object = ClassHandle::Unwrap(holder); return *object; } std::shared_ptr state; }; } // anonymous namespace TransferOptions::TransferOptions(Local options, Type fallback) : fallback{fallback} { ParseOptions(options); } TransferOptions::TransferOptions(MaybeLocal maybe_options, Type fallback) : fallback{fallback} { Local options; if (maybe_options.ToLocal(&options)) { ParseOptions(options); } } void TransferOptions::TransferOptions::ParseOptions(Local options) { bool copy = ReadOption(options, StringTable::Get().copy, false); bool externalCopy = ReadOption(options, StringTable::Get().externalCopy, false); bool reference = ReadOption(options, StringTable::Get().reference, false); if ((copy && externalCopy) || (copy && reference) || (externalCopy && reference)) { throw RuntimeTypeError("Only one of `copy`, `externalCopy`, or `reference` may be set"); } if (copy) { type = Type::Copy; } else if (externalCopy) { type = Type::ExternalCopy; } else if (reference) { type = Type::Reference; } promise = ReadOption(options, StringTable::Get().promise, false); } auto OptionalTransferOut(Local value, TransferOptions options) -> std::unique_ptr { auto TransferWithType = [&](TransferOptions::Type type) -> std::unique_ptr { switch (type) { default: return nullptr; case TransferOptions::Type::Copy: return ExternalCopy::Copy(value); case TransferOptions::Type::ExternalCopy: return std::make_unique(ExternalCopy::Copy(value)); case TransferOptions::Type::DeepReference: return std::make_unique(value, true); case TransferOptions::Type::Reference: return std::make_unique(value); } }; if (options.promise) { if (value->IsPromise()) { return std::make_unique(value.As(), options); } else { return std::make_unique(value, options); } } switch (options.type) { case TransferOptions::Type::None: { if (value->IsObject()) { auto* ptr = ClassHandle::Unwrap(value.As()); if (ptr != nullptr) { return ptr->TransferOut(); } if (value->IsFunction()) { return std::make_unique(value.As()); } } auto result = ExternalCopy::CopyIfPrimitive(value); if (result) { return result; } return TransferWithType(options.fallback); } default: return TransferWithType(options.type); } } auto TransferOut(Local value, TransferOptions options) -> std::unique_ptr { auto copy = OptionalTransferOut(value, options); if (!copy) { throw RuntimeTypeError("A non-transferable value was passed"); } return copy; } } // namespace ivm