blob: 856c465a5567670d9d7c45e3494b4d87c1a021e6 [file] [log] [blame]
// Copyright 2019 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 "third_party/blink/renderer/modules/scheduler/dom_task.h"
#include <utility>
#include "base/check_op.h"
#include "base/metrics/histogram_macros.h"
#include "third_party/blink/renderer/bindings/core/v8/script_value.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_scheduler_post_task_callback.h"
#include "third_party/blink/renderer/core/dom/dom_exception.h"
#include "third_party/blink/renderer/core/probe/core_probes.h"
#include "third_party/blink/renderer/modules/scheduler/dom_scheduler.h"
#include "third_party/blink/renderer/modules/scheduler/dom_task_signal.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
namespace blink {
#define QUEUEING_TIME_PER_PRIORITY_METRIC_NAME \
"DOMScheduler.QueueingDurationPerPriority"
#define PRIORITY_CHANGED_HISTOGRAM_NAME \
"DOMSchedler.TaskSignalPriorityWasChanged"
// Same as UMA_HISTOGRAM_TIMES but for a broader view of this metric we end
// at 1 minute instead of 10 seconds.
#define QUEUEING_TIME_HISTOGRAM(name, sample) \
UMA_HISTOGRAM_CUSTOM_TIMES(QUEUEING_TIME_PER_PRIORITY_METRIC_NAME name, \
sample, base::TimeDelta::FromMilliseconds(1), \
base::TimeDelta::FromMinutes(1), 50)
DOMTask::DOMTask(DOMScheduler* scheduler,
ScriptPromiseResolver* resolver,
V8SchedulerPostTaskCallback* callback,
DOMTaskSignal* signal,
base::TimeDelta delay)
: scheduler_(scheduler),
callback_(callback),
resolver_(resolver),
signal_(signal),
// TODO(kdillon): Expose queuing time from base::sequence_manager so we
// don't have to recalculate it here.
queue_time_(delay.is_zero() ? base::TimeTicks::Now()
: base::TimeTicks()) {
DCHECK(signal_);
DCHECK(signal_->GetTaskRunner());
DCHECK(callback_);
signal_->AddAlgorithm(WTF::Bind(&DOMTask::Abort, WrapWeakPersistent(this)));
task_handle_ = PostDelayedCancellableTask(
*signal_->GetTaskRunner(), FROM_HERE,
WTF::Bind(&DOMTask::Invoke, WrapPersistent(this)), delay);
ScriptState* script_state =
callback_->CallbackRelevantScriptStateOrReportError("DOMTask", "Create");
DCHECK(script_state && script_state->ContextIsValid());
probe::AsyncTaskScheduled(ExecutionContext::From(script_state), "postTask",
&async_task_id_);
}
void DOMTask::Trace(Visitor* visitor) const {
visitor->Trace(scheduler_);
visitor->Trace(callback_);
visitor->Trace(resolver_);
visitor->Trace(signal_);
}
void DOMTask::Invoke() {
DCHECK(callback_);
ScriptState* script_state =
callback_->CallbackRelevantScriptStateOrReportError("DOMTask", "Invoke");
if (!script_state || !script_state->ContextIsValid())
return;
RecordTaskStartMetrics();
InvokeInternal(script_state);
callback_.Release();
}
void DOMTask::InvokeInternal(ScriptState* script_state) {
v8::Isolate* isolate = script_state->GetIsolate();
ScriptState::Scope scope(script_state);
v8::TryCatch try_catch(isolate);
ExecutionContext* context = ExecutionContext::From(script_state);
DCHECK(context);
probe::AsyncTask async_task(context, &async_task_id_);
probe::UserCallback probe(context, "postTask", AtomicString(), true);
v8::Local<v8::Context> v8_context = script_state->GetContext();
v8_context->SetContinuationPreservedEmbedderData(
ToV8(signal_.Get(), v8_context->Global(), isolate));
ScriptValue result;
if (callback_->Invoke(nullptr).To(&result))
resolver_->Resolve(result.V8Value());
else if (try_catch.HasCaught())
resolver_->Reject(try_catch.Exception());
v8_context->SetContinuationPreservedEmbedderData(v8::Local<v8::Object>());
}
void DOMTask::Abort() {
// If the task has already finished running, the promise is either resolved or
// rejected, in which case abort will no longer have any effect.
if (!callback_)
return;
task_handle_.Cancel();
resolver_->Reject(
MakeGarbageCollected<DOMException>(DOMExceptionCode::kAbortError));
ScriptState* script_state =
callback_->CallbackRelevantScriptStateOrReportError("DOMTask", "Abort");
DCHECK(script_state && script_state->ContextIsValid());
probe::AsyncTaskCanceled(ExecutionContext::From(script_state),
&async_task_id_);
}
void DOMTask::RecordTaskStartMetrics() {
UMA_HISTOGRAM_ENUMERATION(PRIORITY_CHANGED_HISTOGRAM_NAME,
signal_->GetPriorityChangeStatus());
if (queue_time_ > base::TimeTicks()) {
base::TimeDelta queue_duration = base::TimeTicks::Now() - queue_time_;
DCHECK_GT(queue_duration, base::TimeDelta());
if (signal_->GetPriorityChangeStatus() ==
DOMTaskSignal::PriorityChangeStatus::kNoPriorityChange) {
WebSchedulingPriority priority =
WebSchedulingPriorityFromString(signal_->priority());
switch (priority) {
case WebSchedulingPriority::kUserBlockingPriority:
QUEUEING_TIME_HISTOGRAM(".UserBlocking", queue_duration);
break;
case WebSchedulingPriority::kUserVisiblePriority:
QUEUEING_TIME_HISTOGRAM(".UserVisable", queue_duration);
break;
case WebSchedulingPriority::kBackgroundPriority:
QUEUEING_TIME_HISTOGRAM(".Background", queue_duration);
break;
}
}
}
}
} // namespace blink