[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/ClearFoundry/ClearScript/master/ClearScriptV8/V8IsolateImpl.cpp [Back]  [Original]

// 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 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 CreateJobImpl(v8::TaskPriority priority, std::unique_ptr upJobTask, const v8::SourceLocation& location) override;
    virtual void PostTaskOnWorkerThreadImpl(v8::TaskPriority priority, std::unique_ptr upTask, const v8::SourceLocation& location) override;
    virtual void PostDelayedTaskOnWorkerThreadImpl(v8::TaskPriority priority, std::unique_ptr upTask, double delayInSeconds, const v8::SourceLocation& location) override;

private:

    V8Platform();

    static V8Platform ms_Instance;
    std::unique_ptr 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 flagStrings;

        flagStrings.push_back("--no_memory_pool_share_memory_on_teardown");

    #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 (!::HasFlag(m_GlobalFlags, V8GlobalFlags::DisableExplicitResourceManagement))
        {
            flagStrings.push_back("--js_explicit_resource_management");
        }

        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(HighResolutionClock::GetHardwareConcurrency());
}

//-----------------------------------------------------------------------------

std::shared_ptr V8Platform::GetForegroundTaskRunner(v8::Isolate* pIsolate, v8::TaskPriority /*priority*/)
{
    return V8IsolateImpl::GetInstanceFromIsolate(pIsolate)->GetForegroundTaskRunner();
}

//-----------------------------------------------------------------------------

std::unique_ptr V8Platform::CreateJobImpl(v8::TaskPriority priority, std::unique_ptr upJobTask, const v8::SourceLocation& /*location*/)
{
    return v8::platform::NewDefaultJobHandle(this, priority, std::move(upJobTask), NumberOfWorkerThreads());
}

//-----------------------------------------------------------------------------

void V8Platform::PostTaskOnWorkerThreadImpl(v8::TaskPriority /*priority*/, std::unique_ptr 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 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::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 bool IdleTasksEnabled() override;
    virtual bool NonNestableTasksEnabled() const override;
    virtual bool NonNestableDelayedTasksEnabled() const override;

protected:

    virtual void PostTaskImpl(std::unique_ptr upTask, const v8::SourceLocation& location) override;
    virtual void PostNonNestableTaskImpl(std::unique_ptr upTask, const v8::SourceLocation& location) override;
    virtual void PostDelayedTaskImpl(std::unique_ptr upTask, double delayInSeconds, const v8::SourceLocation& location) override;
    virtual void PostNonNestableDelayedTaskImpl(std::unique_ptr upTask, double delayInSeconds, const v8::SourceLocation& location) override;
    virtual void PostIdleTaskImpl(std::unique_ptr upTask, const v8::SourceLocation& location) override;

private:

    V8IsolateImpl& m_IsolateImpl;
    WeakRef m_wrIsolate;
};

//-----------------------------------------------------------------------------

V8ForegroundTaskRunner::V8ForegroundTaskRunner(V8IsolateImpl& isolateImpl):
    m_IsolateImpl(isolateImpl),
    m_wrIsolate(isolateImpl.CreateWeakRef())
{
}

//-----------------------------------------------------------------------------

bool V8ForegroundTaskRunner::IdleTasksEnabled()
{
    return false;
}

//-----------------------------------------------------------------------------

bool V8ForegroundTaskRunner::NonNestableTasksEnabled() const
{
    return true;
}

//-----------------------------------------------------------------------------

bool V8ForegroundTaskRunner::NonNestableDelayedTasksEnabled() const
{
    return true;
}

//-----------------------------------------------------------------------------

void V8ForegroundTaskRunner::PostTaskImpl(std::unique_ptr upTask, const v8::SourceLocation& /*location*/)
{
    auto spIsolate = m_wrIsolate.GetTarget();
    if (spIsolate.IsEmpty())
    {
        upTask->Run();
    }
    else
    {
        m_IsolateImpl.RunTaskWithLockAsync(true, std::move(upTask));
    }
}

//-----------------------------------------------------------------------------

void V8ForegroundTaskRunner::PostNonNestableTaskImpl(std::unique_ptr upTask, const v8::SourceLocation& /*location*/)
{
    auto spIsolate = m_wrIsolate.GetTarget();
    if (!spIsolate.IsEmpty())
    {
        m_IsolateImpl.RunTaskWithLockAsync(false, std::move(upTask));
    }
}

//-----------------------------------------------------------------------------

void V8ForegroundTaskRunner::PostDelayedTaskImpl(std::unique_ptr upTask, double delayInSeconds, const v8::SourceLocation& /*location*/)
{
    auto spIsolate = m_wrIsolate.GetTarget();
    if (!spIsolate.IsEmpty())
    {
        m_IsolateImpl.RunTaskWithLockDelayed(true, std::move(upTask), delayInSeconds);
    }
}

//-----------------------------------------------------------------------------

void V8ForegroundTaskRunner::PostNonNestableDelayedTaskImpl(std::unique_ptr upTask, double delayInSeconds, const v8::SourceLocation& /*location*/)
{
    auto spIsolate = m_wrIsolate.GetTarget();
    if (!spIsolate.IsEmpty())
    {
        m_IsolateImpl.RunTaskWithLockDelayed(false, std::move(upTask), delayInSeconds);
    }
}

//-----------------------------------------------------------------------------

void V8ForegroundTaskRunner::PostIdleTaskImpl(std::unique_ptr /*upTask*/, const v8::SourceLocation& /*location*/)
{
    // unexpected call to unsupported method
    std::terminate();
}

//-----------------------------------------------------------------------------
// 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 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(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 s_InstanceCount(0);
static const int s_ContextGroupId = 1;
static const size_t s_StackBreathingRoom = static_cast(16 * 1024);
static size_t* const s_pMinStackLimit = reinterpret_cast(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(*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;
}

//-----------------------------------------------------------------------------

V8GlobalFlags V8IsolateImpl::GetGlobalFlags()
{
    return V8Platform::GetInstance().GetGlobalFlags();
}

//-----------------------------------------------------------------------------

V8IsolateImpl* V8IsolateImpl::GetInstanceFromIsolate(v8::Isolate* pIsolate)
{
    _ASSERTE(pIsolate);
    return static_cast(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 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(pVersion), strlen(pVersion)));

        if (port < 1)
        {
            port = 9222;
        }

        auto wrIsolate = CreateWeakRef();
        m_pvDebugAgent = HostObjectUtil::CreateDebugAgent(m_Name, version, port, remote, [this, wrIsolate] (HostObjectUtil::DebugDirective directive, const StdString* pCommand)
        {
            auto spIsolate = wrIsolate.GetTarget();
            if (!spIsolate.IsEmpty())
            {
                if (directive == HostObjectUtil::DebugDirective::ConnectClient)
                {
                    ConnectDebugClient();
                }
                else if ((directive == HostObjectUtil::DebugDirective::SendCommand) && pCommand)
                {
                    SendDebugCommand(*pCommand);
                }
                else if (directive == HostObjectUtil::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::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 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& cacheBytes)
{
    BEGIN_ISOLATE_SCOPE

        SharedPtr 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& cacheBytes, bool& cacheAccepted)
{
    BEGIN_ISOLATE_SCOPE

        SharedPtr 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& cacheBytes, V8CacheResult& cacheResult)
{
    BEGIN_ISOLATE_SCOPE

        SharedPtr 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();
        }

        CollectGarbageInternal(exhaustive);

    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 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 hName;
        if (!CreateString(name).ToLocal(&hName))
        {
            return false;
        }

        UniqueDeletePtr 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(INT_MAX));

            if (!m_upCpuProfiler)
            {
                m_upCpuProfiler.reset(v8::CpuProfiler::New(m_upIsolate.get()));
            }

            m_upCpuProfiler->SetSamplingInterval(static_cast(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(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 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 upMessage)
{
    _ASSERTE(IsCurrent() && IsLocked());

    if (m_pvDebugAgent)
    {
        HostObjectUtil::SendDebugMessage(m_pvDebugAgent, StdString(upMessage->string()));
    }
}

//-----------------------------------------------------------------------------

void V8IsolateImpl::sendNotification(std::unique_ptr upMessage)
{
    sendResponse(0, std::move(upMessage));
}

//-----------------------------------------------------------------------------

void V8IsolateImpl::flushProtocolNotifications()
{
}

//-----------------------------------------------------------------------------

void* V8IsolateImpl::AddRefV8Object(void* pvObject)
{
    BEGIN_ISOLATE_SCOPE

        return ::PtrFromHandle(CreatePersistent(::HandleFromPtr(pvObject)));

    END_ISOLATE_SCOPE
}

//-----------------------------------------------------------------------------

void V8IsolateImpl::ReleaseV8Object(void* pvObject)
{
    CallWithLockNoWait(true, [pvObject] (V8IsolateImpl* pIsolateImpl)
    {
        pIsolateImpl->Dispose(::HandleFromPtr(pvObject));
    });
}

//-----------------------------------------------------------------------------

void* V8IsolateImpl::AddRefV8Script(void* pvScript)
{
    BEGIN_ISOLATE_SCOPE

        return ::PtrFromHandle(CreatePersistent(::HandleFromPtr(pvScript)));

    END_ISOLATE_SCOPE
}

//-----------------------------------------------------------------------------

void V8IsolateImpl::ReleaseV8Script(void* pvScript)
{
    CallWithLockNoWait(true, [pvScript] (V8IsolateImpl* pIsolateImpl)
    {
        pIsolateImpl->Dispose(::HandleFromPtr(pvScript));
    });
}

//-----------------------------------------------------------------------------

void V8IsolateImpl::RunTaskAsync(std::unique_ptr upTask)
{
    if (upTask)
    {
        if (m_Released)
        {
            upTask->Run();
        }
        else
        {
            std::shared_ptr spTask(std::move(upTask));
            std::weak_ptr 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::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 upTask, double delayInSeconds)
{
    if (upTask && !m_Released)
    {
        std::shared_ptr spTask(std::move(upTask));

        auto wrIsolate = CreateWeakRef();
        SharedPtr spTimer(new Timer(static_cast(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(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 upTask)
{
    if (upTask)
    {
        if (m_Released)
        {
            if (allowNesting)
            {
                upTask->Run();
            }
        }
        else
        {
            std::shared_ptr 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 upTask, double delayInSeconds)
{
    if (upTask && !m_Released)
    {
        std::shared_ptr spTask(std::move(upTask));

        auto wrIsolate = CreateWeakRef();
        SharedPtr spTimer(new Timer(static_cast(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 V8IsolateImpl::GetForegroundTaskRunner()
{
    BEGIN_MUTEX_SCOPE(m_DataMutex)

        if (!m_spForegroundTaskRunner)
        {
            m_spForegroundTaskRunner = std::make_shared(*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_ArrayBufferAllocation) && (newArrayBufferAllocation = 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 spThis(this);

            MutexLock 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 hContext, v8::Local hModule, v8::Local hMeta)
{
    GetInstanceFromIsolate(v8::Isolate::GetCurrent())->InitializeImportMeta(hContext, hModule, hMeta);
}

//-----------------------------------------------------------------------------

v8::MaybeLocal V8IsolateImpl::ModuleImportCallback(v8::Local hContext, v8::Local hHostDefinedOptions, v8::Local hResourceName, v8::Local hSpecifier, v8::Local hImportAssertions)
{
    return GetInstanceFromIsolate(v8::Isolate::GetCurrent())->ImportModule(hContext, hHostDefinedOptions, hResourceName, hSpecifier, hImportAssertions);
}

//-----------------------------------------------------------------------------

v8::MaybeLocal V8IsolateImpl::ModuleResolveCallback(v8::Local hContext, v8::Local hSpecifier, v8::Local /*importAssertions*/, v8::Local hReferrer)
{
    return GetInstanceFromIsolate(v8::Isolate::GetCurrent())->ResolveModule(hContext, hSpecifier, hReferrer);
}

//-----------------------------------------------------------------------------

void V8IsolateImpl::InitializeImportMeta(v8::Local hContext, v8::Local hModule, v8::Local hMeta)
{
    _ASSERTE(IsCurrent() && IsLocked());

    auto pContextImpl = FindContext(hContext);
    if (pContextImpl)
    {
        return pContextImpl->InitializeImportMeta(hContext, hModule, hMeta);
    }
}

//-----------------------------------------------------------------------------

v8::MaybeLocal V8IsolateImpl::ImportModule(v8::Local hContext, v8::Local hHostDefinedOptions, v8::Local hResourceName, v8::Local hSpecifier, v8::Local hImportAssertions)
{
    _ASSERTE(IsCurrent() && IsLocked());

    auto pContextImpl = FindContext(hContext);
    if (pContextImpl)
    {
        return pContextImpl->ImportModule(hHostDefinedOptions, hResourceName, hSpecifier, hImportAssertions);
    }

    return v8::MaybeLocal();
}

//-----------------------------------------------------------------------------

v8::MaybeLocal V8IsolateImpl::ResolveModule(v8::Local hContext, v8::Local hSpecifier, v8::Local hReferrer)
{
    _ASSERTE(IsCurrent() && IsLocked());

    auto pContextImpl = FindContext(hContext);
    if (pContextImpl)
    {
        return pContextImpl->ResolveModule(hSpecifier, hReferrer);
    }

    return v8::MaybeLocal();
}

//-----------------------------------------------------------------------------

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 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::Local V8IsolateImpl::GetCachedScript(uint64_t uniqueId, size_t codeDigest, std::vector& 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();
}

//-----------------------------------------------------------------------------

void V8IsolateImpl::CacheScript(const V8DocumentInfo& documentInfo, size_t codeDigest, v8::Local hScript)
{
    CacheScript(documentInfo, codeDigest, hScript, std::vector());
}

//-----------------------------------------------------------------------------

void V8IsolateImpl::CacheScript(const V8DocumentInfo& documentInfo, size_t codeDigest, v8::Local hScript, const std::vector& cacheBytes)
{
    _ASSERTE(IsCurrent() && IsLocked());

    auto maxScriptCacheSize = HostObjectUtil::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& 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 asyncTasks;
        std::vector 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(nullptr));
    m_upIsolate->SetHostInitializeImportMetaObjectCallback(nullptr);

    m_upIsolate->RemoveBeforeCallEnteredCallback(OnBeforeCallEntered);
    m_upIsolate->RemoveNearHeapLimitCallback(HeapExpansionCallback, 0);

    m_upIsolate.reset();
}

//-----------------------------------------------------------------------------

V8IsolateImpl::ExitMessageLoopReason V8IsolateImpl::RunMessageLoop(RunMessageLoopReason reason)
{
    _ASSERTE(IsCurrent() && IsLocked());

    std::unique_lock 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::QueueNativeCallback([this, wrIsolate] ()
        {
            auto spIsolate = wrIsolate.GetTarget();
            if (!spIsolate.IsEmpty())
            {
                if (m_Mutex.TryLock())
                {
                    MutexLock 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(pvIsolateImpl)->ProcessCallWithLockQueue();
}

//-----------------------------------------------------------------------------

void V8IsolateImpl::ProcessCallWithLockQueue()
{
    BEGIN_PROMISE_HOOK_SCOPE

        std::unique_lock lock(m_DataMutex.GetImpl());
        ProcessCallWithLockQueue(lock);

    END_PROMISE_HOOK_SCOPE
}

//-----------------------------------------------------------------------------

void V8IsolateImpl::ProcessCallWithLockQueue(std::unique_lock& 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& 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, [] (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, [command] (V8IsolateImpl* pIsolateImpl)
    {
        if (pIsolateImpl->m_upInspectorSession)
        {
            pIsolateImpl->m_upInspectorSession->dispatchProtocolMessage(command.GetStringView());
        }
    });
}

//-----------------------------------------------------------------------------

void V8IsolateImpl::DisconnectDebugClient()
{
    CallWithLockNoWait(true, [] (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);

            // 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(pStackMarker - pStackLimit) >= (s_StackBreathingRoom / sizeof(size_t)));
            }

            // set and record stack address limit
            m_upIsolate->SetStackLimit(reinterpret_cast(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);
        }

        // 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(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(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, [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);
                }
            });
        }
    });

    // start heap watch timer
    m_spHeapWatchTimer->Start();
}

//-----------------------------------------------------------------------------

void V8IsolateImpl::CheckHeapSize(const std::optional& 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();
            CollectGarbageInternal(true);

            // 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)
                {
                    // exception policy; are we executing script code?
                    if (InExecutionScope())
                    {
                        // yes; schedule script exception now
                        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
                {
                    // termination policy; mark state and request termination
                    m_IsOutOfMemory = true;
                    TerminateExecution(!timerTriggered);
                    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::CollectGarbageInternal(bool exhaustive)
{
    auto failuresRemaining = exhaustive ? 100 : 1;
    auto allocate = [&failuresRemaining] () noexcept { return failuresRemaining-- RetryCustomAllocate(allocate);
}

//-----------------------------------------------------------------------------

void V8IsolateImpl::OnBeforeCallEntered(v8::Isolate* pIsolate)
{
    GetInstanceFromIsolate(pIsolate)->OnBeforeCallEntered();
}

//-----------------------------------------------------------------------------

void V8IsolateImpl::OnBeforeCallEntered()
{
    _ASSERTE(IsCurrent() && IsLocked());
    OnExecutionStarted();
}

//-----------------------------------------------------------------------------

void V8IsolateImpl::PromiseHook(v8::PromiseHookType type, v8::Local hPromise, v8::Local /*hParent*/)
{
    if ((type == v8::PromiseHookType::kResolve) && !hPromise.IsEmpty())
    {
        auto hContext = hPromise->GetCreationContext(v8::Isolate::GetCurrent()).FromMaybe(v8::Local());
        if (!hContext.IsEmpty())
        {
            GetInstanceFromIsolate(v8::Isolate::GetCurrent())->FlushContextAsync(hContext);
        }
    }
}

//-----------------------------------------------------------------------------

void V8IsolateImpl::FlushContextAsync(v8::Local 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, [wrContext] (V8IsolateImpl* pIsolateImpl)
        {
            auto spContext = wrContext.GetTarget();
            if (!spContext.IsEmpty())
            {
                pIsolateImpl->FlushContext(spContext.DerefAs());
            }
        });
    }
}

//-----------------------------------------------------------------------------

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(pvData)->m_HeapExpansionMultiplier;
        if (multiplier > 1.0)
        {
            auto newLimit = static_cast(static_cast(currentLimit) * multiplier);
            return std::max(newLimit, currentLimit + minBump);
        }
    }

    return currentLimit;
}

Web Proxy Viewer  |  New URL  |  Original Page