Merge lp:~jamesh/libunity/export-multiple-scopes into lp:libunity

Proposed by James Henstridge
Status: Merged
Approved by: Michal Hruby
Approved revision: 230
Merged at revision: 231
Proposed branch: lp:~jamesh/libunity/export-multiple-scopes
Merge into: lp:libunity
Diff against target: 371 lines (+182/-61)
4 files modified
debian/libunity9.symbols (+9/-0)
src/unity-scope-dbus-connector.vala (+24/-49)
src/unity-scope-dbus-impl.vala (+93/-0)
test/vala/test-scope.vala (+56/-12)
To merge this branch: bzr merge lp:~jamesh/libunity/export-multiple-scopes
Reviewer Review Type Date Requested Status
Michal Hruby (community) Approve
PS Jenkins bot (community) continuous-integration Approve
Review via email: mp+165963@code.launchpad.net

Commit message

Make ScopeDBusConnector.export() succeed if the process already owns the requested D-Bus name, making it possible to export multiple scopes from a process under the same bus name.

Description of the change

It is currently not possible to export two scopes from the same process using the new Unity.AbstractScope API: the export() call on the second ScopeDBusConnector will fail when it checks to see if the D-Bus name is already in use.

This branch changes the logic a bit, so rather than calling NameHasOwner(name) and erroring out if it returns true, I instead call GetNameOwner(name) and error out if the owner does not match our connection's unique name. If it does match our unique name, I export the scope. If the name has no owner, we go on to request the name as before.

The rationale for this behaviour is that if the process already owns the name, then it is likely to hold on to it for as long as the scope is exported.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Michal Hruby (mhr3) wrote :

As mentioned on IRC, this is a bit racy, if two scopes are defined to use the same name, in the time between the first acquiring the name and the second one checking whether the name is already owned, any method calls delivered for the second scope will fail.

We need something like a MultiScopeExporter, or otherwise change the export semantics for this to be safe enough.

review: Needs Fixing
223. By James Henstridge

Merge from trunk

224. By James Henstridge

Defer acquisition of D-Bus names until Unity.ScopeDBusConnector.run() is
called, so we can make sure all scopes have been exported prior to
acquiring their associated names.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
225. By James Henstridge

Move the private OwnedName struct to the internal namespace, and update
the symbols file.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
226. By James Henstridge

Update symbols.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Michal Hruby (mhr3) wrote :

140 + public static bool acquire_names ()

Does this method really need to be public? I don't see why the loader should need to call it itself. Perhaps the run () method should be able to throw an error instead?

Revision history for this message
Michal Hruby (mhr3) wrote :

33 + public int ref_count;

Looks very low-level, could you turn the struct into a class with hold() and release() methods and unown the name on last release() call? Should make it more contained.

Also, this still looks like all names that need to be owned are known during the startup. That might not be the case, perhaps the loader starts with exporting a single scope and later another one with a different name is exported? What should the loader impl do in that case?

Revision history for this message
James Henstridge (jamesh) wrote :

I am using the acquire_names() call from the tests at present. It may also be useful from the loader for the load-scope-after-startup case, but I guess I can make it an internal symbol in the mean time to avoid extending the API.

The function is also designed so that it can be run multiple times too: if there is a handle for a particular name, then the name won't be requested again. So calling the function twice should be safe.

227. By James Henstridge

Move the D-Bus name management into an internal helper class, and make
the acquire_names() call internal (can still be used by tests though).

228. By James Henstridge

Tidy up symbol exports.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
James Henstridge (jamesh) wrote :

The branch has been updated to move the D-Bus name management into a separate ScopeDBusNameManager class, and the locking has been removed (based on discussion with Michal on IRC: scope export should only be occurring on the main thread.

The only new exports now are in the Unity.Internal namespace.

Revision history for this message
Michal Hruby (mhr3) wrote :

158 + public ScopeDBusNameManager ()

Singleton class, let's make this private.

164 + public static ScopeDBusNameManager get ()

The rest of libunity uses get_default () for singletons, let's keep it consistent.

225 + warning (@"Unable to own DBus name '$dbus_name'");
226 + Idle.add_full (Priority.DEFAULT, acquire_names.callback);

You can't resume the async method on first failure to own a name, if there are other own_name calls in progress, their callbacks would cause a crash.

review: Needs Fixing
229. By James Henstridge

Rename ScopeDbusNameManager.get() to get_default(), and make constructor
private.

230. By James Henstridge

In acquire_names() wait for all names to be acquired or lost before
returning.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Michal Hruby (mhr3) wrote :

LGTM :)

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/libunity9.symbols'
2--- debian/libunity9.symbols 2013-06-10 18:06:29 +0000
3+++ debian/libunity9.symbols 2013-06-18 05:48:29 +0000
4@@ -355,6 +355,9 @@
5 unity_internal_io_system_data_dirs_length1@Base 7.0.0daily13.05.31ubuntu.unity.next
6 unity_internal_merge_strategy_get_type@Base 7.0.0daily13.05.31ubuntu.unity.next
7 unity_internal_merge_strategy_merge_result@Base 7.0.0daily13.05.31ubuntu.unity.next
8+ unity_internal_owned_name_dup@Base 0replaceme
9+ unity_internal_owned_name_free@Base 0replaceme
10+ unity_internal_owned_name_get_type@Base 0replaceme
11 unity_internal_result_column_get_type@Base 7.0.0daily13.05.31ubuntu.unity.next
12 unity_internal_results_synchronizer_add_provider@Base 7.0.0daily13.05.31ubuntu.unity.next
13 unity_internal_results_synchronizer_clear@Base 7.0.0daily13.05.31ubuntu.unity.next
14@@ -393,6 +396,12 @@
15 unity_internal_scope_dbus_impl_set_categories_model@Base 7.0.0daily13.05.31ubuntu.unity.next
16 unity_internal_scope_dbus_impl_set_filters_model@Base 7.0.0daily13.05.31ubuntu.unity.next
17 unity_internal_scope_dbus_impl_unexport@Base 7.0.0daily13.05.31ubuntu.unity.next
18+ unity_internal_scope_dbus_name_manager_acquire_names@Base 0replaceme
19+ unity_internal_scope_dbus_name_manager_acquire_names_finish@Base 0replaceme
20+ unity_internal_scope_dbus_name_manager_get_default@Base 0replaceme
21+ unity_internal_scope_dbus_name_manager_get_type@Base 0replaceme
22+ unity_internal_scope_dbus_name_manager_own_name@Base 0replaceme
23+ unity_internal_scope_dbus_name_manager_unown_name@Base 0replaceme
24 unity_internal_utils_async_mutex_construct@Base 7.0.0daily13.05.31ubuntu.unity.next
25 unity_internal_utils_async_mutex_get_type@Base 7.0.0daily13.05.31ubuntu.unity.next
26 unity_internal_utils_async_mutex_lock@Base 7.0.0daily13.05.31ubuntu.unity.next
27
28=== modified file 'src/unity-scope-dbus-connector.vala'
29--- src/unity-scope-dbus-connector.vala 2013-03-28 13:34:16 +0000
30+++ src/unity-scope-dbus-connector.vala 2013-06-18 05:48:29 +0000
31@@ -40,7 +40,7 @@
32
33 private Internal.DefaultScopeDBusImpl pimpl;
34 private bool exported;
35- private uint bus_name_own_handle;
36+ private bool name_owned;
37
38 private static int proxy_usage_count = 0;
39
40@@ -51,8 +51,9 @@
41 if (pimpl == null) pimpl = new Internal.DefaultScopeDBusImpl (scope);
42 pimpl.timeout = DAEMON_TIMEOUT_SEC;
43 pimpl.on_timeout_reached.connect (timeout_reached);
44- own_name (); // this will also export the object
45+ pimpl.export ();
46 exported = true;
47+ own_name ();
48
49 proxy_usage_count++;
50 }
51@@ -81,59 +82,22 @@
52 });
53 }
54
55- private void own_name () throws Error
56+ private void own_name ()
57 {
58+ var manager = Internal.ScopeDBusNameManager.get_default ();
59 var dbus_name = scope.get_group_name ();
60- // check if the name is owned already
61- // FIXME: but track which names does this process own, cause trying to own
62- // an already owned name will fail (even though the caller is the owner)!
63- bool has_owner;
64- DBusConnection bus = Bus.get_sync (BusType.SESSION);
65- Variant result = bus.call_sync ("org.freedesktop.DBus",
66- "/org/freedesktop/dbus",
67- "org.freedesktop.DBus",
68- "NameHasOwner",
69- new Variant ("(s)", dbus_name),
70- new VariantType ("(b)"),
71- DBusCallFlags.NO_AUTO_START,
72- -1);
73- result.get ("(b)", out has_owner);
74- if (has_owner)
75- {
76- throw new IOError.FAILED ("Name '%s' is already owned".printf (dbus_name));
77- }
78-
79- bool got_name = false;
80- var ml = new MainLoop ();
81- bus_name_own_handle = Bus.own_name (
82- BusType.SESSION, dbus_name, BusNameOwnerFlags.NONE, () =>
83- {
84- // connection acquired callback
85- try
86- {
87- pimpl.export ();
88- }
89- catch (Error err)
90- {
91- warning ("Unable to export object: %s", err.message);
92- }
93- },
94- () => { got_name = true; ml.quit (); },
95- () => { ml.quit (); }
96- );
97- ml.run ();
98- if (!got_name)
99- {
100- throw new IOError.FAILED ("Unable to own DBus name '%s'".printf (dbus_name));
101- }
102+ manager.own_name (dbus_name);
103+ name_owned = true;
104 }
105
106- private void unown_name ()
107+ private void unown_name()
108 {
109- if (bus_name_own_handle != 0)
110+ if (name_owned)
111 {
112- Bus.unown_name (bus_name_own_handle);
113- bus_name_own_handle = 0;
114+ var manager = Internal.ScopeDBusNameManager.get_default ();
115+ var dbus_name = scope.get_group_name ();
116+ manager.unown_name (dbus_name);
117+ name_owned = false;
118 }
119 }
120
121@@ -142,6 +106,17 @@
122 public static void run ()
123 {
124 if (primary_loop == null) primary_loop = new MainLoop ();
125+
126+ var manager = Internal.ScopeDBusNameManager.get_default ();
127+ manager.acquire_names.begin ((obj, result) =>
128+ {
129+ if (!manager.acquire_names.end (result))
130+ {
131+ warning ("Failed to acquire all required D-Bus names");
132+ primary_loop.quit ();
133+ }
134+ });
135+
136 primary_loop.run ();
137 }
138
139
140=== modified file 'src/unity-scope-dbus-impl.vala'
141--- src/unity-scope-dbus-impl.vala 2013-06-10 17:56:20 +0000
142+++ src/unity-scope-dbus-impl.vala 2013-06-18 05:48:29 +0000
143@@ -786,4 +786,97 @@
144 }
145 }
146
147+private struct OwnedName {
148+ public int ref_count;
149+ public uint bus_name_own_handle;
150+}
151+
152+private class ScopeDBusNameManager : Object
153+{
154+ private HashTable<string,OwnedName?> owned_names;
155+
156+ private ScopeDBusNameManager ()
157+ {
158+ owned_names = new HashTable<string,OwnedName?> (str_hash, str_equal);
159+ }
160+
161+ static ScopeDBusNameManager? name_manager;
162+ public static ScopeDBusNameManager get_default ()
163+ {
164+ if (name_manager == null)
165+ {
166+ name_manager = new ScopeDBusNameManager ();
167+ }
168+ return name_manager;
169+ }
170+
171+ public void own_name (string dbus_name)
172+ {
173+ unowned OwnedName? info = owned_names.get (dbus_name);
174+ if (info != null)
175+ {
176+ info.ref_count += 1;
177+ }
178+ else
179+ {
180+ owned_names.insert (dbus_name, {1, 0});
181+ }
182+ }
183+
184+ public void unown_name (string dbus_name)
185+ {
186+ unowned Internal.OwnedName? info = owned_names.get (dbus_name);
187+ if (info != null)
188+ {
189+ info.ref_count -= 1;
190+ // Ref count dropped to zero => release the name if it has been acquired
191+ if (info.ref_count <= 0)
192+ {
193+ if (info.bus_name_own_handle != 0)
194+ {
195+ Bus.unown_name (info.bus_name_own_handle);
196+ }
197+ owned_names.remove(dbus_name);
198+ }
199+ }
200+ }
201+
202+ public async bool acquire_names ()
203+ {
204+ var count = 0;
205+ var failures = 0;
206+ owned_names.for_each ((dbus_name, info) =>
207+ {
208+ if (info.bus_name_own_handle != 0)
209+ {
210+ // The name has already been requested.
211+ return;
212+ }
213+ count += 1;
214+ info.bus_name_own_handle = Bus.own_name (
215+ BusType.SESSION, dbus_name, BusNameOwnerFlags.NONE, null,
216+ () => {
217+ count -= 1;
218+ if (count <= 0)
219+ {
220+ Idle.add_full (Priority.DEFAULT, acquire_names.callback);
221+ }
222+ },
223+ () => {
224+ failures += 1;
225+ warning (@"Unable to own DBus name '$dbus_name'");
226+ count -= 1;
227+ if (count <= 0)
228+ {
229+ Idle.add_full (Priority.DEFAULT, acquire_names.callback);
230+ }
231+ }
232+ );
233+ });
234+ yield;
235+ return failures == 0;
236+ }
237+
238+}
239+
240 } /* namespace */
241
242=== modified file 'test/vala/test-scope.vala'
243--- test/vala/test-scope.vala 2013-05-30 23:42:30 +0000
244+++ test/vala/test-scope.vala 2013-06-18 05:48:29 +0000
245@@ -86,6 +86,8 @@
246
247 Test.add_data_func ("/Unit/Scope/Export",
248 Fixture.create<ScopeInitTester> (ScopeInitTester.test_scope_export));
249+ Test.add_data_func ("/Unit/Scope/ExportTwoScopes",
250+ Fixture.create<ScopeInitTester> (ScopeInitTester.test_scope_export_two_scopes));
251 Test.add_data_func ("/Unit/Scope/ProxyConnection",
252 Fixture.create<ScopeInitTester> (ScopeInitTester.test_scope_proxy));
253 Test.add_data_func ("/Unit/Scope/OpenChannel",
254@@ -204,11 +206,12 @@
255
256 class TestScope: Unity.AbstractScope
257 {
258+ public string dbus_name { get; construct set; }
259 public string dbus_path { get; construct set; }
260
261- public TestScope (string dbus_path)
262+ public TestScope (string dbus_name, string dbus_path)
263 {
264- Object (dbus_path: dbus_path);
265+ Object (dbus_name: dbus_name, dbus_path: dbus_path);
266 }
267
268 public override Unity.ScopeSearchBase create_search_for_query (Unity.SearchContext ctx)
269@@ -269,7 +272,7 @@
270
271 public override string get_group_name ()
272 {
273- return DBUS_NAME;
274+ return dbus_name;
275 }
276
277 public override string get_unique_name ()
278@@ -317,6 +320,20 @@
279 return proxy;
280 }
281
282+ public static bool acquire_names ()
283+ {
284+ var ml = new MainLoop ();
285+ var manager = Unity.Internal.ScopeDBusNameManager.get_default ();
286+ bool success = false;
287+ manager.acquire_names.begin ((obj, result) =>
288+ {
289+ success = manager.acquire_names.end (result);
290+ ml.quit();
291+ });
292+ ml.run ();
293+ return success;
294+ }
295+
296 private void setup ()
297 {
298 }
299@@ -328,22 +345,48 @@
300
301 public void test_scope_export ()
302 {
303- scope = new TestScope (DBUS_PATH);
304- try
305- {
306- scope.export ();
307- }
308- catch (Error err) { assert_not_reached (); }
309+ scope = new TestScope (DBUS_NAME, DBUS_PATH);
310+ try
311+ {
312+ scope.export ();
313+ }
314+ catch (Error err) { assert_not_reached (); }
315+
316+ assert (acquire_names ());
317+ }
318+
319+ public void test_scope_export_two_scopes ()
320+ {
321+ scope = new TestScope (DBUS_NAME, DBUS_PATH);
322+ try
323+ {
324+ scope.export ();
325+ }
326+ catch (Error err) { assert_not_reached (); }
327+
328+ // Export a second scope using the same D-Bus name
329+ var scope2 = new TestScope (DBUS_NAME, "/second/scope/path");
330+ try
331+ {
332+ scope2.export ();
333+ }
334+ catch (Error err) { assert_not_reached (); }
335+ finally
336+ {
337+ scope2.unexport ();
338+ }
339+ assert (acquire_names ());
340 }
341
342 public void test_scope_proxy ()
343 {
344- scope = new TestScope (DBUS_PATH);
345+ scope = new TestScope (DBUS_NAME, DBUS_PATH);
346 try
347 {
348 scope.export ();
349 }
350 catch (Error err) { assert_not_reached (); }
351+ assert (acquire_names ());
352
353 var proxy = acquire_test_proxy ();
354 assert (proxy != null);
355@@ -363,13 +406,14 @@
356 {
357 base.setup ();
358
359- scope = new TestScope (DBUS_PATH);
360-
361+ scope = new TestScope (DBUS_NAME, DBUS_PATH);
362+
363 try
364 {
365 scope.export ();
366 }
367 catch (Error err) { assert_not_reached (); }
368+ assert (acquire_names ());
369
370 proxy = ScopeInitTester.acquire_test_proxy ();
371 assert (proxy != null);

Subscribers

People subscribed via source and target branches