Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add BlinkSchedulerSingleThreadTaskRunner and use in blink
DeleteSoon/ReleaseSoon leak memory if the underlying task queue is shut down at the time of posting or gets shut down before running the deleter task. This is problematic in Blink because frame- and worker-associated task queues might be shut down well before the process is shut down. This CL adds BlinkSchedulerSingleThreadTaskRunner to fix some of these memory leaks by specializing the behavior of DeleteSoon/ReleaseSoon. There are two main cases to consider: 1. The deleter task scheduled by DeleteSoon/ReleaseSoon gets posted. In this case, the new task runner deletes the object on task destruction. This should be thread-safe because we shut down task queues on the associated thread and sequence-safe since the targeted task runner won't run any other tasks. 2. The deleter task doesn't get posted. In this case we try a backup task runner -- a thread scheduler-associated queue associated with the same thread which has a potentially longer (but not shorter) lifetime. This is sequence-safe since and thread-safe for the same reasons as (1). If this fails, the thread scheduler has already been shut down or is shutting down. In this case, we delete the object synchronously if it's the same-thread, but leak it in the cross-thread case since it might trigger thread checks or violate assumptions (e.g. TLS). Note: this case was not triggered on the bots, but might happen in practice. We can add a DumpWithoutCrashing as a follow-up to see if this happens in practice and try to fix those places. Design doc: https://docs.google.com/document/d/17RyF69EwmDVPCxdQYBWCv_A4aPBdj08BBvG8UsQeKwQ/edit?usp=sharing Bug: 1376851 Change-Id: Ie2964bcec9dbf8c240312973d0c3c5b8f5e9e908 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4049162 Commit-Queue: Scott Haseley <shaseley@chromium.org> Reviewed-by: Francois Pierre Doray <fdoray@chromium.org> Reviewed-by: Nate Chapin <japhet@chromium.org> Cr-Commit-Position: refs/heads/main@{#1085171}
- Loading branch information
Showing
17 changed files
with
814 additions
and
24 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
122 changes: 122 additions & 0 deletions
122
...rty/blink/renderer/platform/scheduler/common/blink_scheduler_single_thread_task_runner.cc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
// Copyright 2022 The Chromium Authors | ||
// 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/platform/scheduler/common/blink_scheduler_single_thread_task_runner.h" | ||
|
||
#include <memory> | ||
#include <utility> | ||
|
||
#include "base/location.h" | ||
#include "base/memory/scoped_refptr.h" | ||
#include "base/task/single_thread_task_runner.h" | ||
|
||
namespace blink::scheduler { | ||
|
||
namespace { | ||
|
||
void DeleteOrReleaseSoonImpl( | ||
const base::Location& from_here, | ||
void (*deleter)(const void*), | ||
const void* object, | ||
scoped_refptr<base::SingleThreadTaskRunner> preferred_task_runner, | ||
scoped_refptr<base::SingleThreadTaskRunner> fallback_task_runner); | ||
|
||
class DeleteHelper { | ||
public: | ||
DeleteHelper( | ||
const base::Location& from_here, | ||
void (*deleter)(const void*), | ||
const void* object, | ||
scoped_refptr<base::SingleThreadTaskRunner> preferred_task_runner, | ||
scoped_refptr<base::SingleThreadTaskRunner> fallback_task_runner) | ||
: from_here_(from_here), | ||
deleter_(deleter), | ||
object_(object), | ||
preferred_task_runner_(std::move(preferred_task_runner)), | ||
fallback_task_runner_(std::move(fallback_task_runner)) {} | ||
|
||
void Delete() { | ||
deleter_(object_); | ||
object_ = nullptr; | ||
} | ||
|
||
~DeleteHelper() { | ||
if (!object_) { | ||
return; | ||
} | ||
|
||
// The deleter task is being destroyed without running, which happens if the | ||
// task queue is shut down after queuing the task queued or if posting it | ||
// failed. It's safe to run the deleter in the former case, but since these | ||
// cases can't be differentiated without synchronization or API changes, use | ||
// the `fallback_task_runner_` if present and delete synchronously if not. | ||
if (fallback_task_runner_) { | ||
DeleteOrReleaseSoonImpl(from_here_, deleter_, object_, | ||
fallback_task_runner_, nullptr); | ||
} else if (preferred_task_runner_->BelongsToCurrentThread()) { | ||
// Note: `deleter_` will run synchronously in [Delete|Release]Soon() if | ||
// the deleter task failed to post to the original preferred and fallback | ||
// task runners. This happens when the APIs are called during thread | ||
// shutdown, and should only occur if invoking those APIs in object | ||
// destructors (on task destruction), where it should be safe to | ||
// synchronously delete. | ||
Delete(); | ||
} else { | ||
// The deleter task couldn't be posted to the intended thread, so the only | ||
// safe thing to do is leak the object. | ||
// TODO(crbug.com/1376851): Add a CHECK, DumpWithoutCrashing, or trace | ||
// event to determine if leaks still occur. | ||
} | ||
} | ||
|
||
private: | ||
base::Location from_here_; | ||
void (*deleter_)(const void*) = nullptr; | ||
const void* object_ = nullptr; | ||
scoped_refptr<base::SingleThreadTaskRunner> preferred_task_runner_; | ||
scoped_refptr<base::SingleThreadTaskRunner> fallback_task_runner_; | ||
}; | ||
|
||
void DeleteOrReleaseSoonImpl( | ||
const base::Location& from_here, | ||
void (*deleter)(const void*), | ||
const void* object, | ||
scoped_refptr<base::SingleThreadTaskRunner> preferred_task_runner, | ||
scoped_refptr<base::SingleThreadTaskRunner> fallback_task_runner) { | ||
auto delete_helper = std::make_unique<DeleteHelper>( | ||
from_here, deleter, object, preferred_task_runner, fallback_task_runner); | ||
preferred_task_runner->PostNonNestableTask( | ||
from_here, | ||
base::BindOnce(&DeleteHelper::Delete, std::move(delete_helper))); | ||
} | ||
|
||
} // namespace | ||
|
||
BlinkSchedulerSingleThreadTaskRunner::BlinkSchedulerSingleThreadTaskRunner( | ||
scoped_refptr<base::SingleThreadTaskRunner> wrapped_task_runner, | ||
scoped_refptr<base::SingleThreadTaskRunner> thread_task_runner) | ||
: outer_(std::move(wrapped_task_runner)), | ||
thread_task_runner_(std::move(thread_task_runner)) { | ||
DCHECK(outer_); | ||
} | ||
|
||
BlinkSchedulerSingleThreadTaskRunner::~BlinkSchedulerSingleThreadTaskRunner() = | ||
default; | ||
|
||
bool BlinkSchedulerSingleThreadTaskRunner::DeleteOrReleaseSoonInternal( | ||
const base::Location& from_here, | ||
void (*deleter)(const void*), | ||
const void* object) { | ||
DCHECK(deleter); | ||
// `object` might be null, in which case there's nothing to do. | ||
if (!object) { | ||
return true; | ||
} | ||
|
||
DeleteOrReleaseSoonImpl(from_here, deleter, object, outer_, | ||
thread_task_runner_); | ||
return true; | ||
} | ||
|
||
} // namespace blink::scheduler |
112 changes: 112 additions & 0 deletions
112
...arty/blink/renderer/platform/scheduler/common/blink_scheduler_single_thread_task_runner.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
// Copyright 2022 The Chromium Authors | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCHEDULER_COMMON_BLINK_SCHEDULER_SINGLE_THREAD_TASK_RUNNER_H_ | ||
#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCHEDULER_COMMON_BLINK_SCHEDULER_SINGLE_THREAD_TASK_RUNNER_H_ | ||
|
||
#include "base/functional/callback_forward.h" | ||
#include "base/memory/scoped_refptr.h" | ||
#include "base/task/delayed_task_handle.h" | ||
#include "base/task/single_thread_task_runner.h" | ||
#include "base/time/time.h" | ||
#include "third_party/blink/renderer/platform/platform_export.h" | ||
|
||
namespace base { | ||
class Location; | ||
} // namespace base | ||
|
||
namespace blink::scheduler { | ||
|
||
// TaskRunner used in the scheduler. | ||
// | ||
// This class specializes the DeleteSoon/ReleaseSoon implementation to prevent | ||
// the object from leaking when possible (see DeleteOrReleaseSoonInternal). This | ||
// is needed in Blink since frame and worker schedulers can get torn down long | ||
// before the process shuts down. | ||
// | ||
// All other task-posting functionality is forwarded to to an underlying task | ||
// runner. | ||
class PLATFORM_EXPORT BlinkSchedulerSingleThreadTaskRunner | ||
: public base::SingleThreadTaskRunner { | ||
public: | ||
// `wrapped_task_runner` is the task runner used for scheduling tasks. | ||
// `thread_task_runner` is used to schedule deleter tasks if | ||
// `wrapped_task_runner`'s task queue is already shut down. | ||
BlinkSchedulerSingleThreadTaskRunner( | ||
scoped_refptr<base::SingleThreadTaskRunner> wrapped_task_runner, | ||
scoped_refptr<base::SingleThreadTaskRunner> thread_task_runner); | ||
~BlinkSchedulerSingleThreadTaskRunner() override; | ||
|
||
BlinkSchedulerSingleThreadTaskRunner(BlinkSchedulerSingleThreadTaskRunner&&) = | ||
delete; | ||
BlinkSchedulerSingleThreadTaskRunner& operator=( | ||
BlinkSchedulerSingleThreadTaskRunner&&) = delete; | ||
|
||
// base::TaskRunner overrides: | ||
bool PostDelayedTask(const base::Location& from_here, | ||
base::OnceClosure task, | ||
base::TimeDelta delay) override { | ||
return outer_->PostDelayedTask(from_here, std::move(task), delay); | ||
} | ||
|
||
// base::SequencedTaskRunner overrides: | ||
bool PostNonNestableDelayedTask(const base::Location& from_here, | ||
base::OnceClosure task, | ||
base::TimeDelta delay) override { | ||
return outer_->PostNonNestableDelayedTask(from_here, std::move(task), | ||
delay); | ||
} | ||
|
||
base::DelayedTaskHandle PostCancelableDelayedTask( | ||
base::subtle::PostDelayedTaskPassKey pass_key, | ||
const base::Location& from_here, | ||
base::OnceClosure task, | ||
base::TimeDelta delay) override { | ||
return outer_->PostCancelableDelayedTask(pass_key, from_here, | ||
std::move(task), delay); | ||
} | ||
|
||
[[nodiscard]] base::DelayedTaskHandle PostCancelableDelayedTaskAt( | ||
base::subtle::PostDelayedTaskPassKey pass_key, | ||
const base::Location& from_here, | ||
base::OnceClosure task, | ||
base::TimeTicks delayed_run_time, | ||
base::subtle::DelayPolicy delay_policy) override { | ||
return outer_->PostCancelableDelayedTaskAt( | ||
pass_key, from_here, std::move(task), delayed_run_time, delay_policy); | ||
} | ||
|
||
bool PostDelayedTaskAt(base::subtle::PostDelayedTaskPassKey pass_key, | ||
const base::Location& from_here, | ||
base::OnceClosure task, | ||
base::TimeTicks delayed_run_time, | ||
base::subtle::DelayPolicy delay_policy) override { | ||
return outer_->PostDelayedTaskAt(pass_key, from_here, std::move(task), | ||
delayed_run_time, delay_policy); | ||
} | ||
|
||
bool RunsTasksInCurrentSequence() const override { | ||
return outer_->RunsTasksInCurrentSequence(); | ||
} | ||
|
||
protected: | ||
// This always returns true, even if `object` gets leaked because the deleter | ||
// task was not posted. | ||
// TODO(crbug.com/1376851): Determine if leaking still occurs and whether to | ||
// CHECK or handle at callsites. | ||
bool DeleteOrReleaseSoonInternal(const base::Location& from_here, | ||
void (*deleter)(const void*), | ||
const void* object) override; | ||
|
||
private: | ||
// The task runner this object forwards all non-delete/release tasks to. | ||
scoped_refptr<base::SingleThreadTaskRunner> outer_; | ||
// Backup task runner used for delete/release tasks if `outer_`'s task queue | ||
// is already shut down when `DeleteOrReleaseSoonInternal()` is called. | ||
scoped_refptr<base::SingleThreadTaskRunner> thread_task_runner_; | ||
}; | ||
|
||
} // namespace blink::scheduler | ||
|
||
#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCHEDULER_COMMON_BLINK_SCHEDULER_SINGLE_THREAD_TASK_RUNNER_H_ |
Oops, something went wrong.