LEW21 / pydbus

Pythonic DBus library
GNU Lesser General Public License v2.1
331 stars 75 forks source link

Race condition in signal handlers #87

Open janakj opened 5 years ago

janakj commented 5 years ago

I am using pydbus to resolve multicast DNS records via Avahi. In my program, I first create a RecordBrowser object in Avahi and then subscribe to the signals it generates:

sysbus = SystemBus()
daemon = sysbus.get('.Avahi', '/')

path = daemon.RecordBrowserNew(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, name, CLASS_IN, type_, 0)
browser = sysbus.get('.Avahi', path)

browser.ItemNew.connect(itemNew)
browser.ItemRemove.connect(itemRemove)
browser.Failure.connect(failure)
browser.AllForNow.connect(allForNow)
browser.CacheExhausted.connect(cacheExhausted)

The above code works fine if Avahi actually needs to resolve the request via multicast DNS.

If, however, Avahi finds the record in its internal cache, it sends the ItemNew signal very quickly. So quickly that it arrives before pydbus gives me a chance to subscribe to the ItemNew signal. In that case, my code misses the signal.

Here is an output generated by dbus-monitor that shows the ItemNew signal arriving right after the RequestBrowser got created:

method call time=1554528042.577752 sender=:1.177 -> destination=org.freedesktop.Avahi serial=8 path=/; interface=org.freedesktop.Avahi.Server; member=RecordBrowserNew
   int32 -1
   int32 -1
   string "2021.voip.phxnet.org"
   uint16 1
   uint16 1
   uint32 0
method return time=1554528042.578172 sender=:1.151 -> destination=:1.177 serial=262 reply_serial=8
   object path "/Client24/RecordBrowser1"
signal time=1554528042.578445 sender=:1.151 -> destination=:1.177 serial=263 path=/Client24/RecordBrowser1; interface=org.freedesktop.Avahi.RecordBrowser; member=ItemNew
   int32 2
   int32 0
   string "2021.voip.phxnet.org"
   uint16 1
   uint16 1
   array of bytes [
      0a 04 03 02
   ]
   uint32 5
signal time=1554528042.578770 sender=:1.151 -> destination=:1.177 serial=264 path=/Client24/RecordBrowser1; interface=org.freedesktop.Avahi.RecordBrowser; member=CacheExhausted

It only takes Avahi a couple of milliseconds to send the signal after the record browser object has been created. This is clearly too fast for pydbus, which seems to do introspection first. Only after introspection the library lets my program subscribe to the signal:

method call time=1554528042.579212 sender=:1.177 -> destination=org.freedesktop.Avahi serial=9 path=/Client24/RecordBrowser1; interface=org.freedesktop.DBus.Introspectable; member=Introspect
method return time=1554528042.579484 sender=:1.151 -> destination=:1.177 serial=265 reply_serial=9
   string "<?xml version="1.0" standalone='no'?><!--*-nxml-*-->
<?xml-stylesheet type="text/xsl" href="introspect.xsl"?>
<!DOCTYPE node SYSTEM "introspect.dtd">

<!--
  This file is part of avahi.

  avahi is free software; you can redistribute it and/or modify it
  under the terms of the GNU Lesser General Public License as
  published by the Free Software Foundation; either version 2 of the
  License, or (at your option) any later version.

  avahi is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with avahi; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
  02111-1307 USA.
-->

<node>

  <interface name="org.freedesktop.DBus.Introspectable">
    <method name="Introspect">
      <arg name="data" type="s" direction="out" />
    </method>
  </interface>

  <interface name="org.freedesktop.Avahi.RecordBrowser">

    <method name="Free"/>

    <signal name="ItemNew">
      <arg name="interface" type="i"/>
      <arg name="protocol" type="i"/>
      <arg name="name" type="s"/>
      <arg name="clazz" type="q"/>
      <arg name="type" type="q"/>
      <arg name="rdata" type="ay"/>
      <arg name="flags" type="u"/>
    </signal>

    <signal name="ItemRemove">
      <arg name="interface" type="i"/>
      <arg name="protocol" type="i"/>
      <arg name="name" type="s"/>
      <arg name="clazz" type="q"/>
      <arg name="type" type="q"/>
      <arg name="rdata" type="ay"/>
      <arg name="flags" type="u"/>
    </signal>

    <signal name="Failure">
      <arg name="error" type="s"/>
    </signal>

    <signal name="AllForNow"/>

    <signal name="CacheExhausted"/>

  </interface>
</node>
"
method call time=1554528042.581344 sender=:1.177 -> destination=org.freedesktop.DBus serial=10 path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=AddMatch
   string "type='signal',sender='org.freedesktop.Avahi',interface='org.freedesktop.Avahi.RecordBrowser',member='ItemNew',path='/Client24/RecordBrowser1'"
method return time=1554528042.581398 sender=org.freedesktop.DBus -> destination=:1.177 serial=4 reply_serial=10

I am not sure what can be done about this, except for maybe modifying the library API somehow to let the program subscribe to signals sooner, i.e., before introspection takes place.

For my use-case, I found a workaround by first subscribing to the ItemNew signal from all record browser objects before I create the object, and then doing my own dispatch:

sysbus.con.signal_subscribe('org.freedesktop.Avahi', 'org.freedesktop.Avahi.RecordBrowser', "ItemNew", None, None, 0, dispatch)

but that's hardly a general solution, it just accidentally works in my program.