blob: 602980362722a58ac14ed2da7ce659c0cb2f6eb7 [file] [log] [blame]
/*
* Copyright (C) 2008 Apple Inc. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_WORKERS_WORKER_THREAD_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_WORKERS_WORKER_THREAD_H_
#include <memory>
#include "base/memory/scoped_refptr.h"
#include "base/optional.h"
#include "base/single_thread_task_runner.h"
#include "base/synchronization/waitable_event.h"
#include "base/thread_annotations.h"
#include "base/unguessable_token.h"
#include "services/network/public/mojom/fetch_api.mojom-blink-forward.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/core/workers/parent_execution_context_task_runners.h"
#include "third_party/blink/renderer/core/workers/worker_backing_thread_startup_data.h"
#include "third_party/blink/renderer/platform/heap/persistent.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_loader_options.h"
#include "third_party/blink/renderer/platform/scheduler/public/post_cancellable_task.h"
#include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h"
#include "third_party/blink/renderer/platform/scheduler/public/thread.h"
#include "third_party/blink/renderer/platform/scheduler/public/thread_type.h"
#include "third_party/blink/renderer/platform/scheduler/public/worker_scheduler.h"
#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
#include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
#include "third_party/blink/renderer/platform/wtf/forward.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
#include "v8/include/v8-inspector.h"
#include "v8/include/v8.h"
namespace blink {
class ConsoleMessageStorage;
class InspectorTaskRunner;
class InspectorIssueStorage;
class WorkerBackingThread;
class WorkerInspectorController;
class WorkerOrWorkletGlobalScope;
class WorkerReportingProxy;
class WorkerResourceTimingNotifier;
struct CrossThreadFetchClientSettingsObjectData;
struct GlobalScopeCreationParams;
struct WorkerDevToolsParams;
struct WorkerMainScriptLoadParameters;
// WorkerThread is a kind of WorkerBackingThread client. Each worker mechanism
// can access the lower thread infrastructure via an implementation of this
// abstract class. Multiple WorkerThreads may share one WorkerBackingThread for
// worklets.
//
// WorkerThread start and termination must be initiated on the main thread and
// an actual task is executed on the worker thread.
//
// When termination starts, (debugger) tasks on WorkerThread are handled as
// follows:
// - A running task may finish unless a forcible termination task interrupts.
// If the running task is for debugger, it's guaranteed to finish without
// any interruptions.
// - Queued tasks never run.
class CORE_EXPORT WorkerThread : public Thread::TaskObserver {
public:
// Represents how this thread is terminated. Used for UMA. Append only.
enum class ExitCode {
kNotTerminated,
kGracefullyTerminated,
kSyncForciblyTerminated,
kAsyncForciblyTerminated,
kMaxValue = kAsyncForciblyTerminated,
};
~WorkerThread() override;
// Starts the underlying thread and creates the global scope. Called on the
// main thread.
// Startup data for WorkerBackingThread is base::nullopt if |this| doesn't own
// the underlying WorkerBackingThread.
// TODO(nhiroki): We could separate WorkerBackingThread initialization from
// GlobalScope initialization sequence, that is, InitializeOnWorkerThread().
// After that, we could remove this startup data for WorkerBackingThread.
// (https://crbug.com/710364)
void Start(std::unique_ptr<GlobalScopeCreationParams>,
const base::Optional<WorkerBackingThreadStartupData>&,
std::unique_ptr<WorkerDevToolsParams>);
// Posts a task to evaluate a top-level classic script on the worker thread.
// Called on the main thread after Start().
void EvaluateClassicScript(const KURL& script_url,
const String& source_code,
std::unique_ptr<Vector<uint8_t>> cached_meta_data,
const v8_inspector::V8StackTraceId& stack_id);
// Posts a task to fetch and run a top-level classic script on the worker
// thread. Called on the main thread after Start().
void FetchAndRunClassicScript(
const KURL& script_url,
std::unique_ptr<WorkerMainScriptLoadParameters>
worker_main_script_load_params,
std::unique_ptr<CrossThreadFetchClientSettingsObjectData>
outside_settings_object_data,
WorkerResourceTimingNotifier* outside_resource_timing_notifier,
const v8_inspector::V8StackTraceId& stack_id);
// Posts a task to fetch and run a top-level module script on the worker
// thread. Called on the main thread after Start().
void FetchAndRunModuleScript(
const KURL& script_url,
std::unique_ptr<WorkerMainScriptLoadParameters>
worker_main_script_load_params,
std::unique_ptr<CrossThreadFetchClientSettingsObjectData>
outside_settings_object_data,
WorkerResourceTimingNotifier* outside_resource_timing_notifier,
network::mojom::CredentialsMode,
RejectCoepUnsafeNone reject_coep_unsafe_none =
RejectCoepUnsafeNone(false));
// Posts a task to the worker thread to close the global scope and terminate
// the underlying thread. This task may be blocked by JavaScript execution on
// the worker thread, so this function also forcibly terminates JavaScript
// execution after a certain grace period.
void Terminate() LOCKS_EXCLUDED(mutex_);
// Terminates the worker thread. Subclasses of WorkerThread can override this
// to do cleanup. The default behavior is to call Terminate() and
// synchronously call EnsureScriptExecutionTerminates() to ensure the thread
// is quickly terminated. Called on the main thread.
virtual void TerminateForTesting();
// Called on the main thread for the leak detector. Forcibly terminates the
// script execution and waits by *blocking* the calling thread until the
// workers are shut down. Please be careful when using this function, because
// after the synchronous termination any V8 APIs may suddenly start to return
// empty handles and it may cause crashes.
// WARNING: This is not safe if a nested worker is running.
static void TerminateAllWorkersForTesting();
// Thread::TaskObserver.
void WillProcessTask(const base::PendingTask&, bool) override;
void DidProcessTask(const base::PendingTask&) override;
virtual WorkerBackingThread& GetWorkerBackingThread() = 0;
virtual void ClearWorkerBackingThread() = 0;
ConsoleMessageStorage* GetConsoleMessageStorage() const {
return console_message_storage_.Get();
}
InspectorIssueStorage* GetInspectorIssueStorage() const {
return inspector_issue_storage_.Get();
}
v8::Isolate* GetIsolate();
bool IsCurrentThread();
WorkerReportingProxy& GetWorkerReportingProxy() const {
return worker_reporting_proxy_;
}
// Only callable on the parent thread.
void DebuggerTaskStarted();
void DebuggerTaskFinished();
// Callable on both the main thread and the worker thread.
const base::UnguessableToken& GetDevToolsWorkerToken() const {
return devtools_worker_token_;
}
// Can be called only on the worker thread, WorkerOrWorkletGlobalScope
// and WorkerInspectorController are not thread safe.
WorkerOrWorkletGlobalScope* GlobalScope();
WorkerInspectorController* GetWorkerInspectorController();
// Number of active worker threads.
static unsigned WorkerThreadCount();
// Runs |function| with |parameters| on each worker thread, and
// adds the current WorkerThread* as the first parameter |function|.
// This only calls |function| for threads for which Start() was already
// called.
// Returns the number of workers that are scheduled to run the function.
template <typename FunctionType, typename... Parameters>
static unsigned CallOnAllWorkerThreads(FunctionType function,
TaskType task_type,
Parameters&&... parameters) {
MutexLocker lock(ThreadSetMutex());
unsigned called_worker_count = 0;
for (WorkerThread* thread : WorkerThreads()) {
PostCrossThreadTask(
*thread->GetTaskRunner(task_type), FROM_HERE,
CrossThreadBindOnce(function, WTF::CrossThreadUnretained(thread),
parameters...));
++called_worker_count;
}
return called_worker_count;
}
int GetWorkerThreadId() const { return worker_thread_id_; }
bool IsForciblyTerminated() LOCKS_EXCLUDED(mutex_);
void WaitForShutdownForTesting();
ExitCode GetExitCodeForTesting() LOCKS_EXCLUDED(mutex_);
scoped_refptr<base::SingleThreadTaskRunner> GetParentTaskRunnerForTesting() {
return parent_thread_default_task_runner_;
}
scheduler::WorkerScheduler* GetScheduler();
// Returns a task runner bound to this worker. Users of the task runner don't
// have to care about the lifetime of the worker. When the worker global scope
// is destroyed, the task runner starts failing PostTask calls and discards
// queued tasks. This function can be called from any threads.
scoped_refptr<base::SingleThreadTaskRunner> GetTaskRunner(TaskType type);
void ChildThreadStartedOnWorkerThread(WorkerThread*);
void ChildThreadTerminatedOnWorkerThread(WorkerThread*);
// Changes the lifecycle state of the associated execution context for
// this worker to Paused and may enter a nested run loop. Only one nested
// message loop will be entered but |pause_or_freeze_count_| will be
// incremented on each call. Inspector can call pause when this thread is
// first created. May be called multiple times and from any thread.
void Pause();
// Changes the lifecycle state of the associated execution context for
// this worker to FrozenPaused and may enter a nested run loop. Only one
// nested message loop will be entered but |pause_or_freeze_count_| will be
// incremented on each call. May be called multiple times and from any thread.
void Freeze();
// Decrements |pause_or_freeze_count_| and if count is zero then
// it will exit the entered nested run loop. Might be called from any thread.
void Resume();
protected:
explicit WorkerThread(WorkerReportingProxy&);
// For service workers. When service workers are started on the IO thread
// Thread::Current() wouldn't be available so we need to pass the parent
// thread default task runner explicitly.
WorkerThread(WorkerReportingProxy&,
scoped_refptr<base::SingleThreadTaskRunner>
parent_thread_default_task_runner);
virtual ThreadType GetThreadType() const = 0;
// Official moment of creation of worker: when the worker thread is created.
// (https://w3c.github.io/hr-time/#time-origin)
const base::TimeTicks time_origin_;
private:
friend class WorkerThreadTest;
FRIEND_TEST_ALL_PREFIXES(WorkerThreadTest, ShouldTerminateScriptExecution);
FRIEND_TEST_ALL_PREFIXES(
WorkerThreadTest,
Terminate_WhileDebuggerTaskIsRunningOnInitialization);
FRIEND_TEST_ALL_PREFIXES(WorkerThreadTest,
Terminate_WhileDebuggerTaskIsRunning);
// Contains threads which are created but haven't started.
static HashSet<WorkerThread*>& InitializingWorkerThreads();
// Contains threads which have started.
static HashSet<WorkerThread*>& WorkerThreads();
// This mutex guards both WorkerThreads() and InitializingWorkerThreads().
static Mutex& ThreadSetMutex();
// Represents the state of this worker thread.
enum class ThreadState {
kNotStarted,
kRunning,
kReadyToShutdown,
};
// Factory method for creating a new worker context for the thread.
// Called on the worker thread.
virtual WorkerOrWorkletGlobalScope* CreateWorkerGlobalScope(
std::unique_ptr<GlobalScopeCreationParams>) = 0;
// Returns true when this WorkerThread owns the associated
// WorkerBackingThread exclusively. If this function returns true, the
// WorkerThread initializes / shutdowns the backing thread. Otherwise
// the backing thread should be initialized / shutdown properly out of this
// class.
virtual bool IsOwningBackingThread() const { return true; }
// Posts a delayed task to forcibly terminate script execution in case the
// normal shutdown sequence does not start within a certain time period.
void ScheduleToTerminateScriptExecution();
enum class TerminationState {
kTerminate,
kPostponeTerminate,
kTerminationUnnecessary,
};
// Returns true if we should synchronously terminate the script execution so
// that a shutdown task can be handled by the thread event loop.
TerminationState ShouldTerminateScriptExecution()
EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// Terminates worker script execution if the worker thread is running and not
// already shutting down. Does not terminate if a debugger task is running,
// because the debugger task is guaranteed to finish and it heavily uses V8
// API calls which would crash after forcible script termination. Called on
// the main thread.
void EnsureScriptExecutionTerminates(ExitCode) LOCKS_EXCLUDED(mutex_);
// These are called in this order during worker thread startup.
void InitializeSchedulerOnWorkerThread(base::WaitableEvent*);
void InitializeOnWorkerThread(
std::unique_ptr<GlobalScopeCreationParams>,
const base::Optional<WorkerBackingThreadStartupData>&,
std::unique_ptr<WorkerDevToolsParams>) LOCKS_EXCLUDED(mutex_);
void EvaluateClassicScriptOnWorkerThread(
const KURL& script_url,
String source_code,
std::unique_ptr<Vector<uint8_t>> cached_meta_data,
const v8_inspector::V8StackTraceId& stack_id);
void FetchAndRunClassicScriptOnWorkerThread(
const KURL& script_url,
std::unique_ptr<WorkerMainScriptLoadParameters>
worker_main_script_load_params,
std::unique_ptr<CrossThreadFetchClientSettingsObjectData>
outside_settings_object,
WorkerResourceTimingNotifier* outside_resource_timing_notifier,
const v8_inspector::V8StackTraceId& stack_id);
void FetchAndRunModuleScriptOnWorkerThread(
const KURL& script_url,
std::unique_ptr<WorkerMainScriptLoadParameters>
worker_main_script_load_params,
std::unique_ptr<CrossThreadFetchClientSettingsObjectData>
outside_settings_object,
WorkerResourceTimingNotifier* outside_resource_timing_notifier,
network::mojom::CredentialsMode,
bool reject_coep_unsafe_none);
// PrepareForShutdownOnWorkerThread() notifies that the context will be
// destroyed, discards queued tasks to prevent running further tasks, and
// initiates termination of nested workers. It runs on the worker thread. It
// can be called due to the parent thread posting a task to run it on the
// worker thread, or the worker thread calling it itself synchronously.
//
// PerformShutdownOnWorkerThread() destroys the global scope, and notifies the
// parent thread of completion of worker shutdown. A call of this function can
// be postponed until all nested workers are terminated. It runs on the worker
// thread. It can be called due to the parent thread posting a task to run it
// on the worker thread, or the worker thread calling it itself synchronously
// after all nested workers are terminated.
//
// These are called in this order during worker shutdown.
//
// The reason why worker shutdown is separated into these 2 functions:
// Workers can simultaneously be requested to terminate for various reasons.
// To serialize the termination requests, worker shutdown is supposed to be
// initiated from the parent thread (i.e., Terminate()). On the other hand,
// queued tasks etc must be discarded as soon as possible after shutdown is
// requested to prevent running further tasks. To be specific, when close() is
// called on the worker global scope, queued tasks must be discarded soon
// before worker shutdown is formally requested via the parent thread. The
// HTML spec defines this behavior (see spec comments in DidProcessTask()).
// To achieve this, the worker thread runs PrepareForShutdownOnWorkerThread()
// immediately after the task that called close() (see DidProcessTask()), and
// then posts a task to the parent thread to request termination. In addition
// to that, separate functions are useful for waiting until all nested workers
// are terminated before the parent thread shut down.
void PrepareForShutdownOnWorkerThread() LOCKS_EXCLUDED(mutex_);
void PerformShutdownOnWorkerThread() LOCKS_EXCLUDED(mutex_);
void SetThreadState(ThreadState) EXCLUSIVE_LOCKS_REQUIRED(mutex_);
void SetExitCode(ExitCode) EXCLUSIVE_LOCKS_REQUIRED(mutex_);
bool CheckRequestedToTerminate() LOCKS_EXCLUDED(mutex_);
class InterruptData;
void PauseOrFreeze(mojom::FrameLifecycleState state);
void PauseOrFreezeOnWorkerThread(mojom::FrameLifecycleState state);
void ResumeOnWorkerThread();
void PauseOrFreezeWithInterruptDataOnWorkerThread(InterruptData*);
static void PauseOrFreezeInsideV8InterruptOnWorkerThread(v8::Isolate*,
void* data);
static void PauseOrFreezeInsidePostTaskOnWorkerThread(
InterruptData* interrupt_data);
// A unique identifier among all WorkerThreads.
const int worker_thread_id_;
// Set on the main thread.
bool requested_to_terminate_ GUARDED_BY(mutex_) = false;
ThreadState thread_state_ GUARDED_BY(mutex_) = ThreadState::kNotStarted;
ExitCode exit_code_ GUARDED_BY(mutex_) = ExitCode::kNotTerminated;
base::TimeDelta forcible_termination_delay_;
scoped_refptr<InspectorTaskRunner> inspector_task_runner_;
base::UnguessableToken devtools_worker_token_;
int debugger_task_counter_ GUARDED_BY(mutex_) = 0;
WorkerReportingProxy& worker_reporting_proxy_;
// Task runner bound with the parent thread's default task queue. Be careful
// that a task runner may run even after the parent execution context and
// |this| are destroyed.
// This is used only for scheduling a worker termination and for testing.
scoped_refptr<base::SingleThreadTaskRunner>
parent_thread_default_task_runner_;
// Tasks managed by this scheduler are canceled when the global scope is
// closed.
std::unique_ptr<scheduler::WorkerScheduler> worker_scheduler_;
// Task runners bound with |worker_scheduler_|. These are captured when the
// worker scheduler is initialized.
using TaskRunnerHashMap = HashMap<TaskType,
scoped_refptr<base::SingleThreadTaskRunner>,
WTF::IntHash<TaskType>,
TaskTypeTraits>;
TaskRunnerHashMap worker_task_runners_;
// This lock protects shared states between the main thread and the worker
// thread. See thread-safety annotations (e.g., GUARDED_BY) in this header
// file.
Mutex mutex_;
// Whether the thread is paused in a nested message loop or not. Used
// only on the worker thread.
int pause_or_freeze_count_ = 0;
// A nested message loop for handling pausing. Pointer is not owned. Used only
// on the worker thread.
Platform::NestedMessageLoopRunner* nested_runner_ = nullptr;
CrossThreadPersistent<ConsoleMessageStorage> console_message_storage_;
CrossThreadPersistent<InspectorIssueStorage> inspector_issue_storage_;
CrossThreadPersistent<WorkerOrWorkletGlobalScope> global_scope_;
CrossThreadPersistent<WorkerInspectorController> worker_inspector_controller_;
// Signaled when the thread completes termination on the worker thread. Only
// the parent context thread should wait on this event after calling
// Terminate().
class RefCountedWaitableEvent;
scoped_refptr<RefCountedWaitableEvent> shutdown_event_;
// Used to cancel a scheduled forcible termination task. See
// mayForciblyTerminateExecution() for details.
TaskHandle forcible_termination_task_handle_;
HashSet<WorkerThread*> child_threads_;
// List of data to passed into the interrupt callbacks. The V8 API takes
// a void* and we need to pass more data that just a ptr, so we pass
// a pointer to a member in this list.
HashSet<std::unique_ptr<InterruptData>> pending_interrupts_
GUARDED_BY(mutex_);
THREAD_CHECKER(parent_thread_checker_);
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_WORKERS_WORKER_THREAD_H_