| /* |
| * 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, |
| kLastEnum, |
| }; |
| |
| ~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_; } |
| |
| PlatformThreadId GetPlatformThreadId(); |
| |
| 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_ |