Chromium Code Reviews| Index: content/browser/service_worker/embedded_worker_instance.cc |
| diff --git a/content/browser/service_worker/embedded_worker_instance.cc b/content/browser/service_worker/embedded_worker_instance.cc |
| index 211c11fe07d8b9a2fbd154c667efe4e4373bddf5..46e797c5a474c8486adcbffad2928a4450131b07 100644 |
| --- a/content/browser/service_worker/embedded_worker_instance.cc |
| +++ b/content/browser/service_worker/embedded_worker_instance.cc |
| @@ -134,13 +134,189 @@ class EmbeddedWorkerInstance::DevToolsProxy : public base::NonThreadSafe { |
| DISALLOW_COPY_AND_ASSIGN(DevToolsProxy); |
| }; |
| +// A handle for a worker process managed by ServiceWorkerProcessManager on the |
| +// UI thread. |
| +class EmbeddedWorkerInstance::WorkerProcessHandle { |
| + public: |
| + WorkerProcessHandle(const base::WeakPtr<ServiceWorkerContextCore>& context, |
| + int embedded_worker_id, |
| + int process_id) |
| + : context_(context), |
| + embedded_worker_id_(embedded_worker_id), |
| + process_id_(process_id) { |
| + DCHECK_NE(ChildProcessHost::kInvalidUniqueID, process_id_); |
| + } |
| + |
| + ~WorkerProcessHandle() { |
| + if (context_) |
| + context_->process_manager()->ReleaseWorkerProcess(embedded_worker_id_); |
| + } |
| + |
| + int process_id() const { return process_id_; } |
| + |
| + private: |
| + base::WeakPtr<ServiceWorkerContextCore> context_; |
| + |
| + const int embedded_worker_id_; |
| + const int process_id_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(WorkerProcessHandle); |
| +}; |
| + |
| +// A task to allocate a worker process and to send a start worker message. This |
| +// is created on EmbeddedWorkerInstance::Start(), owned by the instance and |
| +// destroyed on EmbeddedWorkerInstance::OnScriptEvaluated(). |
| +// |
| +// We can abort starting worker by destroying this task anytime during the |
| +// sequence. In the case, the destructor releases a worker process. |
|
falken
2016/01/14 07:30:00
"the case" -> "the case where process allocation w
nhiroki
2016/01/14 08:43:40
Done.
|
| +class EmbeddedWorkerInstance::StartTask { |
|
falken
2016/01/14 07:30:00
This class is entirely in the IO thread right? Wou
nhiroki
2016/01/14 08:43:40
Done.
|
| + public: |
| + enum class ProcessAllocationState { NOT_ALLOCATED, ALLOCATING, ALLOCATED }; |
| + |
| + explicit StartTask(EmbeddedWorkerInstance* instance) |
| + : instance_(instance), |
| + state_(ProcessAllocationState::NOT_ALLOCATED), |
| + weak_factory_(this) {} |
| + |
| + ~StartTask() { |
| + if (!instance_->context_) |
| + return; |
| + |
| + switch (state_) { |
| + case ProcessAllocationState::NOT_ALLOCATED: |
| + // Not necessary to release a process. |
| + break; |
| + case ProcessAllocationState::ALLOCATING: |
| + // Abort half-baked process allocation on the UI thread. |
| + instance_->context_->process_manager()->ReleaseWorkerProcess( |
| + instance_->embedded_worker_id()); |
| + break; |
| + case ProcessAllocationState::ALLOCATED: |
| + // Otherwise, the process will be released by EmbeddedWorkerInstance. |
| + break; |
| + } |
| + |
| + // Don't have to abort |start_callback_| here. The caller of |
| + // EmbeddedWorkerInstance::Start(), that is, ServiceWorkerVersion does not |
| + // expect it when the start worker sequence is canceled by Stop() because |
| + // the callback, ServiceWorkerVersion::OnStartSentAndScriptEvaluated(), |
| + // could drain valid start requests queued in the version. After the worker |
| + // is stopped, the version attempts to restart the worker if there are |
| + // requests in the queue. See ServiceWorkerVersion::OnStoppedInternal() for |
| + // details. |
| + // TODO(nhiroki): Reconsider this bizarre layering. |
| + } |
| + |
| + void Start(scoped_ptr<EmbeddedWorkerMsg_StartWorker_Params> params, |
| + const StatusCallback& callback) { |
| + TRACE_EVENT_ASYNC_BEGIN2("ServiceWorker", |
| + "EmbeddedWorkerInstance::ProcessAllocate", |
| + params.get(), "Scope", params->scope.spec(), |
| + "Script URL", params->script_url.spec()); |
| + state_ = ProcessAllocationState::ALLOCATING; |
| + start_callback_ = callback; |
| + |
| + GURL scope(params->scope); |
| + GURL script_url(params->script_url); |
| + instance_->context_->process_manager()->AllocateWorkerProcess( |
| + instance_->embedded_worker_id(), scope, script_url, |
| + base::Bind(&StartTask::OnProcessAllocated, weak_factory_.GetWeakPtr(), |
| + base::Passed(¶ms))); |
| + } |
| + |
| + static void RunStartCallback(StartTask* task, |
| + ServiceWorkerStatusCode status) { |
| + StatusCallback callback = task->start_callback_; |
| + task->start_callback_.Reset(); |
| + callback.Run(status); |
| + // |task| may be destroyed. |
| + } |
| + |
| + private: |
| + void OnProcessAllocated( |
| + scoped_ptr<EmbeddedWorkerMsg_StartWorker_Params> params, |
| + ServiceWorkerStatusCode status, |
| + int process_id, |
| + bool is_new_process) { |
| + TRACE_EVENT_ASYNC_END1("ServiceWorker", |
| + "EmbeddedWorkerInstance::ProcessAllocate", |
| + params.get(), "Status", status); |
| + |
| + if (status != SERVICE_WORKER_OK) { |
| + DCHECK_EQ(ChildProcessHost::kInvalidUniqueID, process_id); |
| + StatusCallback callback = start_callback_; |
| + start_callback_.Reset(); |
| + instance_->OnStartFailed(callback, status); |
| + // |this| may be destroyed. |
| + return; |
| + } |
| + |
| + // Notify the instance that a process is allocated. |
| + state_ = ProcessAllocationState::ALLOCATED; |
| + instance_->OnProcessAllocated(make_scoped_ptr(new WorkerProcessHandle( |
| + instance_->context_, instance_->embedded_worker_id(), process_id))); |
| + |
| + // Register the instance to DevToolsManager on UI thread. |
| + const int64_t service_worker_version_id = params->service_worker_version_id; |
| + GURL script_url(params->script_url); |
| + BrowserThread::PostTask( |
| + BrowserThread::UI, FROM_HERE, |
| + base::Bind(RegisterToWorkerDevToolsManagerOnUI, process_id, |
| + instance_->context_.get(), instance_->context_, |
| + service_worker_version_id, script_url, |
| + base::Bind(&StartTask::OnRegisteredToDevToolsManager, |
| + weak_factory_.GetWeakPtr(), base::Passed(¶ms), |
| + is_new_process))); |
| + } |
| + |
| + void OnRegisteredToDevToolsManager( |
| + scoped_ptr<EmbeddedWorkerMsg_StartWorker_Params> params, |
| + bool is_new_process, |
| + int worker_devtools_agent_route_id, |
| + bool wait_for_debugger) { |
| + // Notify the instance that it is registered to the devtools manager. |
| + params->worker_devtools_agent_route_id = worker_devtools_agent_route_id; |
| + params->wait_for_debugger = wait_for_debugger; |
| + instance_->OnRegisteredToDevToolsManager( |
| + is_new_process, worker_devtools_agent_route_id, wait_for_debugger); |
| + |
| + SendStartWorker(std::move(params)); |
| + } |
| + |
| + void SendStartWorker( |
| + scoped_ptr<EmbeddedWorkerMsg_StartWorker_Params> params) { |
| + ServiceWorkerStatusCode status = instance_->registry_->SendStartWorker( |
| + std::move(params), instance_->process_id()); |
| + if (status != SERVICE_WORKER_OK) { |
| + StatusCallback callback = start_callback_; |
| + start_callback_.Reset(); |
| + instance_->OnStartFailed(callback, status); |
| + // |this| may be destroyed. |
| + return; |
| + } |
| + instance_->OnStartWorkerMessageSent(); |
| + |
| + // |start_callback_| will be called via RunStartCallback() when the script |
| + // is evaluated. |
| + } |
| + |
| + // |instance_| must outlive |this|. |
| + EmbeddedWorkerInstance* instance_; |
| + |
| + StatusCallback start_callback_; |
| + ProcessAllocationState state_; |
| + |
| + base::WeakPtrFactory<StartTask> weak_factory_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(StartTask); |
| +}; |
| + |
| EmbeddedWorkerInstance::~EmbeddedWorkerInstance() { |
| DCHECK(status_ == STOPPING || status_ == STOPPED) << status_; |
| devtools_proxy_.reset(); |
| - if (context_ && process_id_ != ChildProcessHost::kInvalidUniqueID) |
| - context_->process_manager()->ReleaseWorkerProcess(embedded_worker_id_); |
| if (registry_->GetWorker(embedded_worker_id_)) |
| - registry_->RemoveWorker(process_id_, embedded_worker_id_); |
| + registry_->RemoveWorker(process_id(), embedded_worker_id_); |
| + process_handle_.reset(); |
| } |
| void EmbeddedWorkerInstance::Start(int64_t service_worker_version_id, |
| @@ -153,6 +329,7 @@ void EmbeddedWorkerInstance::Start(int64_t service_worker_version_id, |
| return; |
| } |
| DCHECK(status_ == STOPPED); |
| + |
| // TODO(horo): If we will see crashes here, we have to find the root cause of |
| // the invalid version ID. Otherwise change CHECK to DCHECK. |
| CHECK_NE(service_worker_version_id, kInvalidServiceWorkerVersionId); |
| @@ -162,13 +339,9 @@ void EmbeddedWorkerInstance::Start(int64_t service_worker_version_id, |
| network_accessed_for_script_ = false; |
| service_registry_.reset(new ServiceRegistryImpl()); |
| FOR_EACH_OBSERVER(Listener, listener_list_, OnStarting()); |
| + |
| scoped_ptr<EmbeddedWorkerMsg_StartWorker_Params> params( |
| new EmbeddedWorkerMsg_StartWorker_Params()); |
| - TRACE_EVENT_ASYNC_BEGIN2("ServiceWorker", |
| - "EmbeddedWorkerInstance::ProcessAllocate", |
| - params.get(), |
| - "Scope", scope.spec(), |
| - "Script URL", script_url.spec()); |
| params->embedded_worker_id = embedded_worker_id_; |
| params->service_worker_version_id = service_worker_version_id; |
| params->scope = scope; |
| @@ -176,21 +349,19 @@ void EmbeddedWorkerInstance::Start(int64_t service_worker_version_id, |
| params->worker_devtools_agent_route_id = MSG_ROUTING_NONE; |
| params->wait_for_debugger = false; |
| params->v8_cache_options = GetV8CacheOptions(); |
| - context_->process_manager()->AllocateWorkerProcess( |
| - embedded_worker_id_, |
| - scope, |
| - script_url, |
| - base::Bind(&EmbeddedWorkerInstance::RunProcessAllocated, |
| - weak_factory_.GetWeakPtr(), |
| - context_, |
| - base::Passed(¶ms), |
| - callback)); |
| + |
| + inflight_start_task_.reset(new StartTask(this)); |
| + inflight_start_task_->Start(std::move(params), callback); |
| } |
| ServiceWorkerStatusCode EmbeddedWorkerInstance::Stop() { |
| DCHECK(status_ == STARTING || status_ == RUNNING) << status_; |
| + |
| + // Abort an inflight start task. |
| + inflight_start_task_.reset(); |
| + |
| ServiceWorkerStatusCode status = |
| - registry_->StopWorker(process_id_, embedded_worker_id_); |
| + registry_->StopWorker(process_id(), embedded_worker_id_); |
| UMA_HISTOGRAM_ENUMERATION("ServiceWorker.SendStopWorker.Status", status, |
| SERVICE_WORKER_ERROR_MAX_VALUE); |
| // StopWorker could fail if we were starting up and don't have a process yet, |
| @@ -219,7 +390,7 @@ ServiceWorkerStatusCode EmbeddedWorkerInstance::SendMessage( |
| DCHECK_NE(kInvalidEmbeddedWorkerThreadId, thread_id_); |
| if (status_ != RUNNING && status_ != STARTING) |
| return SERVICE_WORKER_ERROR_IPC_FAILED; |
| - return registry_->Send(process_id_, |
| + return registry_->Send(process_id(), |
| new EmbeddedWorkerContextMsg_MessageToWorker( |
| thread_id_, embedded_worker_id_, message)); |
| } |
| @@ -237,92 +408,31 @@ EmbeddedWorkerInstance::EmbeddedWorkerInstance( |
| embedded_worker_id_(embedded_worker_id), |
| status_(STOPPED), |
| starting_phase_(NOT_STARTING), |
| - process_id_(ChildProcessHost::kInvalidUniqueID), |
| thread_id_(kInvalidEmbeddedWorkerThreadId), |
| devtools_attached_(false), |
| network_accessed_for_script_(false), |
| weak_factory_(this) {} |
| -// static |
| -void EmbeddedWorkerInstance::RunProcessAllocated( |
| - base::WeakPtr<EmbeddedWorkerInstance> instance, |
| - base::WeakPtr<ServiceWorkerContextCore> context, |
| - scoped_ptr<EmbeddedWorkerMsg_StartWorker_Params> params, |
| - const EmbeddedWorkerInstance::StatusCallback& callback, |
| - ServiceWorkerStatusCode status, |
| - int process_id, |
| - bool is_new_process) { |
| - if (!context) { |
| - callback.Run(SERVICE_WORKER_ERROR_ABORT); |
| - return; |
| - } |
| - if (!instance) { |
| - if (status == SERVICE_WORKER_OK) { |
| - // We only have a process allocated if the status is OK. |
| - context->process_manager()->ReleaseWorkerProcess( |
| - params->embedded_worker_id); |
| - } |
| - callback.Run(SERVICE_WORKER_ERROR_ABORT); |
| - return; |
| - } |
| - instance->ProcessAllocated(std::move(params), callback, process_id, |
| - is_new_process, status); |
| -} |
| +void EmbeddedWorkerInstance::OnProcessAllocated( |
| + scoped_ptr<WorkerProcessHandle> handle) { |
| + DCHECK_EQ(STARTING, status_); |
| + DCHECK(!process_handle_); |
| -void EmbeddedWorkerInstance::ProcessAllocated( |
| - scoped_ptr<EmbeddedWorkerMsg_StartWorker_Params> params, |
| - const StatusCallback& callback, |
| - int process_id, |
| - bool is_new_process, |
| - ServiceWorkerStatusCode status) { |
| - DCHECK_EQ(process_id_, ChildProcessHost::kInvalidUniqueID); |
| - TRACE_EVENT_ASYNC_END1("ServiceWorker", |
| - "EmbeddedWorkerInstance::ProcessAllocate", |
| - params.get(), |
| - "Status", status); |
| - if (status != SERVICE_WORKER_OK) { |
| - OnStartFailed(callback, status); |
| - return; |
| - } |
| - const int64_t service_worker_version_id = params->service_worker_version_id; |
| - process_id_ = process_id; |
| - GURL script_url(params->script_url); |
| - |
| - // Register this worker to DevToolsManager on UI thread, then continue to |
| - // call SendStartWorker on IO thread. |
| + process_handle_ = std::move(handle); |
| starting_phase_ = REGISTERING_TO_DEVTOOLS; |
| - BrowserThread::PostTask( |
| - BrowserThread::UI, FROM_HERE, |
| - base::Bind(RegisterToWorkerDevToolsManagerOnUI, process_id_, |
| - context_.get(), context_, service_worker_version_id, |
| - script_url, |
| - base::Bind(&EmbeddedWorkerInstance::SendStartWorker, |
| - weak_factory_.GetWeakPtr(), base::Passed(¶ms), |
| - callback, is_new_process))); |
| + FOR_EACH_OBSERVER(Listener, listener_list_, OnProcessAllocated()); |
| } |
| -void EmbeddedWorkerInstance::SendStartWorker( |
| - scoped_ptr<EmbeddedWorkerMsg_StartWorker_Params> params, |
| - const StatusCallback& callback, |
| +void EmbeddedWorkerInstance::OnRegisteredToDevToolsManager( |
| bool is_new_process, |
| int worker_devtools_agent_route_id, |
| bool wait_for_debugger) { |
| - // We may have been detached or stopped at some point during the start up |
| - // process, making process_id_ and other state invalid. If that happened, |
| - // abort instead of trying to send the IPC. |
| - if (status_ != STARTING) { |
| - OnStartFailed(callback, SERVICE_WORKER_ERROR_ABORT); |
| - return; |
| - } |
| - |
| if (worker_devtools_agent_route_id != MSG_ROUTING_NONE) { |
| DCHECK(!devtools_proxy_); |
| - devtools_proxy_.reset(new DevToolsProxy(process_id_, |
| - worker_devtools_agent_route_id)); |
| + devtools_proxy_.reset( |
| + new DevToolsProxy(process_id(), worker_devtools_agent_route_id)); |
| } |
| - params->worker_devtools_agent_route_id = worker_devtools_agent_route_id; |
| - params->wait_for_debugger = wait_for_debugger; |
| - if (params->wait_for_debugger) { |
| + if (wait_for_debugger) { |
| // We don't measure the start time when wait_for_debugger flag is set. So we |
| // set the NULL time here. |
| start_timing_ = base::TimeTicks(); |
| @@ -341,16 +451,11 @@ void EmbeddedWorkerInstance::SendStartWorker( |
| // allocation time. |
| start_timing_ = base::TimeTicks::Now(); |
| } |
| +} |
| +void EmbeddedWorkerInstance::OnStartWorkerMessageSent() { |
| starting_phase_ = SENT_START_WORKER; |
| - ServiceWorkerStatusCode status = |
| - registry_->SendStartWorker(std::move(params), process_id_); |
| - if (status != SERVICE_WORKER_OK) { |
| - OnStartFailed(callback, status); |
| - return; |
| - } |
| - DCHECK(start_callback_.is_null()); |
| - start_callback_ = callback; |
| + FOR_EACH_OBSERVER(Listener, listener_list_, OnStartWorkerMessageSent()); |
| } |
| void EmbeddedWorkerInstance::OnReadyForInspection() { |
| @@ -396,7 +501,7 @@ void EmbeddedWorkerInstance::OnThreadStarted(int thread_id) { |
| GetProxy(&services); |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| - base::Bind(SetupMojoOnUIThread, process_id_, thread_id_, |
| + base::Bind(SetupMojoOnUIThread, process_id(), thread_id_, |
| base::Passed(&services_request), |
| base::Passed(exposed_services.PassInterface()))); |
| service_registry_->BindRemoteServiceProvider(std::move(services)); |
| @@ -407,19 +512,21 @@ void EmbeddedWorkerInstance::OnScriptLoadFailed() { |
| } |
| void EmbeddedWorkerInstance::OnScriptEvaluated(bool success) { |
| - starting_phase_ = SCRIPT_EVALUATED; |
| - if (start_callback_.is_null()) { |
| - DVLOG(1) << "Received unexpected OnScriptEvaluated message."; |
| + if (!inflight_start_task_) |
| return; |
| - } |
| + DCHECK_EQ(STARTING, status_); |
| + |
| + starting_phase_ = SCRIPT_EVALUATED; |
| if (success && !start_timing_.is_null()) { |
| UMA_HISTOGRAM_TIMES("EmbeddedWorkerInstance.ScriptEvaluate", |
| base::TimeTicks::Now() - start_timing_); |
| } |
| - StatusCallback callback = start_callback_; |
| - start_callback_.Reset(); |
| - callback.Run(success ? SERVICE_WORKER_OK |
| - : SERVICE_WORKER_ERROR_SCRIPT_EVALUATE_FAILED); |
| + |
| + base::WeakPtr<EmbeddedWorkerInstance> weak_this = weak_factory_.GetWeakPtr(); |
| + StartTask::RunStartCallback( |
| + inflight_start_task_.get(), |
| + success ? SERVICE_WORKER_OK |
| + : SERVICE_WORKER_ERROR_SCRIPT_EVALUATE_FAILED); |
| // |this| may be destroyed by the callback. |
| } |
| @@ -429,6 +536,7 @@ void EmbeddedWorkerInstance::OnStarted() { |
| return; |
| DCHECK(status_ == STARTING); |
| status_ = RUNNING; |
| + inflight_start_task_.reset(); |
| FOR_EACH_OBSERVER(Listener, listener_list_, OnStarted()); |
| } |
| @@ -445,7 +553,7 @@ void EmbeddedWorkerInstance::OnDetached() { |
| } |
| void EmbeddedWorkerInstance::Detach() { |
| - registry_->RemoveWorker(process_id_, embedded_worker_id_); |
| + registry_->RemoveWorker(process_id(), embedded_worker_id_); |
| OnDetached(); |
| } |
| @@ -482,6 +590,12 @@ void EmbeddedWorkerInstance::OnReportConsoleMessage( |
| source_identifier, message_level, message, line_number, source_url)); |
| } |
| +int EmbeddedWorkerInstance::process_id() const { |
| + if (process_handle_) |
| + return process_handle_->process_id(); |
| + return ChildProcessHost::kInvalidUniqueID; |
| +} |
| + |
| int EmbeddedWorkerInstance::worker_devtools_agent_route_id() const { |
| if (devtools_proxy_) |
| return devtools_proxy_->agent_route_id(); |
| @@ -490,7 +604,7 @@ int EmbeddedWorkerInstance::worker_devtools_agent_route_id() const { |
| MessagePortMessageFilter* EmbeddedWorkerInstance::message_port_message_filter() |
| const { |
| - return registry_->MessagePortMessageFilterForProcess(process_id_); |
| + return registry_->MessagePortMessageFilterForProcess(process_id()); |
| } |
| void EmbeddedWorkerInstance::AddListener(Listener* listener) { |
| @@ -507,14 +621,14 @@ void EmbeddedWorkerInstance::OnNetworkAccessedForScriptLoad() { |
| } |
| void EmbeddedWorkerInstance::ReleaseProcess() { |
| + // Abort an inflight start task. |
| + inflight_start_task_.reset(); |
| + |
| devtools_proxy_.reset(); |
| - if (context_ && process_id_ != ChildProcessHost::kInvalidUniqueID) |
| - context_->process_manager()->ReleaseWorkerProcess(embedded_worker_id_); |
| + process_handle_.reset(); |
| status_ = STOPPED; |
| - process_id_ = ChildProcessHost::kInvalidUniqueID; |
| thread_id_ = kInvalidEmbeddedWorkerThreadId; |
| service_registry_.reset(); |
| - start_callback_.Reset(); |
| } |
| void EmbeddedWorkerInstance::OnStartFailed(const StatusCallback& callback, |