avoid-double-destruction-of-ServiceWorkerObjectHost.patch 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. From bd59ce32629ef684624821419c43967b73d2989e Mon Sep 17 00:00:00 2001
  2. From: Hiroki Nakagawa <nhiroki@chromium.org>
  3. Date: Fri, 8 May 2020 08:25:31 +0000
  4. Subject: [PATCH] ServiceWorker: Avoid double destruction of
  5. ServiceWorkerObjectHost on connection error
  6. This CL avoids the case where ServiceWorkerObjectHost is destroyed twice
  7. on ServiceWorkerObjectHost::OnConnectionError() when Chromium is built
  8. with the GCC build toolchain.
  9. > How does the issue happen?
  10. ServiceWorkerObjectHost has a cyclic reference like this:
  11. ServiceWorkerObjectHost
  12. --([1] scoped_refptr)--> ServiceWorkerVersion
  13. --([2] std::unique_ptr)--> ServiceWorkerProviderHost
  14. --([3] std::unique_ptr)--> ServiceWorkerContainerHost
  15. --([4] std::unique_ptr)--> ServiceWorkerObjectHost
  16. Note that ServiceWorkerContainerHost manages ServiceWorkerObjectHost in
  17. map<int64_t version_id, std::unique_ptr<ServiceWorkerObjectHost>>.
  18. When ServiceWorkerObjectHost::OnConnectionError() is called, the
  19. function removes the reference [4] from the map, and destroys
  20. ServiceWorkerObjectHost. If the object host has the last reference [1]
  21. to ServiceWorkerVersion, the destruction also cuts off the references
  22. [2] and [3], and destroys ServiceWorkerProviderHost and
  23. ServiceWorkerContainerHost.
  24. This seems to work well on the Chromium's default toolchain, but not
  25. work on the GCC toolchain. According to the report, destruction of
  26. ServiceWorkerContainerHost happens while the map owned by the container
  27. host is erasing the ServiceWorkerObjectHost, and this results in crash
  28. due to double destruction of the object host.
  29. I don't know the reason why this happens only on the GCC toolchain, but
  30. I suspect the order of object destruction on std::map::erase() could be
  31. different depending on the toolchains.
  32. > How does this CL fix this?
  33. The ideal fix is to redesign the ownership model of
  34. ServiceWorkerVersion, but it's not feasible in the short term.
  35. Instead, this CL avoids destruction of ServiceWorkerObjectHost on
  36. std::map::erase(). The new code takes the ownership of the object host
  37. from the map first, and then erases the entry from the map. This
  38. separates timings to erase the map entry and to destroy the object host,
  39. so the crash should no longer happen.
  40. Bug: 1056598
  41. Change-Id: Id30654cb575bc557c42044d6f0c6f1f9bfaed613
  42. Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2094496
  43. Reviewed-by: Makoto Shimazu <shimazu@chromium.org>
  44. Commit-Queue: Hiroki Nakagawa <nhiroki@chromium.org>
  45. Cr-Commit-Position: refs/heads/master@{#766770}
  46. ---
  47. .../service_worker_container_host.cc | 10 +++++
  48. .../service_worker_object_host_unittest.cc | 38 +++++++++++++++++++
  49. 2 files changed, 48 insertions(+)
  50. diff --git a/content/browser/service_worker/service_worker_container_host.cc b/content/browser/service_worker/service_worker_container_host.cc
  51. index ec7fb1449af..98c62093b0e 100644
  52. --- a/content/browser/service_worker/service_worker_container_host.cc
  53. +++ b/content/browser/service_worker/service_worker_container_host.cc
  54. @@ -669,6 +669,16 @@ void ServiceWorkerContainerHost::RemoveServiceWorkerObjectHost(
  55. int64_t version_id) {
  56. DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
  57. DCHECK(base::Contains(service_worker_object_hosts_, version_id));
  58. +
  59. + // ServiceWorkerObjectHost to be deleted may have the last reference to
  60. + // ServiceWorkerVersion that indirectly owns this ServiceWorkerContainerHost.
  61. + // If we erase the object host directly from the map, |this| could be deleted
  62. + // during the map operation and may crash. To avoid the case, we take the
  63. + // ownership of the object host from the map first, and then erase the entry
  64. + // from the map. See https://crbug.com/1056598 for details.
  65. + std::unique_ptr<ServiceWorkerObjectHost> to_be_deleted =
  66. + std::move(service_worker_object_hosts_[version_id]);
  67. + DCHECK(to_be_deleted);
  68. service_worker_object_hosts_.erase(version_id);
  69. }
  70. diff --git a/content/browser/service_worker/service_worker_object_host_unittest.cc b/content/browser/service_worker/service_worker_object_host_unittest.cc
  71. index 408d7c1f9d1..6eab59040ab 100644
  72. --- a/content/browser/service_worker/service_worker_object_host_unittest.cc
  73. +++ b/content/browser/service_worker/service_worker_object_host_unittest.cc
  74. @@ -200,6 +200,19 @@ class ServiceWorkerObjectHostTest : public testing::Test {
  75. return registration_info;
  76. }
  77. + void CallOnConnectionError(ServiceWorkerContainerHost* container_host,
  78. + int64_t version_id) {
  79. + // ServiceWorkerObjectHost has the last reference to the version.
  80. + ServiceWorkerObjectHost* object_host =
  81. + GetServiceWorkerObjectHost(container_host, version_id);
  82. + EXPECT_TRUE(object_host->version_->HasOneRef());
  83. +
  84. + // Make sure that OnConnectionError induces destruction of the version and
  85. + // the object host.
  86. + object_host->receivers_.Clear();
  87. + object_host->OnConnectionError();
  88. + }
  89. +
  90. BrowserTaskEnvironment task_environment_;
  91. std::unique_ptr<EmbeddedWorkerTestHelper> helper_;
  92. scoped_refptr<ServiceWorkerRegistration> registration_;
  93. @@ -409,5 +422,30 @@ TEST_F(ServiceWorkerObjectHostTest, DispatchExtendableMessageEvent_FromClient) {
  94. events[0]->source_info_for_client->client_type);
  95. }
  96. +// This is a regression test for https://crbug.com/1056598.
  97. +TEST_F(ServiceWorkerObjectHostTest, OnConnectionError) {
  98. + const GURL scope("https://www.example.com/");
  99. + const GURL script_url("https://www.example.com/service_worker.js");
  100. + Initialize(std::make_unique<EmbeddedWorkerTestHelper>(base::FilePath()));
  101. + SetUpRegistration(scope, script_url);
  102. +
  103. + // Create the provider host.
  104. + ASSERT_EQ(blink::ServiceWorkerStatusCode::kOk,
  105. + StartServiceWorker(version_.get()));
  106. +
  107. + // Set up the case where the last reference to the version is owned by the
  108. + // service worker object host.
  109. + ServiceWorkerContainerHost* container_host =
  110. + version_->provider_host()->container_host();
  111. + ServiceWorkerVersion* version_rawptr = version_.get();
  112. + version_ = nullptr;
  113. + ASSERT_TRUE(version_rawptr->HasOneRef());
  114. +
  115. + // Simulate the connection error that induces the object host destruction.
  116. + // This shouldn't crash.
  117. + CallOnConnectionError(container_host, version_rawptr->version_id());
  118. + base::RunLoop().RunUntilIdle();
  119. +}
  120. +
  121. } // namespace service_worker_object_host_unittest
  122. } // namespace content