Commit d9fa0f84 authored by Makoto Shimazu's avatar Makoto Shimazu Committed by Commit Bot

Add an experiment to terminate service worker on no controllee

This CL is to add a new experiment ServiceWorkerTerminationOnNoControllee which
is to terminate a service worker soon after all controllees are gone. The
experiment introduces a small delay until the worker is actually stopped. If a
new client results in being controlled by the service worker during the delay,
the scheduled termination will be canceled.
By default, this feature is disabled and the termination happens based on the
existing 30-second idle timeout.

Bug: 1043845
Change-Id: I132fc2da300ddef7aa6e111e0af2e587b72c8121
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2011589
Commit-Queue: Makoto Shimazu <shimazu@chromium.org>
Reviewed-by: default avatarHiroki Nakagawa <nhiroki@chromium.org>
Reviewed-by: default avatarKinuko Yasuda <kinuko@chromium.org>
Reviewed-by: default avatarKenichi Ishibashi <bashi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#734363}
parent 0d62cfdc
...@@ -41,6 +41,7 @@ ...@@ -41,6 +41,7 @@
#include "content/public/browser/page_navigator.h" #include "content/public/browser/page_navigator.h"
#include "content/public/browser/service_worker_external_request_result.h" #include "content/public/browser/service_worker_external_request_result.h"
#include "content/public/common/content_client.h" #include "content/public/common/content_client.h"
#include "content/public/common/content_features.h"
#include "content/public/common/navigation_policy.h" #include "content/public/common/navigation_policy.h"
#include "content/public/common/result_codes.h" #include "content/public/common/result_codes.h"
#include "net/base/net_errors.h" #include "net/base/net_errors.h"
...@@ -64,6 +65,13 @@ constexpr base::TimeDelta kRequestTimeout = base::TimeDelta::FromMinutes(5); ...@@ -64,6 +65,13 @@ constexpr base::TimeDelta kRequestTimeout = base::TimeDelta::FromMinutes(5);
const base::FeatureParam<int> kUpdateDelayParam{ const base::FeatureParam<int> kUpdateDelayParam{
&blink::features::kServiceWorkerUpdateDelay, "update_delay_in_ms", 1000}; &blink::features::kServiceWorkerUpdateDelay, "update_delay_in_ms", 1000};
// The default value is set to max since it's not used when the feature is
// disabled. In that case, the service worker will be terminated by the idle
// timeout.
const base::FeatureParam<int> kTerminationDelayParam{
&features::kServiceWorkerTerminationOnNoControllee,
"termination_delay_in_ms", std::numeric_limits<int>::max()};
const char kClaimClientsStateErrorMesage[] = const char kClaimClientsStateErrorMesage[] =
"Only the active worker can claim clients."; "Only the active worker can claim clients.";
...@@ -723,6 +731,15 @@ void ServiceWorkerVersion::AddControllee( ...@@ -723,6 +731,15 @@ void ServiceWorkerVersion::AddControllee(
// invalid controller status. // invalid controller status.
CHECK(status_ == ACTIVATING || status_ == ACTIVATED); CHECK(status_ == ACTIVATING || status_ == ACTIVATED);
if (base::FeatureList::IsEnabled(
features::kServiceWorkerTerminationOnNoControllee) &&
!stop_on_no_controllee_callback_.IsCancelled()) {
DCHECK(!HasControllee());
// Cancel to stop the worker since the worker is going to have a controllee.
stop_on_no_controllee_callback_.Cancel();
}
controllee_map_[uuid] = container_host; controllee_map_[uuid] = container_host;
embedded_worker_->UpdateForegroundPriority(); embedded_worker_->UpdateForegroundPriority();
ClearTick(&no_controllees_time_); ClearTick(&no_controllees_time_);
...@@ -750,12 +767,33 @@ void ServiceWorkerVersion::RemoveControllee(const std::string& client_uuid) { ...@@ -750,12 +767,33 @@ void ServiceWorkerVersion::RemoveControllee(const std::string& client_uuid) {
controllee_map_.erase(client_uuid); controllee_map_.erase(client_uuid);
embedded_worker_->UpdateForegroundPriority(); embedded_worker_->UpdateForegroundPriority();
// Notify observers asynchronously since this gets called during // Notify observers asynchronously since this gets called during
// ServiceWorkerProviderHost's destructor, and we don't want observers to do // ServiceWorkerProviderHost's destructor, and we don't want observers to do
// work during that. // work during that.
base::ThreadTaskRunnerHandle::Get()->PostTask( base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&ServiceWorkerVersion::NotifyControlleeRemoved, FROM_HERE, base::BindOnce(&ServiceWorkerVersion::NotifyControlleeRemoved,
weak_factory_.GetWeakPtr(), client_uuid)); weak_factory_.GetWeakPtr(), client_uuid));
if (base::FeatureList::IsEnabled(
features::kServiceWorkerTerminationOnNoControllee) &&
!HasControllee()) {
// Terminate the worker |delay_ms| ms after all controllees are gone.
auto delay_ms =
base::TimeDelta::FromMilliseconds(kTerminationDelayParam.Get());
stop_on_no_controllee_callback_.Reset(base::BindOnce(
[](base::WeakPtr<ServiceWorkerVersion> version) {
if (!version)
return;
// The worker should not have controllee because the callback is
// cancelled when a new controllee appears.
DCHECK(!version->HasControllee());
version->StopWorker(base::DoNothing());
},
weak_factory_.GetWeakPtr()));
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, stop_on_no_controllee_callback_.callback(), delay_ms);
}
} }
void ServiceWorkerVersion::MoveControlleeToBackForwardCacheMap( void ServiceWorkerVersion::MoveControlleeToBackForwardCacheMap(
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
#include <vector> #include <vector>
#include "base/callback.h" #include "base/callback.h"
#include "base/cancelable_callback.h"
#include "base/containers/id_map.h" #include "base/containers/id_map.h"
#include "base/debug/stack_trace.h" #include "base/debug/stack_trace.h"
#include "base/gtest_prod_util.h" #include "base/gtest_prod_util.h"
...@@ -1051,6 +1052,12 @@ class CONTENT_EXPORT ServiceWorkerVersion ...@@ -1051,6 +1052,12 @@ class CONTENT_EXPORT ServiceWorkerVersion
// This is set when this service worker becomes redundant. // This is set when this service worker becomes redundant.
base::debug::StackTrace redundant_state_callstack_; base::debug::StackTrace redundant_state_callstack_;
// Callback to stop service worker small seconds after all controllees are
// gone. This callback can be canceled when the service worker starts to
// control another client and we know the worker needs to be used more.
// Used only when ServiceWorkerTerminationOnNoControllee is on.
base::CancelableOnceClosure stop_on_no_controllee_callback_;
base::WeakPtrFactory<ServiceWorkerVersion> weak_factory_{this}; base::WeakPtrFactory<ServiceWorkerVersion> weak_factory_{this};
DISALLOW_COPY_AND_ASSIGN(ServiceWorkerVersion); DISALLOW_COPY_AND_ASSIGN(ServiceWorkerVersion);
......
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
#include "content/browser/service_worker/service_worker_registration.h" #include "content/browser/service_worker/service_worker_registration.h"
#include "content/browser/service_worker/service_worker_test_utils.h" #include "content/browser/service_worker/service_worker_test_utils.h"
#include "content/common/service_worker/service_worker_utils.h" #include "content/common/service_worker/service_worker_utils.h"
#include "content/public/common/content_features.h"
#include "content/public/test/browser_task_environment.h" #include "content/public/test/browser_task_environment.h"
#include "content/public/test/mock_render_process_host.h" #include "content/public/test/mock_render_process_host.h"
#include "content/public/test/test_service.mojom.h" #include "content/public/test/test_service.mojom.h"
...@@ -81,7 +82,8 @@ class ServiceWorkerVersionTest : public testing::Test { ...@@ -81,7 +82,8 @@ class ServiceWorkerVersionTest : public testing::Test {
}; };
ServiceWorkerVersionTest() ServiceWorkerVersionTest()
: task_environment_(BrowserTaskEnvironment::IO_MAINLOOP) {} : task_environment_(BrowserTaskEnvironment::IO_MAINLOOP,
base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
void SetUp() override { void SetUp() override {
helper_ = GetHelper(); helper_ = GetHelper();
...@@ -1440,5 +1442,145 @@ TEST_F(ServiceWorkerVersionTest, AddMessageToConsole) { ...@@ -1440,5 +1442,145 @@ TEST_F(ServiceWorkerVersionTest, AddMessageToConsole) {
EXPECT_EQ(test_message, service_worker->console_messages()[0]); EXPECT_EQ(test_message, service_worker->console_messages()[0]);
} }
class ServiceWorkerVersionTerminationOnNoControlleeTest
: public ServiceWorkerVersionTest,
public testing::WithParamInterface<bool> {
public:
ServiceWorkerVersionTerminationOnNoControlleeTest() {
if (IsTerminationEnabled()) {
// The value should be the same with |kTerminationDelay|.
feature_list_.InitAndEnableFeatureWithParameters(
features::kServiceWorkerTerminationOnNoControllee,
{{"termination_delay_in_ms", "5000"}});
} else {
feature_list_.InitAndDisableFeature(
features::kServiceWorkerTerminationOnNoControllee);
}
}
ServiceWorkerContainerHost* CreateControllee() {
remote_endpoints_.emplace_back();
base::WeakPtr<ServiceWorkerContainerHost> container_host =
CreateContainerHostForWindow(
33 /* dummy render process id */, true /* is_parent_frame_secure */,
helper_->context()->AsWeakPtr(), &remote_endpoints_.back());
return container_host.get();
}
static bool IsTerminationEnabled() { return GetParam(); }
protected:
static const base::TimeDelta kTerminationDelay;
private:
base::test::ScopedFeatureList feature_list_;
std::vector<ServiceWorkerRemoteProviderEndpoint> remote_endpoints_;
};
// static
// The value should be the same with the number set in the constructor.
const base::TimeDelta
ServiceWorkerVersionTerminationOnNoControlleeTest::kTerminationDelay =
base::TimeDelta::FromMilliseconds(5000);
INSTANTIATE_TEST_SUITE_P(All,
ServiceWorkerVersionTerminationOnNoControlleeTest,
testing::Bool());
// Confirm if a service worker can be terminated when all controllees are gone
// and a certain period of time which is set by a flag passes.
TEST_P(ServiceWorkerVersionTerminationOnNoControlleeTest, NoControllee) {
version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk,
StartServiceWorker(version_.get()));
EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, version_->running_status());
// Service worker should be running after the time passes for more than the
// termination delay.
ServiceWorkerContainerHost* controllee = CreateControllee();
version_->AddControllee(controllee);
task_environment_.FastForwardBy(kTerminationDelay * 2);
EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, version_->running_status());
// Service worker will be terminated when |kTerminationDelay| passes after
// all controllees are gone.
version_->RemoveControllee(controllee->client_uuid());
task_environment_.FastForwardBy(kTerminationDelay);
if (IsTerminationEnabled()) {
EXPECT_EQ(EmbeddedWorkerStatus::STOPPED, version_->running_status());
} else {
EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, version_->running_status());
}
}
// Confirm if a service worker won't be terminated until all controllees are
// gone and a certain period of time which is set by a flag passes.
TEST_P(ServiceWorkerVersionTerminationOnNoControlleeTest,
NotTerminatedUntilAllControlleeAreGone) {
version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk,
StartServiceWorker(version_.get()));
EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, version_->running_status());
ServiceWorkerContainerHost* controllee1 = CreateControllee();
ServiceWorkerContainerHost* controllee2 = CreateControllee();
version_->AddControllee(controllee1);
version_->AddControllee(controllee2);
// Service worker won't be terminated until all controllees are gone.
version_->RemoveControllee(controllee1->client_uuid());
task_environment_.FastForwardBy(kTerminationDelay);
EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, version_->running_status());
// Service worker will be terminated when |kTerminationDelay| passes after
// all controllees are gone.
version_->RemoveControllee(controllee2->client_uuid());
task_environment_.FastForwardBy(kTerminationDelay);
if (IsTerminationEnabled()) {
EXPECT_EQ(EmbeddedWorkerStatus::STOPPED, version_->running_status());
} else {
EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, version_->running_status());
}
}
// Confirm the timeout is extended when a new controllee is added to the
// ServiceWorkerVersion before it's terminated.
TEST_P(ServiceWorkerVersionTerminationOnNoControlleeTest,
AddControlleeBeforeTermination) {
version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk,
StartServiceWorker(version_.get()));
EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, version_->running_status());
{
// Service worker won't be terminated until |kTerminationDelay| passes
// after all controllees are gone.
ServiceWorkerContainerHost* controllee = CreateControllee();
version_->AddControllee(controllee);
version_->RemoveControllee(controllee->client_uuid());
task_environment_.FastForwardBy(kTerminationDelay / 2);
EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, version_->running_status());
}
{
// Service worker won't be terminated if a new controllee is added before
// |kTerminationDelay| passes since the last controllee is removed.
ServiceWorkerContainerHost* controllee = CreateControllee();
version_->AddControllee(controllee);
task_environment_.FastForwardBy(kTerminationDelay);
EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, version_->running_status());
// Service worker will be terminated when |kTerminationDelay| passes
// after all controllees are gone.
version_->RemoveControllee(controllee->client_uuid());
task_environment_.FastForwardBy(kTerminationDelay);
if (IsTerminationEnabled()) {
EXPECT_EQ(EmbeddedWorkerStatus::STOPPED, version_->running_status());
} else {
EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, version_->running_status());
}
}
}
} // namespace service_worker_version_unittest } // namespace service_worker_version_unittest
} // namespace content } // namespace content
...@@ -487,6 +487,12 @@ const base::Feature kServiceWorkerPaymentApps{"ServiceWorkerPaymentApps", ...@@ -487,6 +487,12 @@ const base::Feature kServiceWorkerPaymentApps{"ServiceWorkerPaymentApps",
const base::Feature kServiceWorkerPrefersUnusedProcess{ const base::Feature kServiceWorkerPrefersUnusedProcess{
"ServiceWorkerPrefersUnusedProcess", base::FEATURE_DISABLED_BY_DEFAULT}; "ServiceWorkerPrefersUnusedProcess", base::FEATURE_DISABLED_BY_DEFAULT};
// Use this feature to experiment terminating a service worker when it doesn't
// control any clients: https://crbug.com/1043845.
const base::Feature kServiceWorkerTerminationOnNoControllee{
"ServiceWorkerTerminationOnNoControllee",
base::FEATURE_DISABLED_BY_DEFAULT};
// http://tc39.github.io/ecmascript_sharedmem/shmem.html // http://tc39.github.io/ecmascript_sharedmem/shmem.html
const base::Feature kSharedArrayBuffer { const base::Feature kSharedArrayBuffer {
"SharedArrayBuffer", "SharedArrayBuffer",
......
...@@ -108,6 +108,8 @@ CONTENT_EXPORT extern const base::Feature ...@@ -108,6 +108,8 @@ CONTENT_EXPORT extern const base::Feature
CONTENT_EXPORT extern const base::Feature kServiceWorkerOnUI; CONTENT_EXPORT extern const base::Feature kServiceWorkerOnUI;
CONTENT_EXPORT extern const base::Feature kServiceWorkerPaymentApps; CONTENT_EXPORT extern const base::Feature kServiceWorkerPaymentApps;
CONTENT_EXPORT extern const base::Feature kServiceWorkerPrefersUnusedProcess; CONTENT_EXPORT extern const base::Feature kServiceWorkerPrefersUnusedProcess;
CONTENT_EXPORT extern const base::Feature
kServiceWorkerTerminationOnNoControllee;
CONTENT_EXPORT extern const base::Feature kSharedArrayBuffer; CONTENT_EXPORT extern const base::Feature kSharedArrayBuffer;
CONTENT_EXPORT extern const base::Feature CONTENT_EXPORT extern const base::Feature
kSignedExchangePrefetchCacheForNavigations; kSignedExchangePrefetchCacheForNavigations;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment