Merge lp:~nafai/gwibber/gnomekeyring-fix into lp:gwibber

Proposed by Travis B. Hartwell
Status: Merged
Merged at revision: not available
Proposed branch: lp:~nafai/gwibber/gnomekeyring-fix
Merge into: lp:gwibber
Diff against target: 187 lines (+75/-18)
4 files modified
bin/gwibber-service (+1/-3)
gwibber/accounts.py (+0/-1)
gwibber/microblog/dispatcher.py (+25/-14)
gwibber/microblog/util/keyring.py (+49/-0)
To merge this branch: bzr merge lp:~nafai/gwibber/gnomekeyring-fix
Reviewer Review Type Date Requested Status
Martin Pitt Pending
James W Pending
Ken VanDine Pending
Review via email: mp+22994@code.launchpad.net

Description of the change

This is a proposed fix for bug #554005.

Since accessing Gnome Keyring from threads causes a spike in CPU
usage, I have moved the access to gnome-keyring to the start of
gwibber-service, before threads are started. This section loads the
passwords from the keyring into a dictionary and then provides a
method for lookup. Then the calls to gnomekeyring are replaced with a
call to that method.

This patch handles the CPU pegging problem, but isn't a complete fix.
The following needs still to be done:

1) pitti proposed that the memory where this is stored should be
   mlock()'d. He suggested we use ctypes to wrap the calls to mlock
   and munlock. I've included stub functions, and pitti indicated he
   could help with wrapping this.

2) This implementation only reads the accounts and their passwords at
   the beginning of gwibber-service. The previous implementation
   caught changes to the passwords as well as the addition and removal
   of accounts by whenever actions are taken. Code still needs to be
   added to handle this. There are a couple of options that I can
   think of:

   a) Start and restart the gwibber service when these changes are
      made (in gwibber/accounts.py, I believe). I'm not sure if there
      are unforseen problems with this -- for example,
      gwibber-accounts can be run in a separate process, I believe.
      How does the main gwibber process handle loosing the gwibber
      service connection?

   b) Add a method to the com.Gwibber.Accounts dbus service for
      updating or adding account information (id and password). This
      method would then call a function we could add to
      gwibber.microblog.keyring to modify the passwords dictionary.
      The problem I see with this is that other developers who may
      look at the dbus services may assume this actually changes the
      passwords, not just the in-memory data structure.

   I imagine either of these options shouldn't be too hard to
   implement. I'm hoping that Ken or someone else can find a quick
   way to do this.

To post a comment you must log in.
Revision history for this message
Ken VanDine (ken-vandine) wrote :

Good work, I'll look in more detail in the morning. Since gwibber-service is dbus activated, we could just make gwibber-accounts call the Quit method on the dispatcher and then before exiting itself start it back up via dbus. Before doing this, it should actually verify it was running.

lp:~nafai/gwibber/gnomekeyring-fix updated
712. By Travis B. Hartwell

Merge Ken's threading changes and other updates.

713. By Travis B. Hartwell

Move loading account passwords to the Dispatcher class and expose it
via DBus.

714. By Travis B. Hartwell

Access mlock/munlock via ctypes.

715. By Travis B. Hartwell

Call munlock properly

716. By Travis B. Hartwell

Clean-ups per pitti's suggestions.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'bin/gwibber-service'
--- bin/gwibber-service 2010-04-03 18:10:54 +0000
+++ bin/gwibber-service 2010-04-08 16:24:30 +0000
@@ -72,7 +72,7 @@
72 log.logger.addHandler(console)72 log.logger.addHandler(console)
7373
74## Check to see if the database needs to be purged74## Check to see if the database needs to be purged
75dispatcher.purge()75#dispatcher.purge()
7676
77log.logger.debug("Setting up monitors")77log.logger.debug("Setting up monitors")
78connection_monitor = dispatcher.ConnectionMonitor()78connection_monitor = dispatcher.ConnectionMonitor()
@@ -83,6 +83,4 @@
83log.logger.debug("Monitors are up")83log.logger.debug("Monitors are up")
8484
85dispatcher = dispatcher.Dispatcher(loop)85dispatcher = dispatcher.Dispatcher(loop)
86dispatcher.daemon = True
87dispatcher.start()
88loop.run()86loop.run()
8987
=== modified file 'gwibber/accounts.py'
--- gwibber/accounts.py 2010-04-03 17:20:33 +0000
+++ gwibber/accounts.py 2010-04-08 16:24:30 +0000
@@ -332,7 +332,6 @@
332 value = [getattr(widget.props, p) for p in ["text", "active", "color"] if widget and hasattr(widget.props, p)]332 value = [getattr(widget.props, p) for p in ["text", "active", "color"] if widget and hasattr(widget.props, p)]
333333
334 if value: 334 if value:
335
336 if isinstance(value[0], gtk.gdk.Color):335 if isinstance(value[0], gtk.gdk.Color):
337 self.account[config] = gtk.color_selection_palette_to_string(336 self.account[config] = gtk.color_selection_palette_to_string(
338 gtk.color_selection_palette_from_string(value[0].to_string()))337 gtk.color_selection_palette_from_string(value[0].to_string()))
339338
=== modified file 'gwibber/microblog/dispatcher.py'
--- gwibber/microblog/dispatcher.py 2010-04-05 17:49:37 +0000
+++ gwibber/microblog/dispatcher.py 2010-04-08 16:24:30 +0000
@@ -1,13 +1,14 @@
1#!/usr/bin/env python1#!/usr/bin/env python
2# -*- coding: utf-8 -*-2# -*- coding: utf-8 -*-
33
4import multiprocessing, threading, traceback, json, time4import multiprocessing, traceback, json, time
5import gobject, dbus, dbus.service, mx.DateTime5import gobject, dbus, dbus.service, mx.DateTime
6import twitter, identica, statusnet, flickr, facebook6import twitter, identica, statusnet, flickr, facebook
7import qaiku, friendfeed, digg, gnomekeyring7import qaiku, friendfeed, digg
88
9import urlshorter9import urlshorter
10import util, util.couch10import util, util.couch
11import util.keyring
11from util import log12from util import log
12from util import resources13from util import resources
13from util.couch import Monitor as CouchMonitor14from util.couch import Monitor as CouchMonitor
@@ -58,11 +59,9 @@
5859
59 for key, val in account.items():60 for key, val in account.items():
60 if isinstance(val, str) and val.startswith(":KEYRING:"):61 if isinstance(val, str) and val.startswith(":KEYRING:"):
61 try:62 value = util.keyring.get_account_password(account["_id"])
62 value = gnomekeyring.find_items_sync(63
63 gnomekeyring.ITEM_GENERIC_SECRET,64 if value is None:
64 {"id": str("%s/%s" % (account["_id"], key))})[0].secret
65 except gnomekeyring.NoMatchError:
66 raise exceptions.GwibberProtocolError("keyring")65 raise exceptions.GwibberProtocolError("keyring")
67 return ("Failure", 0)66 return ("Failure", 0)
6867
@@ -339,16 +338,14 @@
339 expire_timeout = 5000338 expire_timeout = 5000
340 n = util.notify(sender_name, message["text"], image, expire_timeout)339 n = util.notify(sender_name, message["text"], image, expire_timeout)
341340
342class MapAsync(threading.Thread):341class MapAsync:
343 def __init__(self, func, iterable, cbsuccess, cbfailure, timeout=120):342 def __init__(self, func, iterable, cbsuccess, cbfailure, timeout=120):
344 threading.Thread.__init__(self)
345 self.iterable = iterable343 self.iterable = iterable
346 self.callback = cbsuccess344 self.callback = cbsuccess
347 self.failure = cbfailure345 self.failure = cbfailure
348 self.timeout = timeout346 self.timeout = timeout
349 self.daemon = True
350 self.func = func347 self.func = func
351 self.start()348 self.run()
352349
353 def run(self):350 def run(self):
354 try:351 try:
@@ -357,7 +354,7 @@
357 except Exception as e:354 except Exception as e:
358 self.failure(e, traceback.format_exc())355 self.failure(e, traceback.format_exc())
359356
360class Dispatcher(dbus.service.Object, threading.Thread):357class Dispatcher(dbus.service.Object):
361 """358 """
362 The Gwibber Dispatcher handles all the backend operations.359 The Gwibber Dispatcher handles all the backend operations.
363 """360 """
@@ -367,13 +364,14 @@
367 self.bus = dbus.SessionBus()364 self.bus = dbus.SessionBus()
368 bus_name = dbus.service.BusName("com.Gwibber.Service", bus=self.bus)365 bus_name = dbus.service.BusName("com.Gwibber.Service", bus=self.bus)
369 dbus.service.Object.__init__(self, bus_name, self.__dbus_object_path__)366 dbus.service.Object.__init__(self, bus_name, self.__dbus_object_path__)
370 threading.Thread.__init__(self)
371 367
372 self.collector = OperationCollector()368 self.collector = OperationCollector()
373369
374 self.refresh_count = 0370 self.refresh_count = 0
375 self.mainloop = loop371 self.mainloop = loop
376372
373 self.RefreshCreds()
374
377 if autorefresh:375 if autorefresh:
378 self.refresh()376 self.refresh()
379377
@@ -501,7 +499,20 @@
501 """499 """
502 log.logger.info("Gwibber Service is being shutdown")500 log.logger.info("Gwibber Service is being shutdown")
503 self.mainloop.quit()501 self.mainloop.quit()
504 502
503 @dbus.service.method("com.Gwibber.Service")
504 def RefreshCreds(self):
505 """
506 Reload accounts and credentials from Gnome Keyring
507 example:
508 import dbus
509 obj = dbus.SessionBus().get_object("com.Gwibber.Service", "/com/gwibber/Service")
510 service = dbus.Interface(obj, "com.Gwibber.Service")
511 service.RefreshCreds()
512 """
513 log.logger.info("Gwibber Service is reloading account credentials")
514 util.keyring.get_account_passwords()
515
505 def loading_complete(self, output):516 def loading_complete(self, output):
506 self.refresh_count += 1517 self.refresh_count += 1
507 self.LoadingComplete()518 self.LoadingComplete()
508519
=== added file 'gwibber/microblog/util/keyring.py'
--- gwibber/microblog/util/keyring.py 1970-01-01 00:00:00 +0000
+++ gwibber/microblog/util/keyring.py 2010-04-08 16:24:30 +0000
@@ -0,0 +1,49 @@
1from desktopcouch.records.server import CouchDatabase
2from const import *
3
4import atexit
5import ctypes
6import gnomekeyring
7
8passwords = {}
9
10def get_account_passwords():
11 global passwords
12
13 accounts = CouchDatabase(COUCH_DB_ACCOUNTS, create=True)
14 ids = [a['id'] for a in accounts.get_records().rows]
15
16 for id in ids:
17 account = dict(accounts.get_record(id).items())
18 for key, val in account.items():
19 if isinstance(val, str) and val.startswith(":KEYRING:"):
20 try:
21 value = gnomekeyring.find_items_sync(
22 gnomekeyring.ITEM_GENERIC_SECRET,
23 {"id": str("%s/%s" % (account["_id"], key))})[0].secret
24 mlock(value)
25 except gnomekeyring.NoMatchError:
26 raise exceptions.GwibberProtocolError("keyring")
27
28 passwords[id] = value
29
30def get_account_password(accountid):
31 global passwords
32
33 return passwords.get(accountid, None)
34
35libc = ctypes.CDLL("libc.so.6")
36
37def mlock(var):
38 libc.mlock(var, len(var))
39
40def munlock(var):
41 libc.munlock(var, len(var))
42
43def munlock_passwords():
44 global passwords
45
46 for acctid in passwords.keys():
47 munlock(passwords[acctid])
48
49atexit.register(munlock_passwords)