ofono/dbus: detach from signals as appropriate

In the dbus_connect() code path, return the subscription token.

In the Modem class, use this token to disconnect signal callbacks when an
interface is removed.

Generalize the signal connect/disconnect handling: have one tuple defining all
signals and their handler functions, add generalized loop to attach them. Store
all subscription tokens in a dict of lists, any number of signal callbacks per
general interface name. When an interface is announced to be removed from
ofono, detach all signals for that interface implicitly.

So far this only handles the MessageManager interface's IncomingMessage signal,
but others will likely follow soon.

Fixes: OS#2242
Change-Id: I0939ef414bc599ee8742df48da04d8d9569d00ba
This commit is contained in:
Neels Hofmeyr 2017-05-10 13:24:05 +02:00 committed by Neels Hofmeyr
parent 803d87c2e8
commit 47de6b0a24
1 changed files with 28 additions and 16 deletions

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from . import log, test from . import log, test, util
from pydbus import SystemBus, Variant from pydbus import SystemBus, Variant
import time import time
@ -37,7 +37,7 @@ class DeferredHandling:
def __init__(self, dbus_iface, handler): def __init__(self, dbus_iface, handler):
self.handler = handler self.handler = handler
dbus_iface.connect(self.receive_signal) self.subscription_id = dbus_iface.connect(self.receive_signal)
def receive_signal(self, *args, **kwargs): def receive_signal(self, *args, **kwargs):
DeferredHandling.defer_queue.append((self.handler, args, kwargs)) DeferredHandling.defer_queue.append((self.handler, args, kwargs))
@ -55,7 +55,7 @@ def dbus_connect(dbus_iface, handler):
so that a signal handler is invoked only after the DBus polling is through so that a signal handler is invoked only after the DBus polling is through
by enlisting signals that should be handled in the by enlisting signals that should be handled in the
DeferredHandling.defer_queue.''' DeferredHandling.defer_queue.'''
DeferredHandling(dbus_iface, handler) return DeferredHandling(dbus_iface, handler).subscription_id
def poll(): def poll():
@ -85,6 +85,7 @@ class Modem(log.Origin):
self.set_log_category(log.C_BUS) self.set_log_category(log.C_BUS)
self._dbus_obj = None self._dbus_obj = None
self._interfaces = set() self._interfaces = set()
self._connected_signals = util.listdict()
self.sms_received_list = [] self.sms_received_list = []
# init interfaces and connect to signals: # init interfaces and connect to signals:
self.dbus_obj() self.dbus_obj()
@ -149,24 +150,35 @@ class Modem(log.Origin):
for iface in additions: for iface in additions:
self._on_interface_enabled(iface) self._on_interface_enabled(iface)
def _disconnect(self, interface_name):
subscriptions = self._connected_signals.pop(interface_name, [])
if subscriptions:
self.dbg('Disconnecting', len(subscriptions), 'signals from', interface_name)
for subscription in subscriptions:
subscription.disconnect()
def _on_interface_enabled(self, interface_name): def _on_interface_enabled(self, interface_name):
self.dbg('Interface enabled:', interface_name) self.dbg('Interface enabled:', interface_name)
if interface_name == I_SMS:
retries = 3 all_wanted_conns = {
while True: I_SMS: ( ('IncomingMessage', self._on_incoming_message), ),
try: }
dbus_connect(self.dbus_obj()[I_SMS].IncomingMessage, self._on_incoming_message)
break want = all_wanted_conns.get(interface_name)
except KeyError: if not want:
self.dbg('Interface not yet available:', I_SMS) return
retries -= 1
time.sleep(1) # sanity
if retries <= 0: self._disconnect(interface_name)
self.err('Interface enabled by signal, but not available:', I_SMS)
raise self.dbg('Connecting', len(want), 'signals to', interface_name)
for signal, cb in want:
dbus_iface = getattr(self.dbus_obj()[interface_name], signal)
self._connected_signals.add(interface_name, dbus_connect(dbus_iface, cb))
def _on_interface_disabled(self, interface_name): def _on_interface_disabled(self, interface_name):
self.dbg('Interface disabled:', interface_name) self.dbg('Interface disabled:', interface_name)
self._disconnect(interface_name)
def has_interface(self, name): def has_interface(self, name):
return name in self._interfaces return name in self._interfaces