0

Eliminate a timed wait from ExportedObject::HandleMessage().

Previouslly, we blocked in D-Bus thread until the method call is handled
in the UI thread. Turned out this was a bad idea, and caused a crash when
the UI thread is hanging (crosbug.com/21341).

This patch will eliminate the timed wait and incoming methods will be handled
completely asynchronously.

BUG=chromium-os:21341
TEST=run dbus_unittests under valgrind


Review URL: http://codereview.chromium.org/8175009

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@104497 0039d316-1c4b-4281-b951-d872f2087c98
This commit is contained in:
satorux@chromium.org
2011-10-07 16:26:30 +00:00
parent 18da0fd59d
commit e184ccec44
4 changed files with 57 additions and 44 deletions

@@ -390,7 +390,6 @@ bool Bus::SetUpAsyncOperations() {
NULL); NULL);
CHECK(success) << "Unable to allocate memory"; CHECK(success) << "Unable to allocate memory";
// TODO(satorux): Timeout is not yet implemented.
success = dbus_connection_set_timeout_functions(connection_, success = dbus_connection_set_timeout_functions(connection_,
&Bus::OnAddTimeoutThunk, &Bus::OnAddTimeoutThunk,
&Bus::OnRemoveTimeoutThunk, &Bus::OnRemoveTimeoutThunk,

@@ -383,6 +383,9 @@ class Bus : public base::RefCountedThreadSafe<Bus> {
// AssertOnOriginThread(). // AssertOnOriginThread().
virtual void AssertOnDBusThread(); virtual void AssertOnDBusThread();
// Returns true if the bus is connected to D-Bus.
bool is_connected() { return connection_ != NULL; }
protected: protected:
// This is protected, so we can define sub classes. // This is protected, so we can define sub classes.
virtual ~Bus(); virtual ~Bus();

@@ -39,10 +39,7 @@ ExportedObject::ExportedObject(Bus* bus,
: bus_(bus), : bus_(bus),
service_name_(service_name), service_name_(service_name),
object_path_(object_path), object_path_(object_path),
object_is_registered_(false), object_is_registered_(false) {
response_from_method_(NULL),
on_method_is_called_(false /* manual_reset */,
false /* initially_signaled */) {
} }
ExportedObject::~ExportedObject() { ExportedObject::~ExportedObject() {
@@ -218,67 +215,76 @@ DBusHandlerResult ExportedObject::HandleMessage(
} }
const base::TimeTicks start_time = base::TimeTicks::Now(); const base::TimeTicks start_time = base::TimeTicks::Now();
Response* response = NULL;
if (bus_->HasDBusThread()) { if (bus_->HasDBusThread()) {
response_from_method_ = NULL;
// Post a task to run the method in the origin thread. // Post a task to run the method in the origin thread.
bus_->PostTaskToOriginThread(FROM_HERE, bus_->PostTaskToOriginThread(FROM_HERE,
base::Bind(&ExportedObject::RunMethod, base::Bind(&ExportedObject::RunMethod,
this, this,
iter->second, iter->second,
method_call.get())); method_call.release(),
// Wait until the method call is done. Blocking is not desirable but we start_time));
// should return the response to the dbus-daemon in the function, so we
// don't have a choice. We wait in the D-Bus thread, so it should be ok.
{
// We need a timeout here in case the method gets stuck.
const int kTimeoutSecs = 10;
const base::TimeDelta timeout(
base::TimeDelta::FromSeconds(kTimeoutSecs));
const bool signaled = on_method_is_called_.TimedWait(timeout);
// Method not called is a fatal error. The method is likely stuck
// infinitely in the origin thread. No way to stop it from here.
CHECK(signaled) << "Method " << absolute_method_name << " not called";
}
response = response_from_method_;
} else { } else {
// If the D-Bus thread is not used, just call the method directly. We // If the D-Bus thread is not used, just call the method directly. We
// don't need the complicated logic to wait for the method call to be // don't need the complicated logic to wait for the method call to be
// complete. // complete.
response = iter->second.Run(method_call.get()); // |response| will be deleted in OnMethodCompleted().
Response* response = iter->second.Run(method_call.get());
OnMethodCompleted(method_call.release(), response, start_time);
} }
// It's valid to say HANDLED here, and send a method response at a later
// time from OnMethodCompleted() asynchronously.
return DBUS_HANDLER_RESULT_HANDLED;
}
void ExportedObject::RunMethod(MethodCallCallback method_call_callback,
MethodCall* method_call,
base::TimeTicks start_time) {
bus_->AssertOnOriginThread();
Response* response = method_call_callback.Run(method_call);
bus_->PostTaskToDBusThread(FROM_HERE,
base::Bind(&ExportedObject::OnMethodCompleted,
this,
method_call,
response,
start_time));
}
void ExportedObject::OnMethodCompleted(MethodCall* method_call,
Response* response,
base::TimeTicks start_time) {
bus_->AssertOnDBusThread();
scoped_ptr<MethodCall> method_call_deleter(method_call);
scoped_ptr<Response> response_deleter(response);
// Record if the method call is successful, or not. 1 if successful. // Record if the method call is successful, or not. 1 if successful.
UMA_HISTOGRAM_ENUMERATION("DBus.ExportedMethodHandleSuccess", UMA_HISTOGRAM_ENUMERATION("DBus.ExportedMethodHandleSuccess",
response ? 1 : 0, response ? 1 : 0,
kSuccessRatioHistogramMaxValue); kSuccessRatioHistogramMaxValue);
// Check if the bus is still connected. If the method takes long to
// complete, the bus may be shut down meanwhile.
if (!bus_->is_connected())
return;
if (!response) { if (!response) {
// Something bad happened in the method call. // Something bad happened in the method call.
scoped_ptr<dbus::ErrorResponse> error_response( scoped_ptr<dbus::ErrorResponse> error_response(
ErrorResponse::FromMethodCall(method_call.get(), ErrorResponse::FromMethodCall(
DBUS_ERROR_FAILED, method_call,
"error occurred in " + member)); DBUS_ERROR_FAILED,
dbus_connection_send(connection, error_response->raw_message(), NULL); "error occurred in " + method_call->GetMember()));
return DBUS_HANDLER_RESULT_HANDLED; bus_->Send(error_response->raw_message(), NULL);
return;
} }
// The method call was successful. // The method call was successful.
dbus_connection_send(connection, response->raw_message(), NULL); bus_->Send(response->raw_message(), NULL);
delete response;
// Record time spent to handle the the method call. Don't include failures. // Record time spent to handle the the method call. Don't include failures.
UMA_HISTOGRAM_TIMES("DBus.ExportedMethodHandleTime", UMA_HISTOGRAM_TIMES("DBus.ExportedMethodHandleTime",
base::TimeTicks::Now() - start_time); base::TimeTicks::Now() - start_time);
return DBUS_HANDLER_RESULT_HANDLED;
}
void ExportedObject::RunMethod(MethodCallCallback method_call_callback,
MethodCall* method_call) {
bus_->AssertOnOriginThread();
response_from_method_ = method_call_callback.Run(method_call);
on_method_is_called_.Signal();
} }
void ExportedObject::OnUnregistered(DBusConnection* connection) { void ExportedObject::OnUnregistered(DBusConnection* connection) {

@@ -123,7 +123,14 @@ class ExportedObject : public base::RefCountedThreadSafe<ExportedObject> {
// Runs the method. Helper function for HandleMessage(). // Runs the method. Helper function for HandleMessage().
void RunMethod(MethodCallCallback method_call_callback, void RunMethod(MethodCallCallback method_call_callback,
MethodCall* method_call); MethodCall* method_call,
base::TimeTicks start_time);
// Called on completion of the method run from RunMethod().
// Takes ownership of |method_call| and |response|.
void OnMethodCompleted(MethodCall* method_call,
Response* response,
base::TimeTicks start_time);
// Called when the object is unregistered. // Called when the object is unregistered.
void OnUnregistered(DBusConnection* connection); void OnUnregistered(DBusConnection* connection);
@@ -141,8 +148,6 @@ class ExportedObject : public base::RefCountedThreadSafe<ExportedObject> {
std::string service_name_; std::string service_name_;
std::string object_path_; std::string object_path_;
bool object_is_registered_; bool object_is_registered_;
dbus::Response* response_from_method_;
base::WaitableEvent on_method_is_called_;
// The method table where keys are absolute method names (i.e. interface // The method table where keys are absolute method names (i.e. interface
// name + method name), and values are the corresponding callbacks. // name + method name), and values are the corresponding callbacks.