Fix a data race in AudioEncoderMutableImpl and derived classes

Before this change, it could happen that a caller would get a pointer
to the encoder_ but not use it before another thread called the
Reconstruct method, changing the pointer. This of course resulted in
bad access crashes. With this change, each use of the pointer acquired
from the encoder() method is protected by the same lock that is
required to update the pointer. Note that this fix is probably too
aggressive, since it also affects the Opus implementation; the crash
has so far only been seen for iSAC.

Also adding a test to trigger the problem. The test did not trigger
the problem deterministically, but out would typically find it in less
than 1000 runs.

BUG=chromium:499468
R=jmarusic@webrtc.org, kwiberg@webrtc.org

Review URL: https://codereview.webrtc.org/1176303004.

Cr-Commit-Position: refs/heads/master@{#9436}
This commit is contained in:
Henrik Lundin
2015-06-15 13:46:15 +02:00
parent 05ce5dd0f1
commit a6aa6d96f8
6 changed files with 230 additions and 11 deletions

View File

@ -17,6 +17,7 @@
#include "webrtc/base/thread_annotations.h"
#include "webrtc/modules/audio_coding/codecs/audio_encoder.h"
#include "webrtc/modules/audio_coding/codecs/g711/include/audio_encoder_pcm.h"
#include "webrtc/modules/audio_coding/codecs/isac/main/interface/audio_encoder_isac.h"
#include "webrtc/modules/audio_coding/codecs/mock/mock_audio_encoder.h"
#include "webrtc/modules/audio_coding/main/acm2/acm_receive_test_oldapi.h"
#include "webrtc/modules/audio_coding/main/acm2/acm_send_test_oldapi.h"
@ -699,6 +700,160 @@ TEST_F(AcmIsacMtTestOldApi, DISABLED_ON_IOS(DoTest)) {
EXPECT_EQ(kEventSignaled, RunTest());
}
class AcmReRegisterIsacMtTestOldApi : public AudioCodingModuleTestOldApi {
protected:
static const int kRegisterAfterNumPackets = 5;
static const int kNumPackets = 10;
static const int kPacketSizeMs = 30;
static const int kPacketSizeSamples = kPacketSizeMs * 16;
AcmReRegisterIsacMtTestOldApi()
: AudioCodingModuleTestOldApi(),
receive_thread_(
ThreadWrapper::CreateThread(CbReceiveThread, this, "receive")),
codec_registration_thread_(
ThreadWrapper::CreateThread(CbCodecRegistrationThread,
this,
"codec_registration")),
test_complete_(EventWrapper::Create()),
crit_sect_(CriticalSectionWrapper::CreateCriticalSection()),
codec_registered_(false),
receive_packet_count_(0),
next_insert_packet_time_ms_(0),
fake_clock_(new SimulatedClock(0)) {
AudioEncoderDecoderIsac::Config config;
config.payload_type = kPayloadType;
isac_encoder_.reset(new AudioEncoderDecoderIsac(config));
clock_ = fake_clock_.get();
}
void SetUp() {
AudioCodingModuleTestOldApi::SetUp();
// Set up input audio source to read from specified file, loop after 5
// seconds, and deliver blocks of 10 ms.
const std::string input_file_name =
webrtc::test::ResourcePath("audio_coding/speech_mono_16kHz", "pcm");
audio_loop_.Init(input_file_name, 5 * kSampleRateHz, kNumSamples10ms);
RegisterCodec(); // Must be called before the threads start below.
StartThreads();
}
void RegisterCodec() override {
static_assert(kSampleRateHz == 16000, "test designed for iSAC 16 kHz");
AudioCodingModule::Codec("ISAC", &codec_, kSampleRateHz, 1);
codec_.pltype = kPayloadType;
// Register iSAC codec in ACM, effectively unregistering the PCM16B codec
// registered in AudioCodingModuleTestOldApi::SetUp();
// Only register the decoder for now. The encoder is registered later.
ASSERT_EQ(0, acm_->RegisterReceiveCodec(codec_));
}
void StartThreads() {
ASSERT_TRUE(receive_thread_->Start());
receive_thread_->SetPriority(kRealtimePriority);
ASSERT_TRUE(codec_registration_thread_->Start());
codec_registration_thread_->SetPriority(kRealtimePriority);
}
void TearDown() {
AudioCodingModuleTestOldApi::TearDown();
receive_thread_->Stop();
codec_registration_thread_->Stop();
}
EventTypeWrapper RunTest() {
return test_complete_->Wait(10 * 60 * 1000); // 10 minutes' timeout.
}
static bool CbReceiveThread(void* context) {
return reinterpret_cast<AcmReRegisterIsacMtTestOldApi*>(context)
->CbReceiveImpl();
}
bool CbReceiveImpl() {
SleepMs(1);
const size_t max_encoded_bytes = isac_encoder_->MaxEncodedBytes();
rtc::scoped_ptr<uint8_t[]> encoded(new uint8_t[max_encoded_bytes]);
AudioEncoder::EncodedInfo info;
{
CriticalSectionScoped lock(crit_sect_.get());
if (clock_->TimeInMilliseconds() < next_insert_packet_time_ms_) {
return true;
}
next_insert_packet_time_ms_ += kPacketSizeMs;
++receive_packet_count_;
// Encode new frame.
uint32_t input_timestamp = rtp_header_.header.timestamp;
while (info.encoded_bytes == 0) {
info = isac_encoder_->Encode(
input_timestamp, audio_loop_.GetNextBlock(), kNumSamples10ms,
max_encoded_bytes, encoded.get());
input_timestamp += 160; // 10 ms at 16 kHz.
}
EXPECT_EQ(rtp_header_.header.timestamp + kPacketSizeSamples,
input_timestamp);
EXPECT_EQ(rtp_header_.header.timestamp, info.encoded_timestamp);
EXPECT_EQ(rtp_header_.header.payloadType, info.payload_type);
}
// Now we're not holding the crit sect when calling ACM.
// Insert into ACM.
EXPECT_EQ(0, acm_->IncomingPacket(encoded.get(), info.encoded_bytes,
rtp_header_));
// Pull audio.
for (int i = 0; i < rtc::CheckedDivExact(kPacketSizeMs, 10); ++i) {
AudioFrame audio_frame;
EXPECT_EQ(0, acm_->PlayoutData10Ms(-1 /* default output frequency */,
&audio_frame));
fake_clock_->AdvanceTimeMilliseconds(10);
}
rtp_utility_->Forward(&rtp_header_);
return true;
}
static bool CbCodecRegistrationThread(void* context) {
return reinterpret_cast<AcmReRegisterIsacMtTestOldApi*>(context)
->CbCodecRegistrationImpl();
}
bool CbCodecRegistrationImpl() {
SleepMs(1);
if (HasFatalFailure()) {
// End the test early if a fatal failure (ASSERT_*) has occurred.
test_complete_->Set();
}
CriticalSectionScoped lock(crit_sect_.get());
if (!codec_registered_ &&
receive_packet_count_ > kRegisterAfterNumPackets) {
// Register the iSAC encoder.
EXPECT_EQ(0, acm_->RegisterSendCodec(codec_));
codec_registered_ = true;
}
if (codec_registered_ && receive_packet_count_ > kNumPackets) {
test_complete_->Set();
}
return true;
}
rtc::scoped_ptr<ThreadWrapper> receive_thread_;
rtc::scoped_ptr<ThreadWrapper> codec_registration_thread_;
const rtc::scoped_ptr<EventWrapper> test_complete_;
const rtc::scoped_ptr<CriticalSectionWrapper> crit_sect_;
bool codec_registered_ GUARDED_BY(crit_sect_);
int receive_packet_count_ GUARDED_BY(crit_sect_);
int64_t next_insert_packet_time_ms_ GUARDED_BY(crit_sect_);
rtc::scoped_ptr<AudioEncoderDecoderIsac> isac_encoder_;
rtc::scoped_ptr<SimulatedClock> fake_clock_;
test::AudioLoop audio_loop_;
};
TEST_F(AcmReRegisterIsacMtTestOldApi, DISABLED_ON_IOS(DoTest)) {
EXPECT_EQ(kEventSignaled, RunTest());
}
// Disabling all of these tests on iOS until file support has been added.
// See https://code.google.com/p/webrtc/issues/detail?id=4752 for details.
#if !defined(WEBRTC_IOS)