2236 строки
68 KiB
C++
2236 строки
68 KiB
C++
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// Licensed under the MIT license.
|
|
|
|
#include "ClearScriptV8Native.h"
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// V8Platform
|
|
//-----------------------------------------------------------------------------
|
|
|
|
class V8Platform final: public v8::Platform
|
|
{
|
|
public:
|
|
|
|
static V8Platform& GetInstance();
|
|
void EnsureInitialized();
|
|
V8GlobalFlags GetGlobalFlags() const;
|
|
|
|
virtual v8::PageAllocator* GetPageAllocator() override;
|
|
virtual int NumberOfWorkerThreads() override;
|
|
virtual std::shared_ptr<v8::TaskRunner> GetForegroundTaskRunner(v8::Isolate* pIsolate, v8::TaskPriority priority) override;
|
|
virtual double MonotonicallyIncreasingTime() override;
|
|
virtual double CurrentClockTimeMillis() override;
|
|
virtual v8::TracingController* GetTracingController() override;
|
|
|
|
protected:
|
|
|
|
virtual std::unique_ptr<v8::JobHandle> CreateJobImpl(v8::TaskPriority priority, std::unique_ptr<v8::JobTask> upJobTask, const v8::SourceLocation& location) override;
|
|
virtual void PostTaskOnWorkerThreadImpl(v8::TaskPriority priority, std::unique_ptr<v8::Task> upTask, const v8::SourceLocation& location) override;
|
|
virtual void PostDelayedTaskOnWorkerThreadImpl(v8::TaskPriority priority, std::unique_ptr<v8::Task> upTask, double delayInSeconds, const v8::SourceLocation& location) override;
|
|
|
|
private:
|
|
|
|
V8Platform();
|
|
|
|
static V8Platform ms_Instance;
|
|
std::unique_ptr<v8::PageAllocator> m_upPageAllocator;
|
|
OnceFlag m_InitializationFlag;
|
|
V8GlobalFlags m_GlobalFlags;
|
|
v8::TracingController m_TracingController;
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
V8Platform& V8Platform::GetInstance()
|
|
{
|
|
return ms_Instance;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
V8GlobalFlags V8Platform::GetGlobalFlags() const
|
|
{
|
|
return m_GlobalFlags;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8Platform::EnsureInitialized()
|
|
{
|
|
m_InitializationFlag.CallOnce([this]
|
|
{
|
|
v8::V8::InitializePlatform(&ms_Instance);
|
|
|
|
m_GlobalFlags = V8_SPLIT_PROXY_MANAGED_INVOKE_NOTHROW(V8GlobalFlags, GetGlobalFlags);
|
|
std::vector<std::string> flagStrings;
|
|
flagStrings.push_back("--expose_gc");
|
|
|
|
#ifdef CLEARSCRIPT_TOP_LEVEL_AWAIT_CONTROL
|
|
|
|
if (!HasFlag(globalFlags, V8GlobalFlags::EnableTopLevelAwait))
|
|
{
|
|
flagStrings.push_back("--no_harmony_top_level_await");
|
|
}
|
|
|
|
#endif // CLEARSCRIPT_TOP_LEVEL_AWAIT_CONTROL
|
|
|
|
if (HasFlag(m_GlobalFlags, V8GlobalFlags::DisableJITCompilation))
|
|
{
|
|
flagStrings.push_back("--jitless");
|
|
}
|
|
|
|
if (HasFlag(m_GlobalFlags, V8GlobalFlags::DisableBackgroundWork))
|
|
{
|
|
flagStrings.push_back("--single_threaded");
|
|
}
|
|
|
|
if (!flagStrings.empty())
|
|
{
|
|
std::string flagsString(flagStrings[0]);
|
|
for (size_t index = 1; index < flagStrings.size(); index++)
|
|
{
|
|
flagsString += " ";
|
|
flagsString += flagStrings[index];
|
|
}
|
|
|
|
v8::V8::SetFlagsFromString(flagsString.c_str(), flagsString.length());
|
|
}
|
|
|
|
ASSERT_EVAL(v8::V8::Initialize());
|
|
});
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
v8::PageAllocator* V8Platform::GetPageAllocator()
|
|
{
|
|
return m_upPageAllocator.get();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
int V8Platform::NumberOfWorkerThreads()
|
|
{
|
|
return static_cast<int>(HighResolutionClock::GetHardwareConcurrency());
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
std::shared_ptr<v8::TaskRunner> V8Platform::GetForegroundTaskRunner(v8::Isolate* pIsolate, v8::TaskPriority /*priority*/)
|
|
{
|
|
return V8IsolateImpl::GetInstanceFromIsolate(pIsolate)->GetForegroundTaskRunner();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
std::unique_ptr<v8::JobHandle> V8Platform::CreateJobImpl(v8::TaskPriority priority, std::unique_ptr<v8::JobTask> upJobTask, const v8::SourceLocation& /*location*/)
|
|
{
|
|
return v8::platform::NewDefaultJobHandle(this, priority, std::move(upJobTask), NumberOfWorkerThreads());
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8Platform::PostTaskOnWorkerThreadImpl(v8::TaskPriority /*priority*/, std::unique_ptr<v8::Task> upTask, const v8::SourceLocation& /*location*/)
|
|
{
|
|
auto pIsolate = v8::Isolate::GetCurrent();
|
|
if (pIsolate == nullptr)
|
|
{
|
|
upTask->Run();
|
|
}
|
|
else
|
|
{
|
|
V8IsolateImpl::GetInstanceFromIsolate(pIsolate)->RunTaskAsync(std::move(upTask));
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8Platform::PostDelayedTaskOnWorkerThreadImpl(v8::TaskPriority /*priority*/, std::unique_ptr<v8::Task> upTask, double delayInSeconds, const v8::SourceLocation& /*location*/)
|
|
{
|
|
auto pIsolate = v8::Isolate::GetCurrent();
|
|
if (pIsolate != nullptr)
|
|
{
|
|
V8IsolateImpl::GetInstanceFromIsolate(pIsolate)->RunTaskDelayed(std::move(upTask), delayInSeconds);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
double V8Platform::MonotonicallyIncreasingTime()
|
|
{
|
|
return HighResolutionClock::GetRelativeSeconds();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
double V8Platform::CurrentClockTimeMillis()
|
|
{
|
|
return std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(std::chrono::system_clock::now().time_since_epoch()).count();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
v8::TracingController* V8Platform::GetTracingController()
|
|
{
|
|
return &m_TracingController;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
V8Platform::V8Platform():
|
|
m_upPageAllocator(v8::platform::NewDefaultPageAllocator()),
|
|
m_GlobalFlags(V8GlobalFlags::None)
|
|
{
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
V8Platform V8Platform::ms_Instance;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// V8ForegroundTaskRunner
|
|
//-----------------------------------------------------------------------------
|
|
|
|
class V8ForegroundTaskRunner final: public v8::TaskRunner
|
|
{
|
|
PROHIBIT_COPY(V8ForegroundTaskRunner)
|
|
|
|
public:
|
|
|
|
V8ForegroundTaskRunner(V8IsolateImpl& isolateImpl);
|
|
|
|
virtual void PostTask(std::unique_ptr<v8::Task> upTask) override;
|
|
virtual void PostNonNestableTask(std::unique_ptr<v8::Task> upTask) override;
|
|
virtual void PostDelayedTask(std::unique_ptr<v8::Task> upTask, double delayInSeconds) override;
|
|
virtual void PostNonNestableDelayedTask(std::unique_ptr<v8::Task> upTask, double delayInSeconds) override;
|
|
virtual void PostIdleTask(std::unique_ptr<v8::IdleTask> upTask) override;
|
|
virtual bool IdleTasksEnabled() override;
|
|
virtual bool NonNestableTasksEnabled() const override;
|
|
virtual bool NonNestableDelayedTasksEnabled() const override;
|
|
|
|
private:
|
|
|
|
V8IsolateImpl& m_IsolateImpl;
|
|
WeakRef<V8Isolate> m_wrIsolate;
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
V8ForegroundTaskRunner::V8ForegroundTaskRunner(V8IsolateImpl& isolateImpl):
|
|
m_IsolateImpl(isolateImpl),
|
|
m_wrIsolate(isolateImpl.CreateWeakRef())
|
|
{
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8ForegroundTaskRunner::PostTask(std::unique_ptr<v8::Task> upTask)
|
|
{
|
|
auto spIsolate = m_wrIsolate.GetTarget();
|
|
if (spIsolate.IsEmpty())
|
|
{
|
|
upTask->Run();
|
|
}
|
|
else
|
|
{
|
|
m_IsolateImpl.RunTaskWithLockAsync(true /*allowNesting*/, std::move(upTask));
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8ForegroundTaskRunner::PostNonNestableTask(std::unique_ptr<v8::Task> upTask)
|
|
{
|
|
auto spIsolate = m_wrIsolate.GetTarget();
|
|
if (!spIsolate.IsEmpty())
|
|
{
|
|
m_IsolateImpl.RunTaskWithLockAsync(false /*allowNesting*/, std::move(upTask));
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8ForegroundTaskRunner::PostDelayedTask(std::unique_ptr<v8::Task> upTask, double delayInSeconds)
|
|
{
|
|
auto spIsolate = m_wrIsolate.GetTarget();
|
|
if (!spIsolate.IsEmpty())
|
|
{
|
|
m_IsolateImpl.RunTaskWithLockDelayed(true /*allowNesting*/, std::move(upTask), delayInSeconds);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8ForegroundTaskRunner::PostNonNestableDelayedTask(std::unique_ptr<v8::Task> upTask, double delayInSeconds)
|
|
{
|
|
auto spIsolate = m_wrIsolate.GetTarget();
|
|
if (!spIsolate.IsEmpty())
|
|
{
|
|
m_IsolateImpl.RunTaskWithLockDelayed(false /*allowNesting*/, std::move(upTask), delayInSeconds);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8ForegroundTaskRunner::PostIdleTask(std::unique_ptr<v8::IdleTask> /*upTask*/)
|
|
{
|
|
// unexpected call to unsupported method
|
|
std::terminate();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool V8ForegroundTaskRunner::IdleTasksEnabled()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool V8ForegroundTaskRunner::NonNestableTasksEnabled() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool V8ForegroundTaskRunner::NonNestableDelayedTasksEnabled() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// V8ArrayBufferAllocator
|
|
//-----------------------------------------------------------------------------
|
|
|
|
class V8ArrayBufferAllocator final: public v8::ArrayBuffer::Allocator
|
|
{
|
|
public:
|
|
|
|
V8ArrayBufferAllocator(V8IsolateImpl& isolateImpl);
|
|
|
|
virtual void* Allocate(size_t size) override;
|
|
virtual void* AllocateUninitialized(size_t size) override;
|
|
virtual void Free(void* pvData, size_t size) override;
|
|
|
|
private:
|
|
|
|
V8IsolateImpl& m_IsolateImpl;
|
|
WeakRef<V8Isolate> m_wrIsolate;
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
V8ArrayBufferAllocator::V8ArrayBufferAllocator(V8IsolateImpl& isolateImpl):
|
|
m_IsolateImpl(isolateImpl),
|
|
m_wrIsolate(isolateImpl.CreateWeakRef())
|
|
{
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void* V8ArrayBufferAllocator::Allocate(size_t size)
|
|
{
|
|
auto spIsolate = m_wrIsolate.GetTarget();
|
|
if (!spIsolate.IsEmpty())
|
|
{
|
|
return m_IsolateImpl.AllocateArrayBuffer(size);
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void* V8ArrayBufferAllocator::AllocateUninitialized(size_t size)
|
|
{
|
|
auto spIsolate = m_wrIsolate.GetTarget();
|
|
if (!spIsolate.IsEmpty())
|
|
{
|
|
return m_IsolateImpl.AllocateUninitializedArrayBuffer(size);
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8ArrayBufferAllocator::Free(void* pvData, size_t size)
|
|
{
|
|
auto spIsolate = m_wrIsolate.GetTarget();
|
|
if (!spIsolate.IsEmpty())
|
|
{
|
|
m_IsolateImpl.FreeArrayBuffer(pvData, size);
|
|
}
|
|
else if (pvData)
|
|
{
|
|
::free(pvData);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// V8OutputStream
|
|
//-----------------------------------------------------------------------------
|
|
|
|
class V8OutputStream final: public v8::OutputStream
|
|
{
|
|
PROHIBIT_COPY(V8OutputStream)
|
|
|
|
public:
|
|
|
|
explicit V8OutputStream(void* pvStream):
|
|
m_pvStream(pvStream)
|
|
{
|
|
}
|
|
|
|
virtual int GetChunkSize() override;
|
|
virtual WriteResult WriteAsciiChunk(char* pData, int size) override;
|
|
virtual void EndOfStream() override;
|
|
|
|
private:
|
|
|
|
void* m_pvStream;
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
int V8OutputStream::GetChunkSize()
|
|
{
|
|
return 64 * 1024;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
V8OutputStream::WriteResult V8OutputStream::WriteAsciiChunk(char* pData, int size)
|
|
{
|
|
try
|
|
{
|
|
V8_SPLIT_PROXY_MANAGED_INVOKE_VOID(WriteBytesToStream, m_pvStream, reinterpret_cast<uint8_t*>(pData), size);
|
|
return kContinue;
|
|
}
|
|
catch (const HostException& exception)
|
|
{
|
|
V8_SPLIT_PROXY_MANAGED_INVOKE_VOID(ScheduleForwardingException, exception.GetException());
|
|
return kAbort;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8OutputStream::EndOfStream()
|
|
{
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// V8IsolateImpl implementation
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#define BEGIN_ISOLATE_NATIVE_SCOPE \
|
|
{ \
|
|
DISABLE_WARNING(4456) /* declaration hides previous local declaration */ \
|
|
NativeScope t_IsolateNativeScope(*this); \
|
|
DEFAULT_WARNING(4456)
|
|
|
|
#define END_ISOLATE_NATIVE_SCOPE \
|
|
IGNORE_UNUSED(t_IsolateNativeScope); \
|
|
}
|
|
|
|
#define BEGIN_ISOLATE_SCOPE \
|
|
{ \
|
|
DISABLE_WARNING(4456) /* declaration hides previous local declaration */ \
|
|
Scope t_IsolateScope(*this); \
|
|
DEFAULT_WARNING(4456)
|
|
|
|
#define END_ISOLATE_SCOPE \
|
|
IGNORE_UNUSED(t_IsolateScope); \
|
|
}
|
|
|
|
#define BEGIN_PROMISE_HOOK_SCOPE \
|
|
{ \
|
|
DISABLE_WARNING(4456) /* declaration hides previous local declaration */ \
|
|
PromiseHookScope t_PromiseHookScope(*this); \
|
|
DEFAULT_WARNING(4456)
|
|
|
|
#define END_PROMISE_HOOK_SCOPE \
|
|
IGNORE_UNUSED(t_PromiseHookScope); \
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
static std::atomic<size_t> s_InstanceCount(0);
|
|
static const int s_ContextGroupId = 1;
|
|
static const size_t s_StackBreathingRoom = static_cast<size_t>(16 * 1024);
|
|
static size_t* const s_pMinStackLimit = reinterpret_cast<size_t*>(sizeof(size_t));
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
V8IsolateImpl::V8IsolateImpl(const StdString& name, const v8::ResourceConstraints* pConstraints, const Options& options):
|
|
m_Name(name),
|
|
m_CallWithLockLevel(0),
|
|
m_DebuggingEnabled(false),
|
|
m_MaxArrayBufferAllocation(options.MaxArrayBufferAllocation),
|
|
m_ArrayBufferAllocation(0),
|
|
m_MaxHeapSize(0),
|
|
m_HeapSizeSampleInterval(0.0),
|
|
m_HeapWatchLevel(0),
|
|
m_HeapExpansionMultiplier(options.HeapExpansionMultiplier),
|
|
m_MaxStackUsage(0),
|
|
m_EnableInterruptPropagation(false),
|
|
m_DisableHeapSizeViolationInterrupt(false),
|
|
m_CpuProfileSampleInterval(1000U),
|
|
m_StackWatchLevel(0),
|
|
m_pStackLimit(nullptr),
|
|
m_IsExecutionTerminating(false),
|
|
m_pExecutionScope(nullptr),
|
|
m_pDocumentInfo(nullptr),
|
|
m_IsOutOfMemory(false),
|
|
m_Released(false)
|
|
{
|
|
V8Platform::GetInstance().EnsureInitialized();
|
|
|
|
m_upIsolate.reset(v8::Isolate::Allocate());
|
|
m_upIsolate->SetData(0, this);
|
|
|
|
BEGIN_ADDREF_SCOPE
|
|
|
|
v8::Isolate::CreateParams params;
|
|
params.array_buffer_allocator_shared = std::make_shared<V8ArrayBufferAllocator>(*this);
|
|
if (pConstraints != nullptr)
|
|
{
|
|
params.constraints.set_max_young_generation_size_in_bytes(pConstraints->max_young_generation_size_in_bytes());
|
|
params.constraints.set_max_old_generation_size_in_bytes(pConstraints->max_old_generation_size_in_bytes());
|
|
}
|
|
|
|
v8::Isolate::Initialize(m_upIsolate.get(), params);
|
|
|
|
m_upIsolate->AddNearHeapLimitCallback(HeapExpansionCallback, this);
|
|
m_upIsolate->AddBeforeCallEnteredCallback(OnBeforeCallEntered);
|
|
|
|
BEGIN_ISOLATE_SCOPE
|
|
|
|
m_upIsolate->SetCaptureStackTraceForUncaughtExceptions(true, 64, v8::StackTrace::kDetailed);
|
|
|
|
m_hHostObjectHolderKey = CreatePersistent(CreatePrivate());
|
|
|
|
if (HasFlag(options.Flags, Flags::EnableDebugging))
|
|
{
|
|
EnableDebugging(options.DebugPort, HasFlag(options.Flags, Flags::EnableRemoteDebugging));
|
|
}
|
|
|
|
m_upIsolate->SetHostInitializeImportMetaObjectCallback(ImportMetaInitializeCallback);
|
|
if (HasFlag(options.Flags, Flags::EnableDynamicModuleImports))
|
|
{
|
|
m_upIsolate->SetHostImportModuleDynamicallyCallback(ModuleImportCallback);
|
|
}
|
|
|
|
END_ISOLATE_SCOPE
|
|
|
|
END_ADDREF_SCOPE
|
|
|
|
++s_InstanceCount;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
V8IsolateImpl* V8IsolateImpl::GetInstanceFromIsolate(v8::Isolate* pIsolate)
|
|
{
|
|
_ASSERTE(pIsolate);
|
|
return static_cast<V8IsolateImpl*>(pIsolate->GetData(0));
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
size_t V8IsolateImpl::GetInstanceCount()
|
|
{
|
|
return s_InstanceCount;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::AddContext(V8ContextImpl* pContextImpl, const V8Context::Options& options)
|
|
{
|
|
_ASSERTE(IsCurrent() && IsLocked());
|
|
|
|
if (!HasFlag(options.Flags, V8Context::Flags::EnableDebugging))
|
|
{
|
|
m_ContextEntries.emplace_back(pContextImpl);
|
|
}
|
|
else
|
|
{
|
|
m_ContextEntries.emplace_front(pContextImpl);
|
|
EnableDebugging(options.DebugPort, HasFlag(options.Flags, V8Context::Flags::EnableRemoteDebugging));
|
|
}
|
|
|
|
if (HasFlag(options.Flags, V8Context::Flags::EnableDynamicModuleImports))
|
|
{
|
|
m_upIsolate->SetHostImportModuleDynamicallyCallback(ModuleImportCallback);
|
|
}
|
|
|
|
if (m_upInspector)
|
|
{
|
|
m_upInspector->contextCreated(v8_inspector::V8ContextInfo(pContextImpl->GetContext(), s_ContextGroupId, pContextImpl->GetName().GetStringView()));
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::RemoveContext(V8ContextImpl* pContextImpl)
|
|
{
|
|
_ASSERTE(IsCurrent() && IsLocked());
|
|
|
|
if (m_upInspector)
|
|
{
|
|
m_upInspector->contextDestroyed(pContextImpl->GetContext());
|
|
}
|
|
|
|
m_ContextEntries.remove_if([pContextImpl] (const ContextEntry& contextEntry)
|
|
{
|
|
return contextEntry.pContextImpl == pContextImpl;
|
|
});
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
V8ContextImpl* V8IsolateImpl::FindContext(v8::Local<v8::Context> hContext)
|
|
{
|
|
_ASSERTE(IsCurrent() && IsLocked());
|
|
|
|
for (const auto& entry : m_ContextEntries)
|
|
{
|
|
if (entry.pContextImpl->GetContext() == hContext)
|
|
{
|
|
return entry.pContextImpl;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::EnableDebugging(int port, bool remote)
|
|
{
|
|
_ASSERTE(IsCurrent() && IsLocked());
|
|
|
|
if (!m_DebuggingEnabled)
|
|
{
|
|
const char* pVersion = v8::V8::GetVersion();
|
|
StdString version(v8_inspector::StringView(reinterpret_cast<const uint8_t*>(pVersion), strlen(pVersion)));
|
|
|
|
if (port < 1)
|
|
{
|
|
port = 9222;
|
|
}
|
|
|
|
auto wrIsolate = CreateWeakRef();
|
|
m_pvDebugAgent = HostObjectUtil::GetInstance().CreateDebugAgent(m_Name, version, port, remote, [this, wrIsolate] (IHostObjectUtil::DebugDirective directive, const StdString* pCommand)
|
|
{
|
|
auto spIsolate = wrIsolate.GetTarget();
|
|
if (!spIsolate.IsEmpty())
|
|
{
|
|
if (directive == IHostObjectUtil::DebugDirective::ConnectClient)
|
|
{
|
|
ConnectDebugClient();
|
|
}
|
|
else if ((directive == IHostObjectUtil::DebugDirective::SendCommand) && pCommand)
|
|
{
|
|
SendDebugCommand(*pCommand);
|
|
}
|
|
else if (directive == IHostObjectUtil::DebugDirective::DisconnectClient)
|
|
{
|
|
DisconnectDebugClient();
|
|
}
|
|
}
|
|
});
|
|
|
|
m_upInspector = v8_inspector::V8Inspector::create(m_upIsolate.get(), this);
|
|
|
|
m_DebuggingEnabled = true;
|
|
m_DebugPort = port;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::DisableDebugging()
|
|
{
|
|
_ASSERTE(IsCurrent() && IsLocked());
|
|
|
|
if (m_DebuggingEnabled)
|
|
{
|
|
m_upInspectorSession.reset();
|
|
m_upInspector.reset();
|
|
|
|
HostObjectUtil::GetInstance().DestroyDebugAgent(m_pvDebugAgent);
|
|
m_DebuggingEnabled = false;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
size_t V8IsolateImpl::GetMaxHeapSize()
|
|
{
|
|
return m_MaxHeapSize;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::SetMaxHeapSize(size_t value)
|
|
{
|
|
m_MaxHeapSize = value;
|
|
m_IsOutOfMemory = false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
double V8IsolateImpl::GetHeapSizeSampleInterval()
|
|
{
|
|
return m_HeapSizeSampleInterval;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::SetHeapSizeSampleInterval(double value)
|
|
{
|
|
m_HeapSizeSampleInterval = value;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
size_t V8IsolateImpl::GetMaxStackUsage()
|
|
{
|
|
return m_MaxStackUsage;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::SetMaxStackUsage(size_t value)
|
|
{
|
|
m_MaxStackUsage = value;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::AwaitDebuggerAndPause()
|
|
{
|
|
BEGIN_ISOLATE_SCOPE
|
|
|
|
if (m_DebuggingEnabled && !m_upInspectorSession)
|
|
{
|
|
auto exitReason = RunMessageLoop(RunMessageLoopReason::AwaitingDebugger);
|
|
switch (exitReason)
|
|
{
|
|
case ExitMessageLoopReason::TerminatedExecution:
|
|
throw V8Exception(V8Exception::Type::Interrupt, m_Name, StdString(SL("Script execution interrupted by host while awaiting debugger connection")), false);
|
|
|
|
case ExitMessageLoopReason::CanceledAwaitDebugger:
|
|
return;
|
|
|
|
default:
|
|
_ASSERTE(exitReason == ExitMessageLoopReason::ResumedExecution);
|
|
}
|
|
|
|
_ASSERTE(m_upInspectorSession);
|
|
if (m_upInspectorSession)
|
|
{
|
|
StdString breakReason(SL("Break on debugger connection"));
|
|
m_upInspectorSession->schedulePauseOnNextStatement(breakReason.GetStringView(), breakReason.GetStringView());
|
|
}
|
|
}
|
|
|
|
END_ISOLATE_SCOPE
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::CancelAwaitDebugger()
|
|
{
|
|
BEGIN_MUTEX_SCOPE(m_DataMutex)
|
|
|
|
if (m_optRunMessageLoopReason == RunMessageLoopReason::AwaitingDebugger)
|
|
{
|
|
m_optExitMessageLoopReason = ExitMessageLoopReason::CanceledAwaitDebugger;
|
|
m_CallWithLockQueueChanged.notify_one();
|
|
}
|
|
|
|
END_MUTEX_SCOPE
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
V8ScriptHolder* V8IsolateImpl::Compile(const V8DocumentInfo& documentInfo, StdString&& code)
|
|
{
|
|
BEGIN_ISOLATE_SCOPE
|
|
|
|
SharedPtr<V8ContextImpl> spContextImpl(!m_ContextEntries.empty() ? m_ContextEntries.front().pContextImpl : new V8ContextImpl(this, m_Name));
|
|
return spContextImpl->Compile(documentInfo, std::move(code));
|
|
|
|
END_ISOLATE_SCOPE
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
V8ScriptHolder* V8IsolateImpl::Compile(const V8DocumentInfo& documentInfo, StdString&& code, V8CacheKind cacheKind, std::vector<uint8_t>& cacheBytes)
|
|
{
|
|
BEGIN_ISOLATE_SCOPE
|
|
|
|
SharedPtr<V8ContextImpl> spContextImpl(!m_ContextEntries.empty() ? m_ContextEntries.front().pContextImpl : new V8ContextImpl(this, m_Name));
|
|
return spContextImpl->Compile(documentInfo, std::move(code), cacheKind, cacheBytes);
|
|
|
|
END_ISOLATE_SCOPE
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
V8ScriptHolder* V8IsolateImpl::Compile(const V8DocumentInfo& documentInfo, StdString&& code, V8CacheKind cacheKind, const std::vector<uint8_t>& cacheBytes, bool& cacheAccepted)
|
|
{
|
|
BEGIN_ISOLATE_SCOPE
|
|
|
|
SharedPtr<V8ContextImpl> spContextImpl(!m_ContextEntries.empty() ? m_ContextEntries.front().pContextImpl : new V8ContextImpl(this, m_Name));
|
|
return spContextImpl->Compile(documentInfo, std::move(code), cacheKind, cacheBytes, cacheAccepted);
|
|
|
|
END_ISOLATE_SCOPE
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
V8ScriptHolder* V8IsolateImpl::Compile(const V8DocumentInfo& documentInfo, StdString&& code, V8CacheKind cacheKind, std::vector<uint8_t>& cacheBytes, V8CacheResult& cacheResult)
|
|
{
|
|
BEGIN_ISOLATE_SCOPE
|
|
|
|
SharedPtr<V8ContextImpl> spContextImpl(!m_ContextEntries.empty() ? m_ContextEntries.front().pContextImpl : new V8ContextImpl(this, m_Name));
|
|
return spContextImpl->Compile(documentInfo, std::move(code), cacheKind, cacheBytes, cacheResult);
|
|
|
|
END_ISOLATE_SCOPE
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool V8IsolateImpl::GetEnableInterruptPropagation()
|
|
{
|
|
return m_EnableInterruptPropagation;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::SetEnableInterruptPropagation(bool value)
|
|
{
|
|
m_EnableInterruptPropagation = value;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool V8IsolateImpl::GetDisableHeapSizeViolationInterrupt()
|
|
{
|
|
return m_DisableHeapSizeViolationInterrupt;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::SetDisableHeapSizeViolationInterrupt(bool value)
|
|
{
|
|
m_DisableHeapSizeViolationInterrupt = value;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::GetHeapStatistics(v8::HeapStatistics& heapStatistics)
|
|
{
|
|
BEGIN_ISOLATE_SCOPE
|
|
|
|
m_upIsolate->GetHeapStatistics(&heapStatistics);
|
|
|
|
END_ISOLATE_SCOPE
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
V8Isolate::Statistics V8IsolateImpl::GetStatistics()
|
|
{
|
|
BEGIN_ISOLATE_SCOPE
|
|
BEGIN_MUTEX_SCOPE(m_DataMutex)
|
|
|
|
return m_Statistics;
|
|
|
|
END_MUTEX_SCOPE
|
|
END_ISOLATE_SCOPE
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::CollectGarbage(bool exhaustive)
|
|
{
|
|
BEGIN_ISOLATE_SCOPE
|
|
|
|
if (exhaustive)
|
|
{
|
|
ClearScriptCache();
|
|
ClearCachesForTesting();
|
|
RequestGarbageCollectionForTesting(v8::Isolate::kFullGarbageCollection);
|
|
}
|
|
else
|
|
{
|
|
RequestGarbageCollectionForTesting(v8::Isolate::kMinorGarbageCollection);
|
|
}
|
|
|
|
END_ISOLATE_SCOPE
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool V8IsolateImpl::BeginCpuProfile(const StdString& name, v8::CpuProfilingMode mode, bool recordSamples)
|
|
{
|
|
BEGIN_ISOLATE_SCOPE
|
|
|
|
if (!m_upCpuProfiler)
|
|
{
|
|
m_upCpuProfiler.reset(v8::CpuProfiler::New(m_upIsolate.get()));
|
|
}
|
|
|
|
v8::Local<v8::String> hName;
|
|
if (!CreateString(name).ToLocal(&hName))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
m_upCpuProfiler->StartProfiling(hName, mode, recordSamples);
|
|
return true;
|
|
|
|
END_ISOLATE_SCOPE
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool V8IsolateImpl::EndCpuProfile(const StdString& name, CpuProfileCallback* pCallback, void* pvArg)
|
|
{
|
|
BEGIN_ISOLATE_SCOPE
|
|
|
|
if (!m_upCpuProfiler)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
v8::Local<v8::String> hName;
|
|
if (!CreateString(name).ToLocal(&hName))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
UniqueDeletePtr<v8::CpuProfile> upProfile(m_upCpuProfiler->StopProfiling(hName));
|
|
if (!upProfile)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (pCallback)
|
|
{
|
|
pCallback(*upProfile, pvArg);
|
|
}
|
|
|
|
return true;
|
|
|
|
END_ISOLATE_SCOPE
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::CollectCpuProfileSample()
|
|
{
|
|
BEGIN_ISOLATE_SCOPE
|
|
|
|
v8::CpuProfiler::CollectSample(m_upIsolate.get());
|
|
|
|
END_ISOLATE_SCOPE
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
uint32_t V8IsolateImpl::GetCpuProfileSampleInterval()
|
|
{
|
|
return m_CpuProfileSampleInterval;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::SetCpuProfileSampleInterval(uint32_t value)
|
|
{
|
|
BEGIN_ISOLATE_SCOPE
|
|
|
|
if (value != m_CpuProfileSampleInterval)
|
|
{
|
|
m_CpuProfileSampleInterval = std::min(std::max(value, 125U), static_cast<uint32_t>(INT_MAX));
|
|
|
|
if (!m_upCpuProfiler)
|
|
{
|
|
m_upCpuProfiler.reset(v8::CpuProfiler::New(m_upIsolate.get()));
|
|
}
|
|
|
|
m_upCpuProfiler->SetSamplingInterval(static_cast<int>(m_CpuProfileSampleInterval));
|
|
}
|
|
|
|
END_ISOLATE_SCOPE
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::WriteHeapSnapshot(void* pvStream)
|
|
{
|
|
BEGIN_ISOLATE_SCOPE
|
|
|
|
auto pSnapshot = m_upIsolate->GetHeapProfiler()->TakeHeapSnapshot();
|
|
|
|
V8OutputStream stream(pvStream);
|
|
pSnapshot->Serialize(&stream);
|
|
|
|
const_cast<v8::HeapSnapshot*>(pSnapshot)->Delete();
|
|
|
|
END_ISOLATE_SCOPE
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::runMessageLoopOnPause(int /*contextGroupId*/)
|
|
{
|
|
RunMessageLoop(RunMessageLoopReason::PausedInDebugger);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::quitMessageLoopOnPause()
|
|
{
|
|
_ASSERTE(IsCurrent() && IsLocked());
|
|
|
|
BEGIN_MUTEX_SCOPE(m_DataMutex)
|
|
m_optExitMessageLoopReason = ExitMessageLoopReason::ResumedExecution;
|
|
END_MUTEX_SCOPE
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::runIfWaitingForDebugger(int /*contextGroupId*/)
|
|
{
|
|
quitMessageLoopOnPause();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
v8::Local<v8::Context> V8IsolateImpl::ensureDefaultContextInGroup(int contextGroupId)
|
|
{
|
|
_ASSERTE(IsCurrent() && IsLocked());
|
|
|
|
if (!m_ContextEntries.empty())
|
|
{
|
|
return m_ContextEntries.front().pContextImpl->GetContext();
|
|
}
|
|
|
|
return v8_inspector::V8InspectorClient::ensureDefaultContextInGroup(contextGroupId);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
double V8IsolateImpl::currentTimeMS()
|
|
{
|
|
return HighResolutionClock::GetRelativeSeconds() * 1000;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::sendResponse(int /*callId*/, std::unique_ptr<v8_inspector::StringBuffer> upMessage)
|
|
{
|
|
_ASSERTE(IsCurrent() && IsLocked());
|
|
|
|
if (m_pvDebugAgent)
|
|
{
|
|
HostObjectUtil::GetInstance().SendDebugMessage(m_pvDebugAgent, StdString(upMessage->string()));
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::sendNotification(std::unique_ptr<v8_inspector::StringBuffer> upMessage)
|
|
{
|
|
sendResponse(0, std::move(upMessage));
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::flushProtocolNotifications()
|
|
{
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void* V8IsolateImpl::AddRefV8Object(void* pvObject)
|
|
{
|
|
BEGIN_ISOLATE_SCOPE
|
|
|
|
return ::PtrFromHandle(CreatePersistent(::HandleFromPtr<v8::Object>(pvObject)));
|
|
|
|
END_ISOLATE_SCOPE
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::ReleaseV8Object(void* pvObject)
|
|
{
|
|
CallWithLockNoWait(true /*allowNesting*/, [pvObject] (V8IsolateImpl* pIsolateImpl)
|
|
{
|
|
pIsolateImpl->Dispose(::HandleFromPtr<v8::Object>(pvObject));
|
|
});
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void* V8IsolateImpl::AddRefV8Script(void* pvScript)
|
|
{
|
|
BEGIN_ISOLATE_SCOPE
|
|
|
|
return ::PtrFromHandle(CreatePersistent(::HandleFromPtr<v8::UnboundScript>(pvScript)));
|
|
|
|
END_ISOLATE_SCOPE
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::ReleaseV8Script(void* pvScript)
|
|
{
|
|
CallWithLockNoWait(true /*allowNesting*/, [pvScript] (V8IsolateImpl* pIsolateImpl)
|
|
{
|
|
pIsolateImpl->Dispose(::HandleFromPtr<v8::Script>(pvScript));
|
|
});
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::RunTaskAsync(std::unique_ptr<v8::Task> upTask)
|
|
{
|
|
if (upTask)
|
|
{
|
|
if (m_Released)
|
|
{
|
|
upTask->Run();
|
|
}
|
|
else
|
|
{
|
|
std::shared_ptr<v8::Task> spTask(std::move(upTask));
|
|
std::weak_ptr<v8::Task> wpTask(spTask);
|
|
|
|
BEGIN_MUTEX_SCOPE(m_DataMutex)
|
|
m_AsyncTasks.push_back(std::move(spTask));
|
|
m_Statistics.BumpPostedTaskCount(TaskKind::Worker);
|
|
END_MUTEX_SCOPE
|
|
|
|
auto wrIsolate = CreateWeakRef();
|
|
HostObjectUtil::GetInstance().QueueNativeCallback([this, wrIsolate, wpTask] ()
|
|
{
|
|
auto spIsolate = wrIsolate.GetTarget();
|
|
if (!spIsolate.IsEmpty())
|
|
{
|
|
auto spTask = wpTask.lock();
|
|
if (spTask)
|
|
{
|
|
spTask->Run();
|
|
|
|
BEGIN_MUTEX_SCOPE(m_DataMutex)
|
|
auto it = std::remove(m_AsyncTasks.begin(), m_AsyncTasks.end(), spTask);
|
|
m_AsyncTasks.erase(it, m_AsyncTasks.end());
|
|
m_Statistics.BumpInvokedTaskCount(TaskKind::Worker);
|
|
END_MUTEX_SCOPE
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::RunTaskDelayed(std::unique_ptr<v8::Task> upTask, double delayInSeconds)
|
|
{
|
|
if (upTask && !m_Released)
|
|
{
|
|
std::shared_ptr<v8::Task> spTask(std::move(upTask));
|
|
|
|
auto wrIsolate = CreateWeakRef();
|
|
SharedPtr<Timer> spTimer(new Timer(static_cast<int>(delayInSeconds * 1000), -1, [this, wrIsolate, spTask] (Timer* pTimer) mutable
|
|
{
|
|
auto spIsolate = wrIsolate.GetTarget();
|
|
if (!spIsolate.IsEmpty())
|
|
{
|
|
spTask->Run();
|
|
|
|
// Release the timer's strong task reference. Doing so avoids a deadlock when
|
|
// spIsolate's implicit destruction below triggers immediate isolate teardown.
|
|
|
|
spTask.reset();
|
|
|
|
// the timer has fired; discard it
|
|
|
|
BEGIN_MUTEX_SCOPE(m_DataMutex)
|
|
auto it = std::remove(m_TaskTimers.begin(), m_TaskTimers.end(), SharedPtr<Timer>(pTimer));
|
|
m_TaskTimers.erase(it, m_TaskTimers.end());
|
|
m_Statistics.BumpInvokedTaskCount(TaskKind::DelayedWorker);
|
|
END_MUTEX_SCOPE
|
|
}
|
|
else
|
|
{
|
|
// Release the timer's strong task reference. Doing so avoids a deadlock if the
|
|
// isolate is awaiting task completion on the managed finalization thread.
|
|
|
|
spTask.reset();
|
|
}
|
|
}));
|
|
|
|
// hold on to the timer to ensure callback execution
|
|
|
|
BEGIN_MUTEX_SCOPE(m_DataMutex)
|
|
m_TaskTimers.push_back(spTimer);
|
|
m_Statistics.BumpPostedTaskCount(TaskKind::DelayedWorker);
|
|
END_MUTEX_SCOPE
|
|
|
|
// Release the local task reference explicitly. Doing so avoids a deadlock if the callback is
|
|
// executed synchronously. That shouldn't happen given the current timer implementation.
|
|
|
|
spTask.reset();
|
|
|
|
// now it's safe to start the timer
|
|
|
|
spTimer->Start();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::RunTaskWithLockAsync(bool allowNesting, std::unique_ptr<v8::Task> upTask)
|
|
{
|
|
if (upTask)
|
|
{
|
|
if (m_Released)
|
|
{
|
|
if (allowNesting)
|
|
{
|
|
upTask->Run();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
std::shared_ptr<v8::Task> spTask(std::move(upTask));
|
|
CallWithLockAsync(allowNesting, [allowNesting, spTask] (V8IsolateImpl* pIsolateImpl)
|
|
{
|
|
spTask->Run();
|
|
|
|
BEGIN_MUTEX_SCOPE(pIsolateImpl->m_DataMutex)
|
|
pIsolateImpl->m_Statistics.BumpInvokedTaskCount(allowNesting ? TaskKind::Foreground : TaskKind::NonNestableForeground);
|
|
END_MUTEX_SCOPE
|
|
});
|
|
|
|
BEGIN_MUTEX_SCOPE(m_DataMutex)
|
|
m_Statistics.BumpPostedTaskCount(allowNesting ? TaskKind::Foreground : TaskKind::NonNestableForeground);
|
|
END_MUTEX_SCOPE
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::RunTaskWithLockDelayed(bool allowNesting, std::unique_ptr<v8::Task> upTask, double delayInSeconds)
|
|
{
|
|
if (upTask && !m_Released)
|
|
{
|
|
std::shared_ptr<v8::Task> spTask(std::move(upTask));
|
|
|
|
auto wrIsolate = CreateWeakRef();
|
|
SharedPtr<Timer> spTimer(new Timer(static_cast<int>(delayInSeconds * 1000), -1, [this, wrIsolate, allowNesting, spTask] (Timer* pTimer) mutable
|
|
{
|
|
auto spIsolate = wrIsolate.GetTarget();
|
|
if (!spIsolate.IsEmpty())
|
|
{
|
|
CallWithLockNoWait(allowNesting, [allowNesting, spTask] (V8IsolateImpl* pIsolateImpl)
|
|
{
|
|
spTask->Run();
|
|
|
|
BEGIN_MUTEX_SCOPE(pIsolateImpl->m_DataMutex)
|
|
pIsolateImpl->m_Statistics.BumpInvokedTaskCount(allowNesting ? TaskKind::DelayedForeground : TaskKind::NonNestableDelayedForeground);
|
|
END_MUTEX_SCOPE
|
|
});
|
|
|
|
// Release the timer's strong task reference. Doing so avoids a deadlock when
|
|
// spIsolate's implicit destruction below triggers immediate isolate teardown.
|
|
|
|
spTask.reset();
|
|
|
|
// the timer has fired; discard it
|
|
|
|
BEGIN_MUTEX_SCOPE(m_DataMutex)
|
|
auto it = std::remove(m_TaskTimers.begin(), m_TaskTimers.end(), pTimer);
|
|
m_TaskTimers.erase(it, m_TaskTimers.end());
|
|
END_MUTEX_SCOPE
|
|
}
|
|
else
|
|
{
|
|
// Release the timer's strong task reference. Doing so avoids a deadlock if the
|
|
// isolate is awaiting task completion on the managed finalization thread.
|
|
|
|
spTask.reset();
|
|
}
|
|
}));
|
|
|
|
// hold on to the timer to ensure callback execution
|
|
|
|
BEGIN_MUTEX_SCOPE(m_DataMutex)
|
|
m_TaskTimers.push_back(spTimer);
|
|
m_Statistics.BumpPostedTaskCount(allowNesting ? TaskKind::DelayedForeground : TaskKind::NonNestableDelayedForeground);
|
|
END_MUTEX_SCOPE
|
|
|
|
// Release the local task reference explicitly. Doing so avoids a deadlock if the callback is
|
|
// executed synchronously. That shouldn't happen given the current timer implementation.
|
|
|
|
spTask.reset();
|
|
|
|
// now it's safe to start the timer
|
|
|
|
spTimer->Start();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
std::shared_ptr<v8::TaskRunner> V8IsolateImpl::GetForegroundTaskRunner()
|
|
{
|
|
BEGIN_MUTEX_SCOPE(m_DataMutex)
|
|
|
|
if (!m_spForegroundTaskRunner)
|
|
{
|
|
m_spForegroundTaskRunner = std::make_shared<V8ForegroundTaskRunner>(*this);
|
|
}
|
|
|
|
return m_spForegroundTaskRunner;
|
|
|
|
END_MUTEX_SCOPE
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void* V8IsolateImpl::AllocateArrayBuffer(size_t size)
|
|
{
|
|
BEGIN_MUTEX_SCOPE(m_DataMutex)
|
|
|
|
auto newArrayBufferAllocation = m_ArrayBufferAllocation + size;
|
|
if ((newArrayBufferAllocation >= m_ArrayBufferAllocation) && (newArrayBufferAllocation <= m_MaxArrayBufferAllocation))
|
|
{
|
|
auto pvData = ::calloc(1, size);
|
|
if (pvData)
|
|
{
|
|
m_ArrayBufferAllocation = newArrayBufferAllocation;
|
|
return pvData;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
END_MUTEX_SCOPE
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void* V8IsolateImpl::AllocateUninitializedArrayBuffer(size_t size)
|
|
{
|
|
BEGIN_MUTEX_SCOPE(m_DataMutex)
|
|
|
|
auto newArrayBufferAllocation = m_ArrayBufferAllocation + size;
|
|
if ((newArrayBufferAllocation >= m_ArrayBufferAllocation) && (newArrayBufferAllocation <= m_MaxArrayBufferAllocation))
|
|
{
|
|
auto pvData = ::malloc(size);
|
|
if (pvData)
|
|
{
|
|
m_ArrayBufferAllocation = newArrayBufferAllocation;
|
|
return pvData;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
END_MUTEX_SCOPE
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::FreeArrayBuffer(void* pvData, size_t size)
|
|
{
|
|
BEGIN_MUTEX_SCOPE(m_DataMutex)
|
|
|
|
if (pvData)
|
|
{
|
|
::free(pvData);
|
|
if (m_ArrayBufferAllocation >= size)
|
|
{
|
|
m_ArrayBufferAllocation -= size;
|
|
}
|
|
}
|
|
|
|
END_MUTEX_SCOPE
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::CallWithLockNoWait(bool allowNesting, CallWithLockCallback&& callback)
|
|
{
|
|
if (callback)
|
|
{
|
|
if (m_Mutex.TryLock())
|
|
{
|
|
// the callback may release this instance; hold it for destruction outside isolate scope
|
|
SharedPtr<V8IsolateImpl> spThis(this);
|
|
|
|
MutexLock<RecursiveMutex> lock(m_Mutex, false);
|
|
if (allowNesting || (m_CallWithLockLevel < 1))
|
|
{
|
|
BEGIN_ISOLATE_NATIVE_SCOPE
|
|
BEGIN_PULSE_VALUE_SCOPE(&m_CallWithLockLevel, m_CallWithLockLevel + 1)
|
|
|
|
callback(this);
|
|
return;
|
|
|
|
END_PULSE_VALUE_SCOPE
|
|
END_ISOLATE_NATIVE_SCOPE
|
|
}
|
|
}
|
|
|
|
CallWithLockAsync(allowNesting, std::move(callback));
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void NORETURN V8IsolateImpl::ThrowOutOfMemoryException()
|
|
{
|
|
m_IsOutOfMemory = true;
|
|
throw V8Exception(V8Exception::Type::Fatal, m_Name, StdString(SL("The V8 runtime has exceeded its memory limit")), ExecutionStarted());
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::ImportMetaInitializeCallback(v8::Local<v8::Context> hContext, v8::Local<v8::Module> hModule, v8::Local<v8::Object> hMeta)
|
|
{
|
|
GetInstanceFromIsolate(hContext->GetIsolate())->InitializeImportMeta(hContext, hModule, hMeta);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
v8::MaybeLocal<v8::Promise> V8IsolateImpl::ModuleImportCallback(v8::Local<v8::Context> hContext, v8::Local<v8::Data> hHostDefinedOptions, v8::Local<v8::Value> hResourceName, v8::Local<v8::String> hSpecifier, v8::Local<v8::FixedArray> hImportAssertions)
|
|
{
|
|
return GetInstanceFromIsolate(hContext->GetIsolate())->ImportModule(hContext, hHostDefinedOptions, hResourceName, hSpecifier, hImportAssertions);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
v8::MaybeLocal<v8::Module> V8IsolateImpl::ModuleResolveCallback(v8::Local<v8::Context> hContext, v8::Local<v8::String> hSpecifier, v8::Local<v8::FixedArray> /*importAssertions*/, v8::Local<v8::Module> hReferrer)
|
|
{
|
|
return GetInstanceFromIsolate(hContext->GetIsolate())->ResolveModule(hContext, hSpecifier, hReferrer);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::InitializeImportMeta(v8::Local<v8::Context> hContext, v8::Local<v8::Module> hModule, v8::Local<v8::Object> hMeta)
|
|
{
|
|
_ASSERTE(IsCurrent() && IsLocked());
|
|
|
|
auto pContextImpl = FindContext(hContext);
|
|
if (pContextImpl)
|
|
{
|
|
return pContextImpl->InitializeImportMeta(hContext, hModule, hMeta);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
v8::MaybeLocal<v8::Promise> V8IsolateImpl::ImportModule(v8::Local<v8::Context> hContext, v8::Local<v8::Data> hHostDefinedOptions, v8::Local<v8::Value> hResourceName, v8::Local<v8::String> hSpecifier, v8::Local<v8::FixedArray> hImportAssertions)
|
|
{
|
|
_ASSERTE(IsCurrent() && IsLocked());
|
|
|
|
auto pContextImpl = FindContext(hContext);
|
|
if (pContextImpl)
|
|
{
|
|
return pContextImpl->ImportModule(hHostDefinedOptions, hResourceName, hSpecifier, hImportAssertions);
|
|
}
|
|
|
|
return v8::MaybeLocal<v8::Promise>();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
v8::MaybeLocal<v8::Module> V8IsolateImpl::ResolveModule(v8::Local<v8::Context> hContext, v8::Local<v8::String> hSpecifier, v8::Local<v8::Module> hReferrer)
|
|
{
|
|
_ASSERTE(IsCurrent() && IsLocked());
|
|
|
|
auto pContextImpl = FindContext(hContext);
|
|
if (pContextImpl)
|
|
{
|
|
return pContextImpl->ResolveModule(hSpecifier, hReferrer);
|
|
}
|
|
|
|
return v8::MaybeLocal<v8::Module>();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool V8IsolateImpl::TryGetCachedScriptInfo(uint64_t uniqueId, V8DocumentInfo& documentInfo)
|
|
{
|
|
_ASSERTE(IsCurrent() && IsLocked());
|
|
|
|
for (auto it = m_ScriptCache.cbegin(); it != m_ScriptCache.cend(); it++)
|
|
{
|
|
if (it->DocumentInfo.GetUniqueId() == uniqueId)
|
|
{
|
|
m_ScriptCache.splice(m_ScriptCache.begin(), m_ScriptCache, it);
|
|
documentInfo = it->DocumentInfo;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
v8::Local<v8::UnboundScript> V8IsolateImpl::GetCachedScript(uint64_t uniqueId, size_t codeDigest)
|
|
{
|
|
_ASSERTE(IsCurrent() && IsLocked());
|
|
|
|
for (auto it = m_ScriptCache.cbegin(); it != m_ScriptCache.cend(); it++)
|
|
{
|
|
if ((it->DocumentInfo.GetUniqueId() == uniqueId) && (it->CodeDigest == codeDigest))
|
|
{
|
|
m_ScriptCache.splice(m_ScriptCache.begin(), m_ScriptCache, it);
|
|
return it->hScript;
|
|
}
|
|
}
|
|
|
|
return v8::Local<v8::UnboundScript>();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
v8::Local<v8::UnboundScript> V8IsolateImpl::GetCachedScript(uint64_t uniqueId, size_t codeDigest, std::vector<uint8_t>& cacheBytes)
|
|
{
|
|
_ASSERTE(IsCurrent() && IsLocked());
|
|
|
|
for (auto it = m_ScriptCache.cbegin(); it != m_ScriptCache.cend(); it++)
|
|
{
|
|
if ((it->DocumentInfo.GetUniqueId() == uniqueId) && (it->CodeDigest == codeDigest))
|
|
{
|
|
m_ScriptCache.splice(m_ScriptCache.begin(), m_ScriptCache, it);
|
|
cacheBytes = it->CacheBytes;
|
|
return it->hScript;
|
|
}
|
|
}
|
|
|
|
cacheBytes.clear();
|
|
return v8::Local<v8::UnboundScript>();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::CacheScript(const V8DocumentInfo& documentInfo, size_t codeDigest, v8::Local<v8::UnboundScript> hScript)
|
|
{
|
|
CacheScript(documentInfo, codeDigest, hScript, std::vector<uint8_t>());
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::CacheScript(const V8DocumentInfo& documentInfo, size_t codeDigest, v8::Local<v8::UnboundScript> hScript, const std::vector<uint8_t>& cacheBytes)
|
|
{
|
|
_ASSERTE(IsCurrent() && IsLocked());
|
|
|
|
auto maxScriptCacheSize = HostObjectUtil::GetInstance().GetMaxScriptCacheSize();
|
|
while (m_ScriptCache.size() >= maxScriptCacheSize)
|
|
{
|
|
Dispose(m_ScriptCache.back().hScript);
|
|
m_ScriptCache.pop_back();
|
|
}
|
|
|
|
_ASSERTE(std::none_of(m_ScriptCache.begin(), m_ScriptCache.end(),
|
|
[&documentInfo, codeDigest] (const ScriptCacheEntry& entry)
|
|
{
|
|
return (entry.DocumentInfo.GetUniqueId() == documentInfo.GetUniqueId()) && (entry.CodeDigest == codeDigest);
|
|
}));
|
|
|
|
ScriptCacheEntry entry { documentInfo, codeDigest, CreatePersistent(hScript), cacheBytes };
|
|
m_ScriptCache.push_front(std::move(entry));
|
|
|
|
m_Statistics.ScriptCacheSize = m_ScriptCache.size();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::SetCachedScriptCacheBytes(uint64_t uniqueId, size_t codeDigest, const std::vector<uint8_t>& cacheBytes)
|
|
{
|
|
_ASSERTE(IsCurrent() && IsLocked());
|
|
|
|
for (auto it = m_ScriptCache.begin(); it != m_ScriptCache.end(); it++)
|
|
{
|
|
if ((it->DocumentInfo.GetUniqueId() == uniqueId) && (it->CodeDigest == codeDigest))
|
|
{
|
|
m_ScriptCache.splice(m_ScriptCache.begin(), m_ScriptCache, it);
|
|
it->CacheBytes = cacheBytes;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::ClearScriptCache()
|
|
{
|
|
_ASSERTE(IsCurrent() && IsLocked());
|
|
|
|
while (!m_ScriptCache.empty())
|
|
{
|
|
Dispose(m_ScriptCache.front().hScript);
|
|
m_ScriptCache.pop_front();
|
|
}
|
|
|
|
m_Statistics.ScriptCacheSize = m_ScriptCache.size();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::TerminateExecutionInternal()
|
|
{
|
|
if (!m_IsExecutionTerminating)
|
|
{
|
|
m_upIsolate->TerminateExecution();
|
|
m_IsExecutionTerminating = true;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::CancelTerminateExecutionInternal()
|
|
{
|
|
if (m_IsExecutionTerminating)
|
|
{
|
|
m_upIsolate->CancelTerminateExecution();
|
|
m_IsExecutionTerminating = false;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
V8IsolateImpl::~V8IsolateImpl()
|
|
{
|
|
--s_InstanceCount;
|
|
m_Released = true;
|
|
|
|
// Entering the isolate scope triggers call-with-lock queue processing. It should always be
|
|
// done here, if for no other reason than that it may prevent deadlocks in V8 isolate disposal.
|
|
|
|
BEGIN_ISOLATE_SCOPE
|
|
DisableDebugging();
|
|
ClearScriptCache();
|
|
END_ISOLATE_SCOPE
|
|
|
|
{
|
|
std::vector<std::shared_ptr<v8::Task>> asyncTasks;
|
|
std::vector<SharedPtr<Timer>> taskTimers;
|
|
|
|
BEGIN_MUTEX_SCOPE(m_DataMutex)
|
|
std::swap(asyncTasks, m_AsyncTasks);
|
|
std::swap(taskTimers, m_TaskTimers);
|
|
END_MUTEX_SCOPE
|
|
|
|
for (const auto& spTask : asyncTasks)
|
|
{
|
|
spTask->Run();
|
|
}
|
|
}
|
|
|
|
Dispose(m_hHostObjectHolderKey);
|
|
|
|
m_upIsolate->SetHostImportModuleDynamicallyCallback(static_cast<v8::HostImportModuleDynamicallyCallback>(nullptr));
|
|
m_upIsolate->SetHostInitializeImportMetaObjectCallback(nullptr);
|
|
|
|
m_upIsolate->RemoveBeforeCallEnteredCallback(OnBeforeCallEntered);
|
|
m_upIsolate->RemoveNearHeapLimitCallback(HeapExpansionCallback, 0);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
V8IsolateImpl::ExitMessageLoopReason V8IsolateImpl::RunMessageLoop(RunMessageLoopReason reason)
|
|
{
|
|
_ASSERTE(IsCurrent() && IsLocked());
|
|
|
|
std::unique_lock<std::mutex> lock(m_DataMutex.GetImpl());
|
|
|
|
if (!m_optRunMessageLoopReason)
|
|
{
|
|
m_optExitMessageLoopReason.reset();
|
|
|
|
BEGIN_PULSE_VALUE_SCOPE(&m_optRunMessageLoopReason, reason)
|
|
|
|
ProcessCallWithLockQueue(lock);
|
|
|
|
while (true)
|
|
{
|
|
m_CallWithLockQueueChanged.wait(lock);
|
|
ProcessCallWithLockQueue(lock);
|
|
|
|
if (m_optExitMessageLoopReason)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
END_PULSE_VALUE_SCOPE
|
|
|
|
ProcessCallWithLockQueue(lock);
|
|
return *m_optExitMessageLoopReason;
|
|
}
|
|
|
|
return ExitMessageLoopReason::NestedInvocation;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::CallWithLockAsync(bool allowNesting, CallWithLockCallback&& callback)
|
|
{
|
|
if (callback)
|
|
{
|
|
BEGIN_MUTEX_SCOPE(m_DataMutex)
|
|
|
|
m_CallWithLockQueue.push(std::make_pair(allowNesting, std::move(callback)));
|
|
|
|
if (m_optRunMessageLoopReason)
|
|
{
|
|
m_CallWithLockQueueChanged.notify_one();
|
|
return;
|
|
}
|
|
|
|
if (m_CallWithLockQueue.size() > 1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
END_MUTEX_SCOPE
|
|
|
|
// trigger asynchronous queue processing
|
|
|
|
auto wrIsolate = CreateWeakRef();
|
|
HostObjectUtil::GetInstance().QueueNativeCallback([this, wrIsolate] ()
|
|
{
|
|
auto spIsolate = wrIsolate.GetTarget();
|
|
if (!spIsolate.IsEmpty())
|
|
{
|
|
if (m_Mutex.TryLock())
|
|
{
|
|
MutexLock<RecursiveMutex> lock(m_Mutex, false);
|
|
BEGIN_ISOLATE_NATIVE_SCOPE
|
|
// do nothing; scope entry triggers automatic queue processing
|
|
END_ISOLATE_NATIVE_SCOPE
|
|
}
|
|
else
|
|
{
|
|
// The isolate is active on another thread, and the queue will be processed automatically
|
|
// at scope exit, but an interrupt ensures relatively timely processing.
|
|
|
|
RequestInterrupt(ProcessCallWithLockQueue, this);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::ProcessCallWithLockQueue(v8::Isolate* /*pIsolate*/, void* pvIsolateImpl)
|
|
{
|
|
static_cast<V8IsolateImpl*>(pvIsolateImpl)->ProcessCallWithLockQueue();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::ProcessCallWithLockQueue()
|
|
{
|
|
BEGIN_PROMISE_HOOK_SCOPE
|
|
|
|
std::unique_lock<std::mutex> lock(m_DataMutex.GetImpl());
|
|
ProcessCallWithLockQueue(lock);
|
|
|
|
END_PROMISE_HOOK_SCOPE
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::ProcessCallWithLockQueue(std::unique_lock<std::mutex>& lock)
|
|
{
|
|
_ASSERTE(lock.mutex() == &m_DataMutex.GetImpl());
|
|
_ASSERTE(lock.owns_lock());
|
|
|
|
CallWithLockQueue callWithLockQueue(PopCallWithLockQueue(lock));
|
|
while (!callWithLockQueue.empty())
|
|
{
|
|
lock.unlock();
|
|
ProcessCallWithLockQueue(callWithLockQueue);
|
|
lock.lock();
|
|
callWithLockQueue = PopCallWithLockQueue(lock);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::ProcessCallWithLockQueue(CallWithLockQueue& callWithLockQueue)
|
|
{
|
|
_ASSERTE(IsCurrent() && IsLocked());
|
|
|
|
BEGIN_PULSE_VALUE_SCOPE(&m_CallWithLockLevel, m_CallWithLockLevel + 1)
|
|
|
|
while (!callWithLockQueue.empty())
|
|
{
|
|
try
|
|
{
|
|
callWithLockQueue.front().second(this);
|
|
}
|
|
catch (...)
|
|
{
|
|
}
|
|
|
|
callWithLockQueue.pop();
|
|
}
|
|
|
|
END_PULSE_VALUE_SCOPE
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
V8IsolateImpl::CallWithLockQueue V8IsolateImpl::PopCallWithLockQueue(const std::unique_lock<std::mutex>& lock)
|
|
{
|
|
_ASSERTE(IsCurrent() && IsLocked());
|
|
_ASSERTE(lock.mutex() == &m_DataMutex.GetImpl());
|
|
_ASSERTE(lock.owns_lock());
|
|
IGNORE_UNUSED(lock);
|
|
|
|
if (m_CallWithLockLevel < 1)
|
|
{
|
|
return std::move(m_CallWithLockQueue);
|
|
}
|
|
|
|
CallWithLockQueue nestableCallWithLockQueue;
|
|
CallWithLockQueue nonNestableCallWithLockQueue;
|
|
|
|
while (!m_CallWithLockQueue.empty())
|
|
{
|
|
auto& callWithLockEntry = m_CallWithLockQueue.front();
|
|
auto& callWithLockQueue = callWithLockEntry.first ? nestableCallWithLockQueue : nonNestableCallWithLockQueue;
|
|
callWithLockQueue.push(std::move(callWithLockEntry));
|
|
m_CallWithLockQueue.pop();
|
|
}
|
|
|
|
m_CallWithLockQueue = std::move(nonNestableCallWithLockQueue);
|
|
return nestableCallWithLockQueue;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::ConnectDebugClient()
|
|
{
|
|
CallWithLockNoWait(true /*allowNesting*/, [] (V8IsolateImpl* pIsolateImpl)
|
|
{
|
|
if (pIsolateImpl->m_upInspector && !pIsolateImpl->m_upInspectorSession)
|
|
{
|
|
pIsolateImpl->m_upInspectorSession = pIsolateImpl->m_upInspector->connect(s_ContextGroupId, pIsolateImpl, v8_inspector::StringView(), v8_inspector::V8Inspector::ClientTrustLevel::kFullyTrusted);
|
|
}
|
|
});
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::SendDebugCommand(const StdString& command)
|
|
{
|
|
CallWithLockNoWait(true /*allowNesting*/, [command] (V8IsolateImpl* pIsolateImpl)
|
|
{
|
|
if (pIsolateImpl->m_upInspectorSession)
|
|
{
|
|
pIsolateImpl->m_upInspectorSession->dispatchProtocolMessage(command.GetStringView());
|
|
}
|
|
});
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::DisconnectDebugClient()
|
|
{
|
|
CallWithLockNoWait(true /*allowNesting*/, [] (V8IsolateImpl* pIsolateImpl)
|
|
{
|
|
pIsolateImpl->m_upInspectorSession.reset();
|
|
});
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
V8IsolateImpl::ExecutionScope* V8IsolateImpl::EnterExecutionScope(ExecutionScope* pExecutionScope, size_t* pStackMarker)
|
|
{
|
|
_ASSERTE(IsCurrent() && IsLocked());
|
|
|
|
// is heap size monitoring in progress?
|
|
if (m_HeapWatchLevel == 0)
|
|
{
|
|
// no; there should be no heap watch timer
|
|
_ASSERTE(m_spHeapWatchTimer.IsEmpty());
|
|
|
|
// is a heap size limit specified?
|
|
size_t maxHeapSize = m_MaxHeapSize;
|
|
if (maxHeapSize > 0)
|
|
{
|
|
// yes; perform initial check and set up heap watch timer
|
|
CheckHeapSize(maxHeapSize, false /*timerTriggered*/);
|
|
|
|
// enter outermost heap size monitoring scope
|
|
m_HeapWatchLevel = 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// heap size monitoring in progress; enter nested scope
|
|
m_HeapWatchLevel++;
|
|
}
|
|
|
|
// is stack usage monitoring in progress?
|
|
if (m_StackWatchLevel == 0)
|
|
{
|
|
// no; there should be no stack address limit
|
|
_ASSERTE(m_pStackLimit == nullptr);
|
|
|
|
// is a stack usage limit specified?
|
|
size_t maxStackUsage = m_MaxStackUsage;
|
|
if (maxStackUsage > 0)
|
|
{
|
|
// yes; ensure minimum breathing room
|
|
maxStackUsage = std::max(maxStackUsage, s_StackBreathingRoom);
|
|
|
|
// calculate stack address limit
|
|
size_t* pStackLimit = pStackMarker - (maxStackUsage / sizeof(size_t));
|
|
if ((pStackLimit < s_pMinStackLimit) || (pStackLimit > pStackMarker))
|
|
{
|
|
// underflow; use minimum non-null stack address
|
|
pStackLimit = s_pMinStackLimit;
|
|
}
|
|
else
|
|
{
|
|
// check stack address limit sanity
|
|
_ASSERTE(static_cast<size_t>(pStackMarker - pStackLimit) >= (s_StackBreathingRoom / sizeof(size_t)));
|
|
}
|
|
|
|
// set and record stack address limit
|
|
m_upIsolate->SetStackLimit(reinterpret_cast<uintptr_t>(pStackLimit));
|
|
m_pStackLimit = pStackLimit;
|
|
|
|
// enter outermost stack usage monitoring scope
|
|
m_StackWatchLevel = 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// stack usage monitoring in progress
|
|
if ((m_pStackLimit != nullptr) && (pStackMarker < m_pStackLimit))
|
|
{
|
|
// stack usage limit exceeded (host-side detection)
|
|
throw V8Exception(V8Exception::Type::General, m_Name, StdString(SL("The V8 runtime has exceeded its stack usage limit")), false /*executionStarted*/);
|
|
}
|
|
|
|
// enter nested stack usage monitoring scope
|
|
m_StackWatchLevel++;
|
|
}
|
|
|
|
// mark execution scope
|
|
return SetExecutionScope(pExecutionScope);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::ExitExecutionScope(ExecutionScope* pPreviousExecutionScope)
|
|
{
|
|
_ASSERTE(IsCurrent() && IsLocked());
|
|
|
|
// reset execution scope
|
|
SetExecutionScope(pPreviousExecutionScope);
|
|
|
|
// is interrupt propagation enabled?
|
|
if (!m_EnableInterruptPropagation)
|
|
{
|
|
// no; cancel termination to allow remaining script frames to execute
|
|
CancelTerminateExecution();
|
|
}
|
|
|
|
// is stack usage monitoring in progress?
|
|
if (m_StackWatchLevel > 0)
|
|
{
|
|
// yes; exit stack usage monitoring scope
|
|
if (--m_StackWatchLevel == 0)
|
|
{
|
|
// exited outermost scope; remove stack address limit
|
|
if (m_pStackLimit != nullptr)
|
|
{
|
|
// V8 has no API for removing a stack address limit
|
|
m_upIsolate->SetStackLimit(reinterpret_cast<uintptr_t>(s_pMinStackLimit));
|
|
m_pStackLimit = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
// is heap size monitoring in progress?
|
|
if (m_HeapWatchLevel > 0)
|
|
{
|
|
// yes; exit heap size monitoring scope
|
|
if (--m_HeapWatchLevel == 0)
|
|
{
|
|
// exited outermost scope; destroy heap watch timer
|
|
m_spHeapWatchTimer.Empty();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
V8IsolateImpl::ExecutionScope* V8IsolateImpl::SetExecutionScope(ExecutionScope* pExecutionScope)
|
|
{
|
|
BEGIN_MUTEX_SCOPE(m_TerminateExecutionMutex)
|
|
|
|
auto pPrevExecutionScope = std::exchange(m_pExecutionScope, pExecutionScope);
|
|
|
|
if (pExecutionScope == nullptr)
|
|
{
|
|
CancelTerminateExecutionInternal();
|
|
}
|
|
|
|
return pPrevExecutionScope;
|
|
|
|
END_MUTEX_SCOPE
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool V8IsolateImpl::InExecutionScope()
|
|
{
|
|
BEGIN_MUTEX_SCOPE(m_TerminateExecutionMutex)
|
|
return m_pExecutionScope != nullptr;
|
|
END_MUTEX_SCOPE
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::OnExecutionStarted()
|
|
{
|
|
BEGIN_MUTEX_SCOPE(m_TerminateExecutionMutex)
|
|
|
|
if (m_pExecutionScope != nullptr)
|
|
{
|
|
m_pExecutionScope->OnExecutionStarted();
|
|
}
|
|
|
|
END_MUTEX_SCOPE
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool V8IsolateImpl::ExecutionStarted()
|
|
{
|
|
BEGIN_MUTEX_SCOPE(m_TerminateExecutionMutex)
|
|
return (m_pExecutionScope != nullptr) ? m_pExecutionScope->ExecutionStarted() : false;
|
|
END_MUTEX_SCOPE
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::SetUpHeapWatchTimer(bool forceMinInterval)
|
|
{
|
|
_ASSERTE(IsCurrent() && IsLocked());
|
|
|
|
const auto minInterval = 50.0;
|
|
auto interval = forceMinInterval ? minInterval : std::max(GetHeapSizeSampleInterval(), minInterval);
|
|
|
|
// create heap watch timer
|
|
auto wrIsolate = CreateWeakRef();
|
|
m_spHeapWatchTimer = new Timer(static_cast<int>(interval), -1, [this, wrIsolate] (Timer* pTimer)
|
|
{
|
|
// heap watch callback; is the isolate still alive?
|
|
auto spIsolate = wrIsolate.GetTarget();
|
|
if (!spIsolate.IsEmpty())
|
|
{
|
|
// yes; request callback on execution thread
|
|
auto wrTimer = pTimer->CreateWeakRef();
|
|
CallWithLockAsync(true /*allowNesting*/, [wrTimer] (V8IsolateImpl* pIsolateImpl)
|
|
{
|
|
// execution thread callback; is the timer still alive?
|
|
auto spTimer = wrTimer.GetTarget();
|
|
if (!spTimer.IsEmpty())
|
|
{
|
|
// yes; check heap size
|
|
pIsolateImpl->CheckHeapSize(std::nullopt, true /*timerTriggered*/);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// start heap watch timer
|
|
m_spHeapWatchTimer->Start();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::CheckHeapSize(const std::optional<size_t>& optMaxHeapSize, bool timerTriggered)
|
|
{
|
|
_ASSERTE(IsCurrent() && IsLocked());
|
|
|
|
// do we have a heap size limit?
|
|
auto maxHeapSize = optMaxHeapSize.has_value() ? optMaxHeapSize.value() : m_MaxHeapSize.load();
|
|
if (maxHeapSize > 0)
|
|
{
|
|
// yes; use normal heap watch timer interval by default
|
|
auto forceMinInterval = false;
|
|
|
|
// is the total heap size over the limit?
|
|
v8::HeapStatistics heapStatistics;
|
|
GetHeapStatistics(heapStatistics);
|
|
if (heapStatistics.total_heap_size() > maxHeapSize)
|
|
{
|
|
// yes; collect garbage
|
|
ClearCachesForTesting();
|
|
RequestGarbageCollectionForTesting(v8::Isolate::kFullGarbageCollection);
|
|
|
|
// is the total heap size still over the limit?
|
|
GetHeapStatistics(heapStatistics);
|
|
if (heapStatistics.total_heap_size() > maxHeapSize)
|
|
{
|
|
// yes; the isolate is out of memory; act based on policy
|
|
if (m_DisableHeapSizeViolationInterrupt)
|
|
{
|
|
if (InExecutionScope())
|
|
{
|
|
m_MaxHeapSize = 0;
|
|
m_upIsolate->ThrowError("The V8 runtime has exceeded its memory limit");
|
|
return;
|
|
}
|
|
|
|
// defer exception until code execution is in progress
|
|
forceMinInterval = true;
|
|
}
|
|
else
|
|
{
|
|
m_IsOutOfMemory = true;
|
|
TerminateExecution();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// the isolate is not out of memory; is heap size monitoring in progress?
|
|
if (!timerTriggered || (m_HeapWatchLevel > 0))
|
|
{
|
|
// yes; restart heap watch timer
|
|
SetUpHeapWatchTimer(forceMinInterval);
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::OnBeforeCallEntered(v8::Isolate* pIsolate)
|
|
{
|
|
GetInstanceFromIsolate(pIsolate)->OnBeforeCallEntered();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::OnBeforeCallEntered()
|
|
{
|
|
_ASSERTE(IsCurrent() && IsLocked());
|
|
OnExecutionStarted();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::PromiseHook(v8::PromiseHookType type, v8::Local<v8::Promise> hPromise, v8::Local<v8::Value> /*hParent*/)
|
|
{
|
|
if ((type == v8::PromiseHookType::kResolve) && !hPromise.IsEmpty())
|
|
{
|
|
auto hContext = hPromise->GetCreationContext().FromMaybe(v8::Local<v8::Context>());
|
|
if (!hContext.IsEmpty())
|
|
{
|
|
GetInstanceFromIsolate(hContext->GetIsolate())->FlushContextAsync(hContext);
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::FlushContextAsync(v8::Local<v8::Context> hContext)
|
|
{
|
|
_ASSERTE(IsCurrent() && IsLocked());
|
|
|
|
for (auto& contextEntry : m_ContextEntries)
|
|
{
|
|
if (contextEntry.pContextImpl->GetContext() == hContext)
|
|
{
|
|
FlushContextAsync(contextEntry);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::FlushContextAsync(ContextEntry& contextEntry)
|
|
{
|
|
auto expected = false;
|
|
if (contextEntry.FlushPending.compare_exchange_strong(expected, true))
|
|
{
|
|
auto wrContext = contextEntry.pContextImpl->CreateWeakRef();
|
|
CallWithLockAsync(true /*allowNesting*/, [wrContext] (V8IsolateImpl* pIsolateImpl)
|
|
{
|
|
auto spContext = wrContext.GetTarget();
|
|
if (!spContext.IsEmpty())
|
|
{
|
|
pIsolateImpl->FlushContext(spContext.DerefAs<V8ContextImpl>());
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void V8IsolateImpl::FlushContext(V8ContextImpl& contextImpl)
|
|
{
|
|
_ASSERTE(IsCurrent() && IsLocked());
|
|
|
|
for (auto& contextEntry : m_ContextEntries)
|
|
{
|
|
if (contextEntry.pContextImpl == &contextImpl)
|
|
{
|
|
contextEntry.FlushPending = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
contextImpl.Flush();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
size_t V8IsolateImpl::HeapExpansionCallback(void* pvData, size_t currentLimit, size_t /*initialLimit*/)
|
|
{
|
|
const size_t minBump = 1024 * 1024;
|
|
|
|
if (pvData)
|
|
{
|
|
auto multiplier = static_cast<const V8IsolateImpl*>(pvData)->m_HeapExpansionMultiplier;
|
|
if (multiplier > 1.0)
|
|
{
|
|
auto newLimit = static_cast<size_t>(static_cast<double>(currentLimit) * multiplier);
|
|
return std::max(newLimit, currentLimit + minBump);
|
|
}
|
|
}
|
|
|
|
return currentLimit;
|
|
}
|