1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "dbus/exported_object.h"
10 #include "base/bind.h"
11 #include "base/logging.h"
12 #include "base/memory/ref_counted.h"
13 #include "base/metrics/histogram_macros.h"
14 #include "base/task_runner.h"
15 #include "base/threading/thread_restrictions.h"
16 #include "base/time/time.h"
18 #include "dbus/message.h"
19 #include "dbus/object_path.h"
20 #include "dbus/scoped_dbus_error.h"
21 #include "dbus/util.h"
27 // Used for success ratio histograms. 1 for success, 0 for failure.
28 const int kSuccessRatioHistogramMaxValue = 2;
32 ExportedObject::ExportedObject(Bus* bus,
33 const ObjectPath& object_path)
35 object_path_(object_path),
36 object_is_registered_(false) {
37 LOG_IF(FATAL, !object_path_.IsValid()) << object_path_.value();
40 ExportedObject::~ExportedObject() {
41 DCHECK(!object_is_registered_);
44 bool ExportedObject::ExportMethodAndBlock(
45 const std::string& interface_name,
46 const std::string& method_name,
47 MethodCallCallback method_call_callback) {
48 bus_->AssertOnDBusThread();
50 // Check if the method is already exported.
51 const std::string absolute_method_name =
52 GetAbsoluteMemberName(interface_name, method_name);
53 if (method_table_.find(absolute_method_name) != method_table_.end()) {
54 LOG(ERROR) << absolute_method_name << " is already exported";
60 if (!bus_->SetUpAsyncOperations())
65 // Add the method callback to the method table.
66 method_table_[absolute_method_name] = method_call_callback;
71 bool ExportedObject::UnexportMethodAndBlock(const std::string& interface_name,
72 const std::string& method_name) {
73 bus_->AssertOnDBusThread();
75 const std::string absolute_method_name =
76 GetAbsoluteMemberName(interface_name, method_name);
77 MethodTable::const_iterator iter = method_table_.find(absolute_method_name);
78 if (iter == method_table_.end()) {
79 LOG(ERROR) << absolute_method_name << " is not exported";
83 method_table_.erase(iter);
88 void ExportedObject::ExportMethod(const std::string& interface_name,
89 const std::string& method_name,
90 MethodCallCallback method_call_callback,
91 OnExportedCallback on_exported_calback) {
92 bus_->AssertOnOriginThread();
94 base::Closure task = base::Bind(&ExportedObject::ExportMethodInternal,
100 bus_->GetDBusTaskRunner()->PostTask(FROM_HERE, task);
103 void ExportedObject::UnexportMethod(
104 const std::string& interface_name,
105 const std::string& method_name,
106 OnUnexportedCallback on_unexported_calback) {
107 bus_->AssertOnOriginThread();
110 base::Bind(&ExportedObject::UnexportMethodInternal, this, interface_name,
111 method_name, on_unexported_calback);
112 bus_->GetDBusTaskRunner()->PostTask(FROM_HERE, task);
115 void ExportedObject::SendSignal(Signal* signal) {
116 // For signals, the object path should be set to the path to the sender
117 // object, which is this exported object here.
118 CHECK(signal->SetPath(object_path_));
120 // Increment the reference count so we can safely reference the
121 // underlying signal message until the signal sending is complete. This
122 // will be unref'ed in SendSignalInternal().
123 DBusMessage* signal_message = signal->raw_message();
124 dbus_message_ref(signal_message);
126 const base::TimeTicks start_time = base::TimeTicks::Now();
127 if (bus_->GetDBusTaskRunner()->RunsTasksInCurrentSequence()) {
128 // The Chrome OS power manager doesn't use a dedicated TaskRunner for
129 // sending DBus messages. Sending signals asynchronously can cause an
130 // inversion in the message order if the power manager calls
131 // ObjectProxy::CallMethodAndBlock() before going back to the top level of
132 // the MessageLoop: crbug.com/472361.
133 SendSignalInternal(start_time, signal_message);
135 bus_->GetDBusTaskRunner()->PostTask(
136 FROM_HERE, base::BindOnce(&ExportedObject::SendSignalInternal, this,
137 start_time, signal_message));
141 void ExportedObject::Unregister() {
142 bus_->AssertOnDBusThread();
144 if (!object_is_registered_)
147 bus_->UnregisterObjectPath(object_path_);
148 object_is_registered_ = false;
151 void ExportedObject::ExportMethodInternal(
152 const std::string& interface_name,
153 const std::string& method_name,
154 MethodCallCallback method_call_callback,
155 OnExportedCallback on_exported_calback) {
156 bus_->AssertOnDBusThread();
158 const bool success = ExportMethodAndBlock(interface_name,
160 method_call_callback);
161 bus_->GetOriginTaskRunner()->PostTask(
163 base::BindOnce(&ExportedObject::OnExported, this, on_exported_calback,
164 interface_name, method_name, success));
167 void ExportedObject::UnexportMethodInternal(
168 const std::string& interface_name,
169 const std::string& method_name,
170 OnUnexportedCallback on_unexported_calback) {
171 bus_->AssertOnDBusThread();
173 const bool success = UnexportMethodAndBlock(interface_name, method_name);
174 bus_->GetOriginTaskRunner()->PostTask(
176 base::BindOnce(&ExportedObject::OnUnexported, this, on_unexported_calback,
177 interface_name, method_name, success));
180 void ExportedObject::OnExported(OnExportedCallback on_exported_callback,
181 const std::string& interface_name,
182 const std::string& method_name,
184 bus_->AssertOnOriginThread();
186 on_exported_callback.Run(interface_name, method_name, success);
189 void ExportedObject::OnUnexported(OnExportedCallback on_unexported_callback,
190 const std::string& interface_name,
191 const std::string& method_name,
193 bus_->AssertOnOriginThread();
195 on_unexported_callback.Run(interface_name, method_name, success);
198 void ExportedObject::SendSignalInternal(base::TimeTicks start_time,
199 DBusMessage* signal_message) {
201 bus_->Send(signal_message, &serial);
202 dbus_message_unref(signal_message);
203 // Record time spent to send the the signal. This is not accurate as the
204 // signal will actually be sent from the next run of the message loop,
205 // but we can at least tell the number of signals sent.
206 UMA_HISTOGRAM_TIMES("DBus.SignalSendTime",
207 base::TimeTicks::Now() - start_time);
210 bool ExportedObject::Register() {
211 bus_->AssertOnDBusThread();
213 if (object_is_registered_)
216 ScopedDBusError error;
218 DBusObjectPathVTable vtable = {};
219 vtable.message_function = &ExportedObject::HandleMessageThunk;
220 vtable.unregister_function = &ExportedObject::OnUnregisteredThunk;
221 const bool success = bus_->TryRegisterObjectPath(object_path_,
226 LOG(ERROR) << "Failed to register the object: " << object_path_.value()
227 << ": " << (error.is_set() ? error.message() : "");
231 object_is_registered_ = true;
235 DBusHandlerResult ExportedObject::HandleMessage(
236 DBusConnection* connection,
237 DBusMessage* raw_message) {
238 bus_->AssertOnDBusThread();
239 // ExportedObject only handles method calls. Ignore other message types (e.g.
241 if (dbus_message_get_type(raw_message) != DBUS_MESSAGE_TYPE_METHOD_CALL)
242 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
244 // raw_message will be unrefed on exit of the function. Increment the
245 // reference so we can use it in MethodCall.
246 dbus_message_ref(raw_message);
247 std::unique_ptr<MethodCall> method_call(
248 MethodCall::FromRawMessage(raw_message));
249 const std::string interface = method_call->GetInterface();
250 const std::string member = method_call->GetMember();
252 if (interface.empty()) {
253 // We don't support method calls without interface.
254 LOG(WARNING) << "Interface is missing: " << method_call->ToString();
255 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
258 // Check if we know about the method.
259 const std::string absolute_method_name = GetAbsoluteMemberName(
261 MethodTable::const_iterator iter = method_table_.find(absolute_method_name);
262 if (iter == method_table_.end()) {
263 // Don't know about the method.
264 LOG(WARNING) << "Unknown method: " << method_call->ToString();
265 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
268 const base::TimeTicks start_time = base::TimeTicks::Now();
269 if (bus_->HasDBusThread()) {
270 // Post a task to run the method in the origin thread.
271 bus_->GetOriginTaskRunner()->PostTask(
273 base::BindOnce(&ExportedObject::RunMethod, this, iter->second,
274 std::move(method_call), start_time));
276 // If the D-Bus thread is not used, just call the method directly.
277 MethodCall* method = method_call.get();
278 iter->second.Run(method,
279 base::Bind(&ExportedObject::SendResponse,
282 base::Passed(&method_call)));
285 // It's valid to say HANDLED here, and send a method response at a later
286 // time from OnMethodCompleted() asynchronously.
287 return DBUS_HANDLER_RESULT_HANDLED;
290 void ExportedObject::RunMethod(MethodCallCallback method_call_callback,
291 std::unique_ptr<MethodCall> method_call,
292 base::TimeTicks start_time) {
293 bus_->AssertOnOriginThread();
294 MethodCall* method = method_call.get();
295 method_call_callback.Run(method,
296 base::Bind(&ExportedObject::SendResponse,
299 base::Passed(&method_call)));
302 void ExportedObject::SendResponse(base::TimeTicks start_time,
303 std::unique_ptr<MethodCall> method_call,
304 std::unique_ptr<Response> response) {
306 if (bus_->HasDBusThread()) {
307 bus_->GetDBusTaskRunner()->PostTask(
308 FROM_HERE, base::BindOnce(&ExportedObject::OnMethodCompleted, this,
309 std::move(method_call), std::move(response),
312 OnMethodCompleted(std::move(method_call), std::move(response), start_time);
316 void ExportedObject::OnMethodCompleted(std::unique_ptr<MethodCall> method_call,
317 std::unique_ptr<Response> response,
318 base::TimeTicks start_time) {
319 bus_->AssertOnDBusThread();
321 // Record if the method call is successful, or not. 1 if successful.
322 UMA_HISTOGRAM_ENUMERATION("DBus.ExportedMethodHandleSuccess",
324 kSuccessRatioHistogramMaxValue);
326 // Check if the bus is still connected. If the method takes long to
327 // complete, the bus may be shut down meanwhile.
328 if (!bus_->IsConnected())
332 // Something bad happened in the method call.
333 std::unique_ptr<ErrorResponse> error_response(ErrorResponse::FromMethodCall(
334 method_call.get(), DBUS_ERROR_FAILED,
335 "error occurred in " + method_call->GetMember()));
336 bus_->Send(error_response->raw_message(), nullptr);
340 // The method call was successful.
341 bus_->Send(response->raw_message(), nullptr);
343 // Record time spent to handle the the method call. Don't include failures.
344 UMA_HISTOGRAM_TIMES("DBus.ExportedMethodHandleTime",
345 base::TimeTicks::Now() - start_time);
348 void ExportedObject::OnUnregistered(DBusConnection* connection) {
351 DBusHandlerResult ExportedObject::HandleMessageThunk(
352 DBusConnection* connection,
353 DBusMessage* raw_message,
355 ExportedObject* self = reinterpret_cast<ExportedObject*>(user_data);
356 return self->HandleMessage(connection, raw_message);
359 void ExportedObject::OnUnregisteredThunk(DBusConnection *connection,
361 ExportedObject* self = reinterpret_cast<ExportedObject*>(user_data);
362 return self->OnUnregistered(connection);