blob: 5c6a40a2bf35c19c3e79d145181d77111636a529 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "core/workers/WorkerThread.h"
#include <memory>
#include <utility>
#include "bindings/core/v8/V8CacheOptions.h"
#include "core/frame/Settings.h"
#include "core/inspector/InspectorTaskRunner.h"
#include "core/workers/GlobalScopeCreationParams.h"
#include "core/workers/WorkerReportingProxy.h"
#include "core/workers/WorkerThreadTestHelper.h"
#include "platform/WaitableEvent.h"
#include "platform/testing/UnitTestHelpers.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::AtMost;
namespace blink {
using ExitCode = WorkerThread::ExitCode;
namespace {
class MockWorkerReportingProxy final : public WorkerReportingProxy {
public:
MockWorkerReportingProxy() = default;
~MockWorkerReportingProxy() override = default;
MOCK_METHOD1(DidCreateWorkerGlobalScope, void(WorkerOrWorkletGlobalScope*));
MOCK_METHOD0(DidInitializeWorkerContext, void());
MOCK_METHOD2(WillEvaluateWorkerScriptMock,
void(size_t scriptSize, size_t cachedMetadataSize));
MOCK_METHOD1(DidEvaluateWorkerScript, void(bool success));
MOCK_METHOD0(DidCloseWorkerGlobalScope, void());
MOCK_METHOD0(WillDestroyWorkerGlobalScope, void());
MOCK_METHOD0(DidTerminateWorkerThread, void());
void WillEvaluateWorkerScript(size_t script_size,
size_t cached_metadata_size) override {
script_evaluation_event_.Signal();
WillEvaluateWorkerScriptMock(script_size, cached_metadata_size);
}
void WaitUntilScriptEvaluation() { script_evaluation_event_.Wait(); }
private:
WaitableEvent script_evaluation_event_;
};
// Used as a debugger task. Waits for a signal from the main thread.
void WaitForSignalTask(WorkerThread* worker_thread,
WaitableEvent* waitable_event) {
EXPECT_TRUE(worker_thread->IsCurrentThread());
// Notify the main thread that the debugger task is waiting for the signal.
PostCrossThreadTask(
*worker_thread->GetParentFrameTaskRunners()->Get(TaskType::kInternalTest),
FROM_HERE, CrossThreadBind(&testing::ExitRunLoop));
waitable_event->Wait();
}
} // namespace
class WorkerThreadTest : public ::testing::Test {
public:
WorkerThreadTest() = default;
void SetUp() override {
reporting_proxy_ = std::make_unique<MockWorkerReportingProxy>();
security_origin_ = SecurityOrigin::Create(KURL("http://fake.url/"));
worker_thread_ =
std::make_unique<WorkerThreadForTest>(nullptr, *reporting_proxy_);
lifecycle_observer_ = new MockWorkerThreadLifecycleObserver(
worker_thread_->GetWorkerThreadLifecycleContext());
}
void TearDown() override {}
void Start() {
worker_thread_->StartWithSourceCode(security_origin_.get(),
"//fake source code",
ParentFrameTaskRunners::Create());
}
void StartWithSourceCodeNotToFinish() {
// Use a JavaScript source code that makes an infinite loop so that we
// can catch some kind of issues as a timeout.
worker_thread_->StartWithSourceCode(security_origin_.get(),
"while(true) {}",
ParentFrameTaskRunners::Create());
}
void SetForcibleTerminationDelay(TimeDelta forcible_termination_delay) {
worker_thread_->forcible_termination_delay_ = forcible_termination_delay;
}
bool IsForcibleTerminationTaskScheduled() {
return worker_thread_->forcible_termination_task_handle_.IsActive();
}
protected:
void ExpectReportingCalls() {
EXPECT_CALL(*reporting_proxy_, DidCreateWorkerGlobalScope(_)).Times(1);
EXPECT_CALL(*reporting_proxy_, DidInitializeWorkerContext()).Times(1);
EXPECT_CALL(*reporting_proxy_, WillEvaluateWorkerScriptMock(_, _)).Times(1);
EXPECT_CALL(*reporting_proxy_, DidEvaluateWorkerScript(true)).Times(1);
EXPECT_CALL(*reporting_proxy_, WillDestroyWorkerGlobalScope()).Times(1);
EXPECT_CALL(*reporting_proxy_, DidTerminateWorkerThread()).Times(1);
EXPECT_CALL(*lifecycle_observer_, ContextDestroyed(_)).Times(1);
}
void ExpectReportingCallsForWorkerPossiblyTerminatedBeforeInitialization() {
EXPECT_CALL(*reporting_proxy_, DidCreateWorkerGlobalScope(_)).Times(1);
EXPECT_CALL(*reporting_proxy_, DidInitializeWorkerContext())
.Times(AtMost(1));
EXPECT_CALL(*reporting_proxy_, WillEvaluateWorkerScriptMock(_, _))
.Times(AtMost(1));
EXPECT_CALL(*reporting_proxy_, DidEvaluateWorkerScript(_)).Times(AtMost(1));
EXPECT_CALL(*reporting_proxy_, WillDestroyWorkerGlobalScope())
.Times(AtMost(1));
EXPECT_CALL(*reporting_proxy_, DidTerminateWorkerThread()).Times(1);
EXPECT_CALL(*lifecycle_observer_, ContextDestroyed(_)).Times(1);
}
void ExpectReportingCallsForWorkerForciblyTerminated() {
EXPECT_CALL(*reporting_proxy_, DidCreateWorkerGlobalScope(_)).Times(1);
EXPECT_CALL(*reporting_proxy_, DidInitializeWorkerContext()).Times(1);
EXPECT_CALL(*reporting_proxy_, WillEvaluateWorkerScriptMock(_, _)).Times(1);
EXPECT_CALL(*reporting_proxy_, DidEvaluateWorkerScript(false)).Times(1);
EXPECT_CALL(*reporting_proxy_, WillDestroyWorkerGlobalScope()).Times(1);
EXPECT_CALL(*reporting_proxy_, DidTerminateWorkerThread()).Times(1);
EXPECT_CALL(*lifecycle_observer_, ContextDestroyed(_)).Times(1);
}
ExitCode GetExitCode() { return worker_thread_->GetExitCodeForTesting(); }
scoped_refptr<const SecurityOrigin> security_origin_;
std::unique_ptr<MockWorkerReportingProxy> reporting_proxy_;
std::unique_ptr<WorkerThreadForTest> worker_thread_;
Persistent<MockWorkerThreadLifecycleObserver> lifecycle_observer_;
};
TEST_F(WorkerThreadTest, ShouldTerminateScriptExecution) {
using ThreadState = WorkerThread::ThreadState;
worker_thread_->inspector_task_runner_ = InspectorTaskRunner::Create(nullptr);
// SetExitCode() and ShouldTerminateScriptExecution() require the lock.
MutexLocker dummy_lock(worker_thread_->mutex_);
EXPECT_EQ(ThreadState::kNotStarted, worker_thread_->thread_state_);
EXPECT_FALSE(worker_thread_->ShouldTerminateScriptExecution());
worker_thread_->SetThreadState(ThreadState::kRunning);
EXPECT_TRUE(worker_thread_->ShouldTerminateScriptExecution());
worker_thread_->SetThreadState(ThreadState::kReadyToShutdown);
EXPECT_FALSE(worker_thread_->ShouldTerminateScriptExecution());
// This is necessary to satisfy DCHECK in the dtor of WorkerThread.
worker_thread_->SetExitCode(ExitCode::kGracefullyTerminated);
}
TEST_F(WorkerThreadTest, AsyncTerminate_OnIdle) {
ExpectReportingCalls();
Start();
// Wait until the initialization completes and the worker thread becomes
// idle.
worker_thread_->WaitForInit();
// The worker thread is not being blocked, so the worker thread should be
// gracefully shut down.
worker_thread_->Terminate();
EXPECT_TRUE(IsForcibleTerminationTaskScheduled());
worker_thread_->WaitForShutdownForTesting();
EXPECT_EQ(ExitCode::kGracefullyTerminated, GetExitCode());
}
TEST_F(WorkerThreadTest, SyncTerminate_OnIdle) {
ExpectReportingCalls();
Start();
// Wait until the initialization completes and the worker thread becomes
// idle.
worker_thread_->WaitForInit();
WorkerThread::TerminateAllWorkersForTesting();
// The worker thread may gracefully shut down before forcible termination
// runs.
ExitCode exit_code = GetExitCode();
EXPECT_TRUE(ExitCode::kGracefullyTerminated == exit_code ||
ExitCode::kSyncForciblyTerminated == exit_code);
}
TEST_F(WorkerThreadTest, AsyncTerminate_ImmediatelyAfterStart) {
ExpectReportingCallsForWorkerPossiblyTerminatedBeforeInitialization();
Start();
// The worker thread is not being blocked, so the worker thread should be
// gracefully shut down.
worker_thread_->Terminate();
worker_thread_->WaitForShutdownForTesting();
EXPECT_EQ(ExitCode::kGracefullyTerminated, GetExitCode());
}
TEST_F(WorkerThreadTest, SyncTerminate_ImmediatelyAfterStart) {
ExpectReportingCallsForWorkerPossiblyTerminatedBeforeInitialization();
Start();
// There are two possible cases depending on timing:
// (1) If the thread hasn't been initialized on the worker thread yet,
// TerminateAllWorkersForTesting() should wait for initialization and shut
// down the thread immediately after that.
// (2) If the thread has already been initialized on the worker thread,
// TerminateAllWorkersForTesting() should synchronously forcibly terminates
// the worker execution.
WorkerThread::TerminateAllWorkersForTesting();
ExitCode exit_code = GetExitCode();
EXPECT_TRUE(ExitCode::kGracefullyTerminated == exit_code ||
ExitCode::kSyncForciblyTerminated == exit_code);
}
TEST_F(WorkerThreadTest, AsyncTerminate_WhileTaskIsRunning) {
constexpr TimeDelta kDelay = TimeDelta::FromMilliseconds(10);
SetForcibleTerminationDelay(kDelay);
ExpectReportingCallsForWorkerForciblyTerminated();
StartWithSourceCodeNotToFinish();
reporting_proxy_->WaitUntilScriptEvaluation();
// Terminate() schedules a forcible termination task.
worker_thread_->Terminate();
EXPECT_TRUE(IsForcibleTerminationTaskScheduled());
EXPECT_EQ(ExitCode::kNotTerminated, GetExitCode());
// Multiple Terminate() calls should not take effect.
worker_thread_->Terminate();
worker_thread_->Terminate();
EXPECT_EQ(ExitCode::kNotTerminated, GetExitCode());
// Wait until the forcible termination task runs.
testing::RunDelayedTasks(kDelay);
worker_thread_->WaitForShutdownForTesting();
EXPECT_EQ(ExitCode::kAsyncForciblyTerminated, GetExitCode());
}
TEST_F(WorkerThreadTest, SyncTerminate_WhileTaskIsRunning) {
ExpectReportingCallsForWorkerForciblyTerminated();
StartWithSourceCodeNotToFinish();
reporting_proxy_->WaitUntilScriptEvaluation();
// TerminateAllWorkersForTesting() synchronously terminates the worker
// execution.
WorkerThread::TerminateAllWorkersForTesting();
EXPECT_EQ(ExitCode::kSyncForciblyTerminated, GetExitCode());
}
TEST_F(WorkerThreadTest,
AsyncTerminateAndThenSyncTerminate_WhileTaskIsRunning) {
SetForcibleTerminationDelay(TimeDelta::FromMilliseconds(10));
ExpectReportingCallsForWorkerForciblyTerminated();
StartWithSourceCodeNotToFinish();
reporting_proxy_->WaitUntilScriptEvaluation();
// Terminate() schedules a forcible termination task.
worker_thread_->Terminate();
EXPECT_TRUE(IsForcibleTerminationTaskScheduled());
EXPECT_EQ(ExitCode::kNotTerminated, GetExitCode());
// TerminateAllWorkersForTesting() should overtake the scheduled forcible
// termination task.
WorkerThread::TerminateAllWorkersForTesting();
EXPECT_FALSE(IsForcibleTerminationTaskScheduled());
EXPECT_EQ(ExitCode::kSyncForciblyTerminated, GetExitCode());
}
TEST_F(WorkerThreadTest, Terminate_WhileDebuggerTaskIsRunningOnInitialization) {
constexpr TimeDelta kDelay = TimeDelta::FromMilliseconds(10);
SetForcibleTerminationDelay(kDelay);
EXPECT_CALL(*reporting_proxy_, DidCreateWorkerGlobalScope(_)).Times(1);
EXPECT_CALL(*reporting_proxy_, DidInitializeWorkerContext()).Times(1);
EXPECT_CALL(*reporting_proxy_, WillDestroyWorkerGlobalScope()).Times(1);
EXPECT_CALL(*reporting_proxy_, DidTerminateWorkerThread()).Times(1);
EXPECT_CALL(*lifecycle_observer_, ContextDestroyed(_)).Times(1);
auto headers = std::make_unique<Vector<CSPHeaderAndType>>();
CSPHeaderAndType header_and_type("contentSecurityPolicy",
kContentSecurityPolicyHeaderTypeReport);
headers->push_back(header_and_type);
auto global_scope_creation_params =
std::make_unique<GlobalScopeCreationParams>(
KURL("http://fake.url/"), "fake user agent", headers.get(),
kReferrerPolicyDefault, security_origin_.get(),
false /* starter_secure_context */, nullptr /* workerClients */,
mojom::IPAddressSpace::kLocal, nullptr /* originTrialToken */,
base::UnguessableToken::Create(),
std::make_unique<WorkerSettings>(Settings::Create().get()),
kV8CacheOptionsDefault, nullptr /* module_fetch_coordinator */);
// Specify PauseOnWorkerStart::kPause so that the worker thread can pause
// on initialization to run debugger tasks.
worker_thread_->Start(std::move(global_scope_creation_params),
WorkerBackingThreadStartupData::CreateDefault(),
WorkerInspectorProxy::PauseOnWorkerStart::kPause,
ParentFrameTaskRunners::Create());
// Used to wait for worker thread termination in a debugger task on the
// worker thread.
WaitableEvent waitable_event;
worker_thread_->AppendDebuggerTask(CrossThreadBind(
&WaitForSignalTask, CrossThreadUnretained(worker_thread_.get()),
CrossThreadUnretained(&waitable_event)));
// Wait for the debugger task.
testing::EnterRunLoop();
EXPECT_TRUE(worker_thread_->inspector_task_runner_->IsRunningTask());
// Terminate() schedules a forcible termination task.
worker_thread_->Terminate();
EXPECT_TRUE(IsForcibleTerminationTaskScheduled());
EXPECT_EQ(ExitCode::kNotTerminated, GetExitCode());
// Wait until the task runs. It shouldn't terminate the script execution
// because of the running debugger task.
testing::RunDelayedTasks(kDelay);
EXPECT_FALSE(IsForcibleTerminationTaskScheduled());
EXPECT_EQ(ExitCode::kNotTerminated, GetExitCode());
// Forcible script termination request should also respect the running
// debugger task.
worker_thread_->EnsureScriptExecutionTerminates(
ExitCode::kSyncForciblyTerminated);
EXPECT_EQ(ExitCode::kNotTerminated, GetExitCode());
// Resume the debugger task. Shutdown starts after that.
waitable_event.Signal();
worker_thread_->WaitForShutdownForTesting();
EXPECT_EQ(ExitCode::kGracefullyTerminated, GetExitCode());
}
TEST_F(WorkerThreadTest, Terminate_WhileDebuggerTaskIsRunning) {
constexpr TimeDelta kDelay = TimeDelta::FromMilliseconds(10);
SetForcibleTerminationDelay(kDelay);
ExpectReportingCalls();
Start();
worker_thread_->WaitForInit();
// Used to wait for worker thread termination in a debugger task on the
// worker thread.
WaitableEvent waitable_event;
worker_thread_->AppendDebuggerTask(CrossThreadBind(
&WaitForSignalTask, CrossThreadUnretained(worker_thread_.get()),
CrossThreadUnretained(&waitable_event)));
// Wait for the debugger task.
testing::EnterRunLoop();
EXPECT_TRUE(worker_thread_->inspector_task_runner_->IsRunningTask());
// Terminate() schedules a forcible termination task.
worker_thread_->Terminate();
EXPECT_TRUE(IsForcibleTerminationTaskScheduled());
EXPECT_EQ(ExitCode::kNotTerminated, GetExitCode());
// Wait until the task runs. It shouldn't terminate the script execution
// because of the running debugger task.
testing::RunDelayedTasks(kDelay);
EXPECT_FALSE(IsForcibleTerminationTaskScheduled());
EXPECT_EQ(ExitCode::kNotTerminated, GetExitCode());
// Forcible script termination request should also respect the running
// debugger task.
worker_thread_->EnsureScriptExecutionTerminates(
ExitCode::kSyncForciblyTerminated);
EXPECT_EQ(ExitCode::kNotTerminated, GetExitCode());
// Resume the debugger task. Shutdown starts after that.
waitable_event.Signal();
worker_thread_->WaitForShutdownForTesting();
EXPECT_EQ(ExitCode::kGracefullyTerminated, GetExitCode());
}
} // namespace blink