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"
8 #include "base/logging.h"
9 #include "base/memory/ref_counted.h"
10 #include "base/message_loop/message_loop.h"
11 #include "base/metrics/histogram.h"
12 #include "base/threading/thread_restrictions.h"
13 #include "base/time/time.h"
15 #include "dbus/message.h"
16 #include "dbus/object_path.h"
17 #include "dbus/scoped_dbus_error.h"
18 #include "dbus/util.h"
24 // Used for success ratio histograms. 1 for success, 0 for failure.
25 const int kSuccessRatioHistogramMaxValue
= 2;
29 ExportedObject::ExportedObject(Bus
* bus
,
30 const ObjectPath
& object_path
)
32 object_path_(object_path
),
33 object_is_registered_(false) {
36 ExportedObject::~ExportedObject() {
37 DCHECK(!object_is_registered_
);
40 bool ExportedObject::ExportMethodAndBlock(
41 const std::string
& interface_name
,
42 const std::string
& method_name
,
43 MethodCallCallback method_call_callback
) {
44 bus_
->AssertOnDBusThread();
46 // Check if the method is already exported.
47 const std::string absolute_method_name
=
48 GetAbsoluteMemberName(interface_name
, method_name
);
49 if (method_table_
.find(absolute_method_name
) != method_table_
.end()) {
50 LOG(ERROR
) << absolute_method_name
<< " is already exported";
56 if (!bus_
->SetUpAsyncOperations())
61 // Add the method callback to the method table.
62 method_table_
[absolute_method_name
] = method_call_callback
;
67 void ExportedObject::ExportMethod(const std::string
& interface_name
,
68 const std::string
& method_name
,
69 MethodCallCallback method_call_callback
,
70 OnExportedCallback on_exported_calback
) {
71 bus_
->AssertOnOriginThread();
73 base::Closure task
= base::Bind(&ExportedObject::ExportMethodInternal
,
79 bus_
->GetDBusTaskRunner()->PostTask(FROM_HERE
, task
);
82 void ExportedObject::SendSignal(Signal
* signal
) {
83 // For signals, the object path should be set to the path to the sender
84 // object, which is this exported object here.
85 CHECK(signal
->SetPath(object_path_
));
87 // Increment the reference count so we can safely reference the
88 // underlying signal message until the signal sending is complete. This
89 // will be unref'ed in SendSignalInternal().
90 DBusMessage
* signal_message
= signal
->raw_message();
91 dbus_message_ref(signal_message
);
93 const base::TimeTicks start_time
= base::TimeTicks::Now();
94 if (bus_
->GetDBusTaskRunner()->RunsTasksOnCurrentThread()) {
95 // The Chrome OS power manager doesn't use a dedicated TaskRunner for
96 // sending DBus messages. Sending signals asynchronously can cause an
97 // inversion in the message order if the power manager calls
98 // ObjectProxy::CallMethodAndBlock() before going back to the top level of
99 // the MessageLoop: crbug.com/472361.
100 SendSignalInternal(start_time
, signal_message
);
102 bus_
->GetDBusTaskRunner()->PostTask(
104 base::Bind(&ExportedObject::SendSignalInternal
,
111 void ExportedObject::Unregister() {
112 bus_
->AssertOnDBusThread();
114 if (!object_is_registered_
)
117 bus_
->UnregisterObjectPath(object_path_
);
118 object_is_registered_
= false;
121 void ExportedObject::ExportMethodInternal(
122 const std::string
& interface_name
,
123 const std::string
& method_name
,
124 MethodCallCallback method_call_callback
,
125 OnExportedCallback on_exported_calback
) {
126 bus_
->AssertOnDBusThread();
128 const bool success
= ExportMethodAndBlock(interface_name
,
130 method_call_callback
);
131 bus_
->GetOriginTaskRunner()->PostTask(FROM_HERE
,
132 base::Bind(&ExportedObject::OnExported
,
140 void ExportedObject::OnExported(OnExportedCallback on_exported_callback
,
141 const std::string
& interface_name
,
142 const std::string
& method_name
,
144 bus_
->AssertOnOriginThread();
146 on_exported_callback
.Run(interface_name
, method_name
, success
);
149 void ExportedObject::SendSignalInternal(base::TimeTicks start_time
,
150 DBusMessage
* signal_message
) {
152 bus_
->Send(signal_message
, &serial
);
153 dbus_message_unref(signal_message
);
154 // Record time spent to send the the signal. This is not accurate as the
155 // signal will actually be sent from the next run of the message loop,
156 // but we can at least tell the number of signals sent.
157 UMA_HISTOGRAM_TIMES("DBus.SignalSendTime",
158 base::TimeTicks::Now() - start_time
);
161 bool ExportedObject::Register() {
162 bus_
->AssertOnDBusThread();
164 if (object_is_registered_
)
167 ScopedDBusError error
;
169 DBusObjectPathVTable vtable
= {};
170 vtable
.message_function
= &ExportedObject::HandleMessageThunk
;
171 vtable
.unregister_function
= &ExportedObject::OnUnregisteredThunk
;
172 const bool success
= bus_
->TryRegisterObjectPath(object_path_
,
177 LOG(ERROR
) << "Failed to register the object: " << object_path_
.value()
178 << ": " << (error
.is_set() ? error
.message() : "");
182 object_is_registered_
= true;
186 DBusHandlerResult
ExportedObject::HandleMessage(
187 DBusConnection
* connection
,
188 DBusMessage
* raw_message
) {
189 bus_
->AssertOnDBusThread();
190 DCHECK_EQ(DBUS_MESSAGE_TYPE_METHOD_CALL
, dbus_message_get_type(raw_message
));
192 // raw_message will be unrefed on exit of the function. Increment the
193 // reference so we can use it in MethodCall.
194 dbus_message_ref(raw_message
);
195 scoped_ptr
<MethodCall
> method_call(
196 MethodCall::FromRawMessage(raw_message
));
197 const std::string interface
= method_call
->GetInterface();
198 const std::string member
= method_call
->GetMember();
200 if (interface
.empty()) {
201 // We don't support method calls without interface.
202 LOG(WARNING
) << "Interface is missing: " << method_call
->ToString();
203 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
206 // Check if we know about the method.
207 const std::string absolute_method_name
= GetAbsoluteMemberName(
209 MethodTable::const_iterator iter
= method_table_
.find(absolute_method_name
);
210 if (iter
== method_table_
.end()) {
211 // Don't know about the method.
212 LOG(WARNING
) << "Unknown method: " << method_call
->ToString();
213 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
216 const base::TimeTicks start_time
= base::TimeTicks::Now();
217 if (bus_
->HasDBusThread()) {
218 // Post a task to run the method in the origin thread.
219 bus_
->GetOriginTaskRunner()->PostTask(FROM_HERE
,
220 base::Bind(&ExportedObject::RunMethod
,
223 base::Passed(&method_call
),
226 // If the D-Bus thread is not used, just call the method directly.
227 MethodCall
* method
= method_call
.get();
228 iter
->second
.Run(method
,
229 base::Bind(&ExportedObject::SendResponse
,
232 base::Passed(&method_call
)));
235 // It's valid to say HANDLED here, and send a method response at a later
236 // time from OnMethodCompleted() asynchronously.
237 return DBUS_HANDLER_RESULT_HANDLED
;
240 void ExportedObject::RunMethod(MethodCallCallback method_call_callback
,
241 scoped_ptr
<MethodCall
> method_call
,
242 base::TimeTicks start_time
) {
243 bus_
->AssertOnOriginThread();
244 MethodCall
* method
= method_call
.get();
245 method_call_callback
.Run(method
,
246 base::Bind(&ExportedObject::SendResponse
,
249 base::Passed(&method_call
)));
252 void ExportedObject::SendResponse(base::TimeTicks start_time
,
253 scoped_ptr
<MethodCall
> method_call
,
254 scoped_ptr
<Response
> response
) {
256 if (bus_
->HasDBusThread()) {
257 bus_
->GetDBusTaskRunner()->PostTask(
259 base::Bind(&ExportedObject::OnMethodCompleted
,
261 base::Passed(&method_call
),
262 base::Passed(&response
),
265 OnMethodCompleted(method_call
.Pass(), response
.Pass(), start_time
);
269 void ExportedObject::OnMethodCompleted(scoped_ptr
<MethodCall
> method_call
,
270 scoped_ptr
<Response
> response
,
271 base::TimeTicks start_time
) {
272 bus_
->AssertOnDBusThread();
274 // Record if the method call is successful, or not. 1 if successful.
275 UMA_HISTOGRAM_ENUMERATION("DBus.ExportedMethodHandleSuccess",
277 kSuccessRatioHistogramMaxValue
);
279 // Check if the bus is still connected. If the method takes long to
280 // complete, the bus may be shut down meanwhile.
281 if (!bus_
->is_connected())
285 // Something bad happened in the method call.
286 scoped_ptr
<ErrorResponse
> error_response(
287 ErrorResponse::FromMethodCall(
290 "error occurred in " + method_call
->GetMember()));
291 bus_
->Send(error_response
->raw_message(), NULL
);
295 // The method call was successful.
296 bus_
->Send(response
->raw_message(), NULL
);
298 // Record time spent to handle the the method call. Don't include failures.
299 UMA_HISTOGRAM_TIMES("DBus.ExportedMethodHandleTime",
300 base::TimeTicks::Now() - start_time
);
303 void ExportedObject::OnUnregistered(DBusConnection
* connection
) {
306 DBusHandlerResult
ExportedObject::HandleMessageThunk(
307 DBusConnection
* connection
,
308 DBusMessage
* raw_message
,
310 ExportedObject
* self
= reinterpret_cast<ExportedObject
*>(user_data
);
311 return self
->HandleMessage(connection
, raw_message
);
314 void ExportedObject::OnUnregisteredThunk(DBusConnection
*connection
,
316 ExportedObject
* self
= reinterpret_cast<ExportedObject
*>(user_data
);
317 return self
->OnUnregistered(connection
);