Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(758)

Unified Diff: third_party/WebKit/Source/modules/webaudio/BaseAudioContext.cpp

Issue 2913303002: Avoid unsafe heap access from audio thread. (Closed)
Patch Set: improve method documentation Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: third_party/WebKit/Source/modules/webaudio/BaseAudioContext.cpp
diff --git a/third_party/WebKit/Source/modules/webaudio/BaseAudioContext.cpp b/third_party/WebKit/Source/modules/webaudio/BaseAudioContext.cpp
index 6bdb579eca2a0cc64a87bfb3f1e8d371cfcb76f3..69b31328a0c7c67d20094956baf00bf5d6894250 100644
--- a/third_party/WebKit/Source/modules/webaudio/BaseAudioContext.cpp
+++ b/third_party/WebKit/Source/modules/webaudio/BaseAudioContext.cpp
@@ -95,6 +95,7 @@ BaseAudioContext::BaseAudioContext(Document* document)
destination_node_(nullptr),
is_cleared_(false),
is_resolving_resume_promises_(false),
+ has_posted_cleanup_task_(false),
user_gesture_required_(false),
connection_count_(0),
deferred_task_handler_(DeferredTaskHandler::Create()),
@@ -149,7 +150,6 @@ BaseAudioContext::~BaseAudioContext() {
// be in the destructor if there are still AudioNodes around.
DCHECK(!IsDestinationInitialized());
DCHECK(!active_source_nodes_.size());
- DCHECK(!finished_source_handlers_.size());
DCHECK(!is_resolving_resume_promises_);
DCHECK(!resume_resolvers_.size());
DCHECK(!autoplay_status_.has_value());
@@ -700,34 +700,10 @@ void BaseAudioContext::NotifyStateChange() {
void BaseAudioContext::NotifySourceNodeFinishedProcessing(
AudioHandler* handler) {
DCHECK(IsAudioThread());
+ MutexLocker lock(finished_source_handlers_mutex_);
finished_source_handlers_.push_back(handler);
}
-void BaseAudioContext::RemoveFinishedSourceNodes(bool needs_removal) {
- DCHECK(IsAudioThread());
-
- if (needs_removal) {
- Platform::Current()->MainThread()->GetWebTaskRunner()->PostTask(
- BLINK_FROM_HERE,
- CrossThreadBind(
- &BaseAudioContext::RemoveFinishedSourceNodesOnMainThread,
- WrapCrossThreadPersistent(this)));
- }
-}
-
-void BaseAudioContext::RemoveFinishedSourceNodesOnMainThread() {
- DCHECK(IsMainThread());
- AutoLocker locker(this);
- // Quadratic worst case, but sizes of both vectors are considered
- // manageable, especially |m_finishedSourceNodes| is likely to be short.
- for (AudioNode* node : finished_source_nodes_) {
- size_t i = active_source_nodes_.Find(node);
- if (i != kNotFound)
- active_source_nodes_.erase(i);
- }
- finished_source_nodes_.clear();
-}
-
Document* BaseAudioContext::GetDocument() const {
return ToDocument(GetExecutionContext());
}
@@ -754,26 +730,6 @@ bool BaseAudioContext::AreAutoplayRequirementsFulfilled() const {
return false;
}
-bool BaseAudioContext::ReleaseFinishedSourceNodes() {
- DCHECK(IsGraphOwner());
- DCHECK(IsAudioThread());
- bool did_remove = false;
- for (AudioHandler* handler : finished_source_handlers_) {
- for (AudioNode* node : active_source_nodes_) {
- if (finished_source_nodes_.Contains(node))
- continue;
- if (handler == &node->Handler()) {
- handler->BreakConnection();
- finished_source_nodes_.insert(node);
- did_remove = true;
- break;
- }
- }
- }
- finished_source_handlers_.clear();
- return did_remove;
-}
-
void BaseAudioContext::NotifySourceNodeStartedProcessing(AudioNode* node) {
DCHECK(IsMainThread());
AutoLocker locker(this);
@@ -791,23 +747,11 @@ void BaseAudioContext::ReleaseActiveSourceNodes() {
}
void BaseAudioContext::HandleStoppableSourceNodes() {
+ DCHECK(IsAudioThread());
DCHECK(IsGraphOwner());
- // Find AudioBufferSourceNodes to see if we can stop playing them.
- for (AudioNode* node : active_source_nodes_) {
- // If the AudioNode has been marked as finished and released by
- // the audio thread, but not yet removed by the main thread
- // (see releaseActiveSourceNodes() above), |node| must not be
- // touched as its handler may have been released already.
- if (finished_source_nodes_.Contains(node))
- continue;
- if (node->Handler().GetNodeType() ==
- AudioHandler::kNodeTypeAudioBufferSource) {
- AudioBufferSourceNode* source_node =
- static_cast<AudioBufferSourceNode*>(node);
- source_node->GetAudioBufferSourceHandler().HandleStoppableSourceNode();
- }
- }
+ if (active_source_nodes_.size())
+ ScheduleMainThreadCleanup();
}
void BaseAudioContext::HandlePreRenderTasks(
@@ -844,39 +788,88 @@ void BaseAudioContext::HandlePostRenderTasks() {
// is that there will be some nodes which will take slightly longer than usual
// to be deleted or removed from the render graph (in which case they'll
// render silence).
- bool did_remove = false;
if (TryLock()) {
// Take care of AudioNode tasks where the tryLock() failed previously.
GetDeferredTaskHandler().BreakConnections();
- // Dynamically clean up nodes which are no longer needed.
- did_remove = ReleaseFinishedSourceNodes();
-
GetDeferredTaskHandler().HandleDeferredTasks();
GetDeferredTaskHandler().RequestToDeleteHandlersOnMainThread();
unlock();
}
-
- RemoveFinishedSourceNodes(did_remove);
}
-void BaseAudioContext::ResolvePromisesForResumeOnMainThread() {
+void BaseAudioContext::PerformCleanupOnMainThread() {
DCHECK(IsMainThread());
AutoLocker locker(this);
- for (auto& resolver : resume_resolvers_) {
- if (context_state_ == kClosed) {
- resolver->Reject(DOMException::Create(
- kInvalidStateError, "Cannot resume a context that has been closed"));
- } else {
- SetContextState(kRunning);
- resolver->Resolve();
+ if (is_resolving_resume_promises_) {
+ for (auto& resolver : resume_resolvers_) {
+ if (context_state_ == kClosed) {
+ resolver->Reject(DOMException::Create(
+ kInvalidStateError,
+ "Cannot resume a context that has been closed"));
+ } else {
+ SetContextState(kRunning);
+ resolver->Resolve();
+ }
}
+ resume_resolvers_.clear();
+ is_resolving_resume_promises_ = false;
}
- resume_resolvers_.clear();
- is_resolving_resume_promises_ = false;
+ if (active_source_nodes_.size()) {
+ // Find AudioBufferSourceNodes to see if we can stop playing them.
+ for (AudioNode* node : active_source_nodes_) {
+ if (node->Handler().GetNodeType() ==
+ AudioHandler::kNodeTypeAudioBufferSource) {
+ AudioBufferSourceNode* source_node =
+ static_cast<AudioBufferSourceNode*>(node);
+ source_node->GetAudioBufferSourceHandler().HandleStoppableSourceNode();
+ }
+ }
+
+ Vector<AudioHandler*> finished_handlers;
+ {
+ MutexLocker lock(finished_source_handlers_mutex_);
+ finished_source_handlers_.swap(finished_handlers);
+ }
+ // Break the connection and release active nodes that have finished
+ // playing.
+ unsigned remove_count = 0;
+ Vector<bool> removables;
+ removables.resize(active_source_nodes_.size());
+ for (AudioHandler* handler : finished_handlers) {
+ for (unsigned i = 0; i < active_source_nodes_.size(); ++i) {
+ if (handler == &active_source_nodes_[i]->Handler()) {
+ handler->BreakConnection();
+ removables[i] = true;
+ remove_count++;
+ break;
+ }
+ }
+ }
+
+ // Copy over the surviving active nodes.
+ HeapVector<Member<AudioNode>> actives;
+ actives.ReserveInitialCapacity(active_source_nodes_.size() - remove_count);
+ for (unsigned i = 0; i < removables.size(); ++i) {
+ if (!removables[i])
+ actives.push_back(active_source_nodes_[i]);
+ }
+ active_source_nodes_.swap(actives);
+ }
+ has_posted_cleanup_task_ = false;
+}
+
+void BaseAudioContext::ScheduleMainThreadCleanup() {
+ if (has_posted_cleanup_task_)
+ return;
+ Platform::Current()->MainThread()->GetWebTaskRunner()->PostTask(
+ BLINK_FROM_HERE,
+ CrossThreadBind(&BaseAudioContext::PerformCleanupOnMainThread,
+ WrapCrossThreadPersistent(this)));
+ has_posted_cleanup_task_ = true;
}
void BaseAudioContext::ResolvePromisesForResume() {
@@ -890,10 +883,7 @@ void BaseAudioContext::ResolvePromisesForResume() {
// often and it takes some time to resolve the promises in the main thread.
if (!is_resolving_resume_promises_ && resume_resolvers_.size() > 0) {
is_resolving_resume_promises_ = true;
- Platform::Current()->MainThread()->GetWebTaskRunner()->PostTask(
- BLINK_FROM_HERE,
- CrossThreadBind(&BaseAudioContext::ResolvePromisesForResumeOnMainThread,
- WrapCrossThreadPersistent(this)));
+ ScheduleMainThreadCleanup();
}
}

Powered by Google App Engine
This is Rietveld 408576698