Changeset 270127 in webkit


Ignore:
Timestamp:
Nov 20, 2020 12:48:22 PM (3 years ago)
Author:
Chris Dumez
Message:

AudioWorkletProcessor::process() may get called on non-audio worklet thread and crash
https://bugs.webkit.org/show_bug.cgi?id=219209

Reviewed by Geoffrey Garen.

Source/WebCore:

If the AudioContext is already running when the AudioWorklet becomes ready, then the
AudioWorkletNode::process() was getting called on the CoreAudio rendering thread instead
of the AudioWorklet thread. This was causing us to run the AudioWorkletProcessor's JS
on the wrong thread, which would cause flaky crashes in release and assertion hits in
debug.

To address the issue, I updated AudioWorkletNode::process() to output silence when
it is called on another thread than the AudioWorklet thread. Also, if the AudioContext
is already running when the AudioWorklet becomes ready, we need to restart the
AudioDestination so that we switch the audio rendering from the CoreAudio rendering
thread to the AudioWorklet thread.

Test: http/wpt/webaudio/the-audio-api/the-audioworklet-interface/context-already-rendering.html

  • Modules/webaudio/AudioDestinationNode.h:

(WebCore::AudioDestinationNode::restartRendering):

  • Modules/webaudio/AudioWorkletNode.cpp:

(WebCore::AudioWorkletNode::isWorkletThread):
(WebCore::AudioWorkletNode::process):

  • Modules/webaudio/AudioWorkletNode.h:
  • Modules/webaudio/BaseAudioContext.cpp:

(WebCore::BaseAudioContext::addAudioParamDescriptors):
(WebCore::BaseAudioContext::workletIsReady):

  • Modules/webaudio/BaseAudioContext.h:
  • Modules/webaudio/DefaultAudioDestinationNode.cpp:

(WebCore::DefaultAudioDestinationNode::uninitialize):
(WebCore::DefaultAudioDestinationNode::startRendering):
(WebCore::DefaultAudioDestinationNode::resume):
(WebCore::DefaultAudioDestinationNode::suspend):
(WebCore::DefaultAudioDestinationNode::restartRendering):
(WebCore::DefaultAudioDestinationNode::setChannelCount):

  • Modules/webaudio/DefaultAudioDestinationNode.h:

LayoutTests:

Add layout test coverage.

  • http/wpt/webaudio/the-audio-api/the-audioworklet-interface/context-already-rendering-expected.txt: Added.
  • http/wpt/webaudio/the-audio-api/the-audioworklet-interface/context-already-rendering.html: Added.
  • http/wpt/webaudio/the-audio-api/the-audioworklet-interface/processors/basic-processor.js: Added.

(BasicProcessor.prototype.process):
(BasicProcessor):

Location:
trunk
Files:
3 added
9 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r270126 r270127  
     12020-11-20  Chris Dumez  <cdumez@apple.com>
     2
     3        AudioWorkletProcessor::process() may get called on non-audio worklet thread and crash
     4        https://bugs.webkit.org/show_bug.cgi?id=219209
     5
     6        Reviewed by Geoffrey Garen.
     7
     8        Add layout test coverage.
     9
     10        * http/wpt/webaudio/the-audio-api/the-audioworklet-interface/context-already-rendering-expected.txt: Added.
     11        * http/wpt/webaudio/the-audio-api/the-audioworklet-interface/context-already-rendering.html: Added.
     12        * http/wpt/webaudio/the-audio-api/the-audioworklet-interface/processors/basic-processor.js: Added.
     13        (BasicProcessor.prototype.process):
     14        (BasicProcessor):
     15
    1162020-11-20  Lauro Moura  <lmoura@igalia.com>
    217
  • trunk/Source/WebCore/ChangeLog

    r270126 r270127  
     12020-11-20  Chris Dumez  <cdumez@apple.com>
     2
     3        AudioWorkletProcessor::process() may get called on non-audio worklet thread and crash
     4        https://bugs.webkit.org/show_bug.cgi?id=219209
     5
     6        Reviewed by Geoffrey Garen.
     7
     8        If the AudioContext is already running when the AudioWorklet becomes ready, then the
     9        AudioWorkletNode::process() was getting called on the CoreAudio rendering thread instead
     10        of the AudioWorklet thread. This was causing us to run the AudioWorkletProcessor's JS
     11        on the wrong thread, which would cause flaky crashes in release and assertion hits in
     12        debug.
     13
     14        To address the issue, I updated AudioWorkletNode::process() to output silence when
     15        it is called on another thread than the AudioWorklet thread. Also, if the AudioContext
     16        is already running when the AudioWorklet becomes ready, we need to restart the
     17        AudioDestination so that we switch the audio rendering from the CoreAudio rendering
     18        thread to the AudioWorklet thread.
     19
     20        Test: http/wpt/webaudio/the-audio-api/the-audioworklet-interface/context-already-rendering.html
     21
     22        * Modules/webaudio/AudioDestinationNode.h:
     23        (WebCore::AudioDestinationNode::restartRendering):
     24        * Modules/webaudio/AudioWorkletNode.cpp:
     25        (WebCore::AudioWorkletNode::isWorkletThread):
     26        (WebCore::AudioWorkletNode::process):
     27        * Modules/webaudio/AudioWorkletNode.h:
     28        * Modules/webaudio/BaseAudioContext.cpp:
     29        (WebCore::BaseAudioContext::addAudioParamDescriptors):
     30        (WebCore::BaseAudioContext::workletIsReady):
     31        * Modules/webaudio/BaseAudioContext.h:
     32        * Modules/webaudio/DefaultAudioDestinationNode.cpp:
     33        (WebCore::DefaultAudioDestinationNode::uninitialize):
     34        (WebCore::DefaultAudioDestinationNode::startRendering):
     35        (WebCore::DefaultAudioDestinationNode::resume):
     36        (WebCore::DefaultAudioDestinationNode::suspend):
     37        (WebCore::DefaultAudioDestinationNode::restartRendering):
     38        (WebCore::DefaultAudioDestinationNode::setChannelCount):
     39        * Modules/webaudio/DefaultAudioDestinationNode.h:
     40
    1412020-11-20  Lauro Moura  <lmoura@igalia.com>
    242
  • trunk/Source/WebCore/Modules/webaudio/AudioDestinationNode.h

    r269475 r270127  
    6060    virtual void suspend(CompletionHandler<void(Optional<Exception>&&)>&& completionHandler) { completionHandler(WTF::nullopt); }
    6161    virtual void close(CompletionHandler<void()>&& completionHandler) { completionHandler(); }
     62    virtual void restartRendering() { }
    6263
    6364    virtual bool isPlaying() { return false; }
  • trunk/Source/WebCore/Modules/webaudio/AudioWorkletNode.cpp

    r268728 r270127  
    183183        auto locker = holdLock(m_processLock);
    184184        m_processor = WTFMove(processor);
     185        m_workletThread = &Thread::current();
    185186    } else
    186187        fireProcessorErrorOnMainThread(ProcessorError::ConstructorError);
     
    192193
    193194    auto locker = tryHoldLock(m_processLock);
    194     if (!locker || !m_processor) {
     195    if (!locker || !m_processor || &Thread::current() != m_workletThread) {
    195196        // We're not ready yet or we are getting destroyed. In this case, we output silence.
    196197        for (unsigned i = 0; i < numberOfOutputs(); ++i)
  • trunk/Source/WebCore/Modules/webaudio/AudioWorkletNode.h

    r268517 r270127  
    8888    RefPtr<AudioWorkletProcessor> m_processor; // Should only be used on the rendering thread.
    8989    HashMap<String, std::unique_ptr<AudioFloatArray>> m_paramValuesMap;
     90    Thread* m_workletThread { nullptr };
    9091
    9192    // Keeps the reference of AudioBus objects from AudioNodeInput and AudioNodeOutput in order
  • trunk/Source/WebCore/Modules/webaudio/BaseAudioContext.cpp

    r269475 r270127  
    10411041{
    10421042    ASSERT(!m_parameterDescriptorMap.contains(processorName));
     1043    bool wasEmpty = m_parameterDescriptorMap.isEmpty();
    10431044    m_parameterDescriptorMap.add(processorName, WTFMove(descriptors));
     1045    if (wasEmpty)
     1046        workletIsReady();
    10441047}
    10451048
     
    10541057
    10551058    m_finishedSourceNodes.append(&node);
     1059}
     1060
     1061void BaseAudioContext::workletIsReady()
     1062{
     1063    ASSERT(isMainThread());
     1064
     1065    // If we're already rendering when the worklet becomes ready, we need to restart
     1066    // rendering in order to switch to the audio worklet thread.
     1067    if (m_destinationNode)
     1068        m_destinationNode->restartRendering();
    10561069}
    10571070
  • trunk/Source/WebCore/Modules/webaudio/BaseAudioContext.h

    r269475 r270127  
    318318
    319319    void scheduleNodeDeletion();
     320    void workletIsReady();
    320321
    321322    // When source nodes begin playing, the BaseAudioContext keeps them alive inside m_referencedSourceNodes.
  • trunk/Source/WebCore/Modules/webaudio/DefaultAudioDestinationNode.cpp

    r269475 r270127  
    7676
    7777    ALWAYS_LOG(LOGIDENTIFIER);
     78    m_wasDestinationStarted = false;
    7879    m_destination->stop();
    7980    m_destination = nullptr;
     
    129130    };
    130131
     132    m_wasDestinationStarted = true;
    131133    m_destination->start(dispatchToRenderThreadFunction(), WTFMove(innerCompletionHandler));
    132134}
     
    141143        return;
    142144    }
     145    m_wasDestinationStarted = true;
    143146    m_destination->start(dispatchToRenderThreadFunction(), [completionHandler = WTFMove(completionHandler)](bool success) mutable {
    144147        completionHandler(success ? WTF::nullopt : makeOptional(Exception { InvalidStateError, "Failed to start the audio device"_s }));
     
    156159    }
    157160
     161    m_wasDestinationStarted = false;
    158162    m_destination->stop([completionHandler = WTFMove(completionHandler)](bool success) mutable {
    159163        completionHandler(success ? WTF::nullopt : makeOptional(Exception { InvalidStateError, "Failed to stop the audio device"_s }));
    160164    });
     165}
     166
     167void DefaultAudioDestinationNode::restartRendering()
     168{
     169    if (!m_wasDestinationStarted)
     170        return;
     171
     172    m_destination->stop();
     173    m_destination->start(dispatchToRenderThreadFunction());
    161174}
    162175
     
    192205    if (this->channelCount() != oldChannelCount && isInitialized()) {
    193206        // Re-create destination.
    194         m_destination->stop();
     207        if (m_wasDestinationStarted)
     208            m_destination->stop();
    195209        createDestination();
    196         m_destination->start(dispatchToRenderThreadFunction());
     210        if (m_wasDestinationStarted)
     211            m_destination->start(dispatchToRenderThreadFunction());
    197212    }
    198213
  • trunk/Source/WebCore/Modules/webaudio/DefaultAudioDestinationNode.h

    r269475 r270127  
    6060    void resume(CompletionHandler<void(Optional<Exception>&&)>&&) final;
    6161    void suspend(CompletionHandler<void(Optional<Exception>&&)>&&) final;
     62    void restartRendering() final;
    6263    void close(CompletionHandler<void()>&&) final;
    6364    unsigned maxChannelCount() const final;
     
    6566
    6667    RefPtr<AudioDestination> m_destination;
     68    bool m_wasDestinationStarted { false };
    6769    String m_inputDeviceId;
    6870    unsigned m_numberOfInputChannels { 0 };
Note: See TracChangeset for help on using the changeset viewer.