Commit 27cdc40b authored by Dale Curtis's avatar Dale Curtis Committed by Commit Bot

[pulse] Add error checking while waiting for pa_operations.

The pulseaudio source code checks pa_context and pa_stream state
while waiting for operation completion. Lets do the same thing on
the off chance that's the source of our hangs.

See CHECK_DEAD_GOTO macro upstream:
https://github.com/pulseaudio/pulseaudio/blob/master/src/pulse/simple.c#L68

This change modifies the WaitForOperationCompletion() method to
take an optional pa_stream and/or pa_context and checks their
state while waiting. In cases where failure could be signaled as
an error upstream, a bool status code is used to do so.

BUG=986021
R=tguilbert

Change-Id: I7075293d1f74ad12f6c596bce39bbf0bba2c9a73
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1811698
Auto-Submit: Dale Curtis <dalecurtis@chromium.org>
Reviewed-by: default avatarThomas Guilbert <tguilbert@chromium.org>
Commit-Queue: Dale Curtis <dalecurtis@chromium.org>
Cr-Commit-Position: refs/heads/master@{#697806}
parent bfe1bb08
...@@ -31,6 +31,7 @@ constexpr int kMinimumOutputBufferSize = 512; ...@@ -31,6 +31,7 @@ constexpr int kMinimumOutputBufferSize = 512;
constexpr int kMaximumOutputBufferSize = 8192; constexpr int kMaximumOutputBufferSize = 8192;
constexpr int kDefaultInputBufferSize = 1024; constexpr int kDefaultInputBufferSize = 1024;
constexpr int kDefaultSampleRate = 48000; constexpr int kDefaultSampleRate = 48000;
constexpr int kDefaultChannelCount = 2;
AudioManagerPulse::AudioManagerPulse(std::unique_ptr<AudioThread> audio_thread, AudioManagerPulse::AudioManagerPulse(std::unique_ptr<AudioThread> audio_thread,
AudioLogFactory* audio_log_factory, AudioLogFactory* audio_log_factory,
...@@ -39,9 +40,9 @@ AudioManagerPulse::AudioManagerPulse(std::unique_ptr<AudioThread> audio_thread, ...@@ -39,9 +40,9 @@ AudioManagerPulse::AudioManagerPulse(std::unique_ptr<AudioThread> audio_thread,
: AudioManagerBase(std::move(audio_thread), audio_log_factory), : AudioManagerBase(std::move(audio_thread), audio_log_factory),
input_mainloop_(pa_mainloop), input_mainloop_(pa_mainloop),
input_context_(pa_context), input_context_(pa_context),
devices_(NULL), devices_(nullptr),
native_input_sample_rate_(0), native_input_sample_rate_(kDefaultSampleRate),
native_channel_count_(0), native_channel_count_(kDefaultChannelCount),
default_source_is_monitor_(false) { default_source_is_monitor_(false) {
DCHECK(input_mainloop_); DCHECK(input_mainloop_);
DCHECK(input_context_); DCHECK(input_context_);
...@@ -84,7 +85,7 @@ void AudioManagerPulse::GetAudioDeviceNames( ...@@ -84,7 +85,7 @@ void AudioManagerPulse::GetAudioDeviceNames(
operation = pa_context_get_sink_info_list( operation = pa_context_get_sink_info_list(
input_context_, OutputDevicesInfoCallback, this); input_context_, OutputDevicesInfoCallback, this);
} }
WaitForOperationCompletion(input_mainloop_, operation); WaitForOperationCompletion(input_mainloop_, operation, input_context_);
// Prepend the default device if the list is not empty. // Prepend the default device if the list is not empty.
if (!device_names->empty()) if (!device_names->empty())
...@@ -111,7 +112,7 @@ AudioParameters AudioManagerPulse::GetInputStreamParameters( ...@@ -111,7 +112,7 @@ AudioParameters AudioManagerPulse::GetInputStreamParameters(
auto* operation = pa_context_get_source_info_by_name( auto* operation = pa_context_get_source_info_by_name(
input_context_, default_source_name_.c_str(), DefaultSourceInfoCallback, input_context_, default_source_name_.c_str(), DefaultSourceInfoCallback,
this); this);
WaitForOperationCompletion(input_mainloop_, operation); WaitForOperationCompletion(input_mainloop_, operation, input_context_);
// We don't want to accidentally open a monitor device, so return invalid // We don't want to accidentally open a monitor device, so return invalid
// parameters for those. // parameters for those.
...@@ -252,18 +253,9 @@ void AudioManagerPulse::UpdateNativeAudioHardwareInfo() { ...@@ -252,18 +253,9 @@ void AudioManagerPulse::UpdateNativeAudioHardwareInfo() {
DCHECK(input_mainloop_); DCHECK(input_mainloop_);
DCHECK(input_context_); DCHECK(input_context_);
AutoPulseLock auto_lock(input_mainloop_); AutoPulseLock auto_lock(input_mainloop_);
// Ensure the context hasn't gone bad after creation.
pa_context_state_t context_state = pa_context_get_state(input_context_);
if (!PA_CONTEXT_IS_GOOD(context_state)) {
LOG(ERROR) << "Can't retrieve hardware parameters. pa_context is bad: "
<< context_state;
return;
}
pa_operation* operation = pa_context_get_server_info( pa_operation* operation = pa_context_get_server_info(
input_context_, AudioHardwareInfoCallback, this); input_context_, AudioHardwareInfoCallback, this);
WaitForOperationCompletion(input_mainloop_, operation); WaitForOperationCompletion(input_mainloop_, operation, input_context_);
// Be careful about adding OS calls to this method. // Be careful about adding OS calls to this method.
// GetPreferredOutputStreamParameters() calls this method on a critical path. // GetPreferredOutputStreamParameters() calls this method on a critical path.
......
...@@ -30,6 +30,7 @@ pa_operation* pa_context_set_source_volume_by_index(pa_context* c, uint32_t idx, ...@@ -30,6 +30,7 @@ pa_operation* pa_context_set_source_volume_by_index(pa_context* c, uint32_t idx,
void pa_context_set_state_callback(pa_context* c, pa_context_notify_cb_t cb, void* userdata); void pa_context_set_state_callback(pa_context* c, pa_context_notify_cb_t cb, void* userdata);
pa_operation_state_t pa_operation_get_state(pa_operation* o); pa_operation_state_t pa_operation_get_state(pa_operation* o);
void pa_context_unref(pa_context* c); void pa_context_unref(pa_context* c);
void pa_operation_cancel(pa_operation* o)
void pa_operation_unref(pa_operation* o); void pa_operation_unref(pa_operation* o);
int pa_stream_begin_write(pa_stream* p, void** data, size_t* nbytes); int pa_stream_begin_write(pa_stream* p, void** data, size_t* nbytes);
int pa_stream_connect_playback(pa_stream* s, const char* dev, const pa_buffer_attr* attr, pa_stream_flags_t flags, const pa_cvolume* volume,pa_stream* sync_stream); int pa_stream_connect_playback(pa_stream* s, const char* dev, const pa_buffer_attr* attr, pa_stream_flags_t flags, const pa_cvolume* volume,pa_stream* sync_stream);
......
...@@ -88,7 +88,11 @@ void PulseAudioInputStream::Start(AudioInputCallback* callback) { ...@@ -88,7 +88,11 @@ void PulseAudioInputStream::Start(AudioInputCallback* callback) {
pa_operation* operation = pa_operation* operation =
pa_stream_cork(handle_, 0, &pulse::StreamSuccessCallback, pa_mainloop_); pa_stream_cork(handle_, 0, &pulse::StreamSuccessCallback, pa_mainloop_);
WaitForOperationCompletion(pa_mainloop_, operation);
if (!WaitForOperationCompletion(pa_mainloop_, operation, pa_context_,
handle_)) {
callback_->OnError();
}
} }
void PulseAudioInputStream::Stop() { void PulseAudioInputStream::Stop() {
...@@ -106,16 +110,21 @@ void PulseAudioInputStream::Stop() { ...@@ -106,16 +110,21 @@ void PulseAudioInputStream::Stop() {
pa_stream_drop(handle_); pa_stream_drop(handle_);
fifo_.Clear(); fifo_.Clear();
pa_operation* operation = pa_stream_flush(handle_, pa_operation* operation =
&pulse::StreamSuccessCallback, pa_stream_flush(handle_, &pulse::StreamSuccessCallback, pa_mainloop_);
pa_mainloop_); if (!WaitForOperationCompletion(pa_mainloop_, operation, pa_context_,
WaitForOperationCompletion(pa_mainloop_, operation); handle_)) {
callback_->OnError();
}
// Stop the stream. // Stop the stream.
pa_stream_set_read_callback(handle_, NULL, NULL); pa_stream_set_read_callback(handle_, NULL, NULL);
operation = pa_stream_cork(handle_, 1, &pulse::StreamSuccessCallback, operation =
pa_mainloop_); pa_stream_cork(handle_, 1, &pulse::StreamSuccessCallback, pa_mainloop_);
WaitForOperationCompletion(pa_mainloop_, operation); if (!WaitForOperationCompletion(pa_mainloop_, operation, pa_context_,
handle_)) {
callback_->OnError();
}
callback_ = NULL; callback_ = NULL;
} }
...@@ -126,9 +135,9 @@ void PulseAudioInputStream::Close() { ...@@ -126,9 +135,9 @@ void PulseAudioInputStream::Close() {
if (handle_) { if (handle_) {
// Disable all the callbacks before disconnecting. // Disable all the callbacks before disconnecting.
pa_stream_set_state_callback(handle_, NULL, NULL); pa_stream_set_state_callback(handle_, NULL, NULL);
pa_operation* operation = pa_stream_flush( pa_operation* operation =
handle_, &pulse::StreamSuccessCallback, pa_mainloop_); pa_stream_flush(handle_, &pulse::StreamSuccessCallback, pa_mainloop_);
WaitForOperationCompletion(pa_mainloop_, operation); WaitForOperationCompletion(pa_mainloop_, operation, pa_context_, handle_);
if (pa_stream_get_state(handle_) != PA_STREAM_UNCONNECTED) if (pa_stream_get_state(handle_) != PA_STREAM_UNCONNECTED)
pa_stream_disconnect(handle_); pa_stream_disconnect(handle_);
...@@ -158,10 +167,11 @@ void PulseAudioInputStream::SetVolume(double volume) { ...@@ -158,10 +167,11 @@ void PulseAudioInputStream::SetVolume(double volume) {
if (!channels_) { if (!channels_) {
// Get the number of channels for the source only when the |channels_| is 0. // Get the number of channels for the source only when the |channels_| is 0.
// We are assuming the stream source is not changed on the fly here. // We are assuming the stream source is not changed on the fly here.
operation = pa_context_get_source_info_by_index( operation = pa_context_get_source_info_by_index(pa_context_, index,
pa_context_, index, &VolumeCallback, this); &VolumeCallback, this);
WaitForOperationCompletion(pa_mainloop_, operation); if (!WaitForOperationCompletion(pa_mainloop_, operation, pa_context_,
if (!channels_) { handle_) ||
!channels_) {
DLOG(WARNING) << "Failed to get the number of channels for the source"; DLOG(WARNING) << "Failed to get the number of channels for the source";
return; return;
} }
...@@ -351,8 +361,8 @@ bool PulseAudioInputStream::GetSourceInformation(pa_source_info_cb_t callback) { ...@@ -351,8 +361,8 @@ bool PulseAudioInputStream::GetSourceInformation(pa_source_info_cb_t callback) {
size_t index = pa_stream_get_device_index(handle_); size_t index = pa_stream_get_device_index(handle_);
pa_operation* operation = pa_operation* operation =
pa_context_get_source_info_by_index(pa_context_, index, callback, this); pa_context_get_source_info_by_index(pa_context_, index, callback, this);
WaitForOperationCompletion(pa_mainloop_, operation); return WaitForOperationCompletion(pa_mainloop_, operation, pa_context_,
return true; handle_);
} }
} // namespace media } // namespace media
...@@ -91,7 +91,8 @@ void PulseAudioOutputStream::Reset() { ...@@ -91,7 +91,8 @@ void PulseAudioOutputStream::Reset() {
// Ensure all samples are played out before shutdown. // Ensure all samples are played out before shutdown.
pa_operation* operation = pa_stream_flush( pa_operation* operation = pa_stream_flush(
pa_stream_, &pulse::StreamSuccessCallback, pa_mainloop_); pa_stream_, &pulse::StreamSuccessCallback, pa_mainloop_);
WaitForOperationCompletion(pa_mainloop_, operation); WaitForOperationCompletion(pa_mainloop_, operation, pa_context_,
pa_stream_);
// Release PulseAudio structures. // Release PulseAudio structures.
pa_stream_disconnect(pa_stream_); pa_stream_disconnect(pa_stream_);
...@@ -214,7 +215,10 @@ void PulseAudioOutputStream::Start(AudioSourceCallback* callback) { ...@@ -214,7 +215,10 @@ void PulseAudioOutputStream::Start(AudioSourceCallback* callback) {
// Uncork (resume) the stream. // Uncork (resume) the stream.
pa_operation* operation = pa_stream_cork( pa_operation* operation = pa_stream_cork(
pa_stream_, 0, &pulse::StreamSuccessCallback, pa_mainloop_); pa_stream_, 0, &pulse::StreamSuccessCallback, pa_mainloop_);
WaitForOperationCompletion(pa_mainloop_, operation); if (!WaitForOperationCompletion(pa_mainloop_, operation, pa_context_,
pa_stream_)) {
callback->OnError();
}
} }
void PulseAudioOutputStream::Stop() { void PulseAudioOutputStream::Stop() {
...@@ -226,18 +230,25 @@ void PulseAudioOutputStream::Stop() { ...@@ -226,18 +230,25 @@ void PulseAudioOutputStream::Stop() {
// Set |source_callback_| to NULL so all FulfillWriteRequest() calls which may // Set |source_callback_| to NULL so all FulfillWriteRequest() calls which may
// occur while waiting on the flush and cork exit immediately. // occur while waiting on the flush and cork exit immediately.
source_callback_ = NULL; auto* callback = source_callback_;
source_callback_ = nullptr;
// Flush the stream prior to cork, doing so after will cause hangs. Write // Flush the stream prior to cork, doing so after will cause hangs. Write
// callbacks are suspended while inside pa_threaded_mainloop_lock() so this // callbacks are suspended while inside pa_threaded_mainloop_lock() so this
// is all thread safe. // is all thread safe.
pa_operation* operation = pa_stream_flush( pa_operation* operation =
pa_stream_, &pulse::StreamSuccessCallback, pa_mainloop_); pa_stream_flush(pa_stream_, &pulse::StreamSuccessCallback, pa_mainloop_);
WaitForOperationCompletion(pa_mainloop_, operation); if (!WaitForOperationCompletion(pa_mainloop_, operation, pa_context_,
pa_stream_)) {
callback->OnError();
}
operation = pa_stream_cork(pa_stream_, 1, &pulse::StreamSuccessCallback, operation = pa_stream_cork(pa_stream_, 1, &pulse::StreamSuccessCallback,
pa_mainloop_); pa_mainloop_);
WaitForOperationCompletion(pa_mainloop_, operation); if (!WaitForOperationCompletion(pa_mainloop_, operation, pa_context_,
pa_stream_)) {
callback->OnError();
}
} }
void PulseAudioOutputStream::SetVolume(double volume) { void PulseAudioOutputStream::SetVolume(double volume) {
......
...@@ -292,17 +292,43 @@ pa_channel_map ChannelLayoutToPAChannelMap(ChannelLayout channel_layout) { ...@@ -292,17 +292,43 @@ pa_channel_map ChannelLayoutToPAChannelMap(ChannelLayout channel_layout) {
return channel_map; return channel_map;
} }
void WaitForOperationCompletion(pa_threaded_mainloop* pa_mainloop, bool WaitForOperationCompletion(pa_threaded_mainloop* mainloop,
pa_operation* operation) { pa_operation* operation,
pa_context* optional_context,
pa_stream* optional_stream) {
if (!operation) { if (!operation) {
DLOG(WARNING) << "Operation is NULL"; LOG(ERROR) << "pa_operation is nullptr.";
return; return false;
} }
while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING) while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING) {
pa_threaded_mainloop_wait(pa_mainloop); if (optional_context) {
pa_context_state_t context_state = pa_context_get_state(optional_context);
if (!PA_CONTEXT_IS_GOOD(context_state)) {
LOG(ERROR) << "pa_context went bad while waiting: state="
<< context_state << ", error="
<< pa_strerror(pa_context_errno(optional_context));
pa_operation_cancel(operation);
pa_operation_unref(operation);
return false;
}
}
if (optional_stream) {
pa_stream_state_t stream_state = pa_stream_get_state(optional_stream);
if (!PA_STREAM_IS_GOOD(stream_state)) {
LOG(ERROR) << "pa_stream went bad while waiting: " << stream_state;
pa_operation_cancel(operation);
pa_operation_unref(operation);
return false;
}
}
pa_threaded_mainloop_wait(mainloop);
}
pa_operation_unref(operation); pa_operation_unref(operation);
return true;
} }
base::TimeDelta GetHardwareLatency(pa_stream* stream) { base::TimeDelta GetHardwareLatency(pa_stream* stream) {
...@@ -534,7 +560,7 @@ std::string GetBusOfInput(pa_threaded_mainloop* mainloop, ...@@ -534,7 +560,7 @@ std::string GetBusOfInput(pa_threaded_mainloop* mainloop,
InputBusData data(mainloop, name); InputBusData data(mainloop, name);
pa_operation* operation = pa_operation* operation =
pa_context_get_source_info_list(context, InputBusCallback, &data); pa_context_get_source_info_list(context, InputBusCallback, &data);
WaitForOperationCompletion(mainloop, operation); WaitForOperationCompletion(mainloop, operation, context);
return data.bus_; return data.bus_;
} }
...@@ -547,7 +573,7 @@ std::string GetOutputCorrespondingTo(pa_threaded_mainloop* mainloop, ...@@ -547,7 +573,7 @@ std::string GetOutputCorrespondingTo(pa_threaded_mainloop* mainloop,
OutputBusData data(mainloop, bus); OutputBusData data(mainloop, bus);
pa_operation* operation = pa_operation* operation =
pa_context_get_sink_info_list(context, OutputBusCallback, &data); pa_context_get_sink_info_list(context, OutputBusCallback, &data);
WaitForOperationCompletion(mainloop, operation); WaitForOperationCompletion(mainloop, operation, context);
return data.name_; return data.name_;
} }
...@@ -560,7 +586,7 @@ std::string GetRealDefaultDeviceId(pa_threaded_mainloop* mainloop, ...@@ -560,7 +586,7 @@ std::string GetRealDefaultDeviceId(pa_threaded_mainloop* mainloop,
DefaultDevicesData data(mainloop); DefaultDevicesData data(mainloop);
pa_operation* operation = pa_operation* operation =
pa_context_get_server_info(context, &GetDefaultDeviceIdCallback, &data); pa_context_get_server_info(context, &GetDefaultDeviceIdCallback, &data);
WaitForOperationCompletion(mainloop, operation); WaitForOperationCompletion(mainloop, operation, context);
return (type == RequestType::INPUT) ? data.input_ : data.output_; return (type == RequestType::INPUT) ? data.input_ : data.output_;
} }
......
...@@ -50,8 +50,13 @@ void ContextStateCallback(pa_context* context, void* mainloop); ...@@ -50,8 +50,13 @@ void ContextStateCallback(pa_context* context, void* mainloop);
pa_channel_map ChannelLayoutToPAChannelMap(ChannelLayout channel_layout); pa_channel_map ChannelLayoutToPAChannelMap(ChannelLayout channel_layout);
void WaitForOperationCompletion(pa_threaded_mainloop* mainloop, // Blocks until pa_operation completes. If |optional_context| and/or
pa_operation* operation); // |optional_stream| are provided, the method will cancel |operation| and return
// false if either the context or stream enter a bad state while waiting.
bool WaitForOperationCompletion(pa_threaded_mainloop* mainloop,
pa_operation* operation,
pa_context* optional_context = nullptr,
pa_stream* optional_stream = nullptr);
base::TimeDelta GetHardwareLatency(pa_stream* stream); base::TimeDelta GetHardwareLatency(pa_stream* stream);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment