Unable to duplicate bus filter
kent-williams opened this issue · comments
Hello,
First off thank you so much for sharing this library! I'm working on an asyncio bluetooth low energy (bluez) library utilizing your library.
I have a class that sets up a callback filter using add_filter & bus_add_match. My issue is that when multiple instances of this class are created, only the first instance to register the filter is receiving all the message signals. I have been sure to include the path, since that is the only differentiator between the bus_add_match calls. My question therefore is, should I be treating the bus_add_match as a global rule, or should they be able to register match rules individually?
Hopefully this is clear enough, happy to expand if not.
Thanks!
-Kent
Let me try and understand what you are doing.
Are you opening multiple connections to the same D-Bus daemon? Or are you registering these callbacks on the same connection? I imagine that separate Connection
objects would each receive their own copies of appropriately-addressed incoming messages.
If you are registering multiple filters on the same Connection
, are you returning DBUS.HANDLER_RESULT_NOT_YET_HANDLED
from the handlers? Because DBUS.HANDLER_RESULT_HANDLED
will stop the search for further filters.
For each instance of the class, I am opening a new connection to the same D-Bus daemon (DBUS.BUS_SYSTEM). For each one I am calling bus_add_match with the same rules, but differing only in the path. All signals that were registered with different path rules are being sent to the first registered filter and match only, and no other instances after that.
I recently observed something new. When I switched the signal call back return from HANDLER_RESULT_HANDLED to HANDLER_RESULT_NOT_YET_HANDLED, each instance of the class received the message. This means that the path key is not being properly filtered I'd imagine, and therefore the message is sent to both, even though it should only be sent to the callback that registered the appropriate keys including the sender path. The bus_add_match looks like the following:
self._dbus.bus_add_match({"type": "signal", "interface": "org.freedesktop.DBus.Properties", "member": "PropertiesChanged",
"arg0": "org.bluez.GattCharacteristic1", "path": char_path})
Where the path looks something like '/org/bluez/hci0/dev_D4_D2_34_E3_BC_C5/service001b/char001f'
Does anything stand out that might indicate why that sender path isn't being filtered correctly?
Thanks!
Just a note that add_filter
lets you filter all messages. As for bus_add_match
, note that it should do prefix matching if either the message or match path ends with /
. The latter doesn’t in your case, but what about the former?
I very much appreciate the help! I hadn't discovered bus_add_match_action
until now, and it works as expected. I must still be misunderstanding the use of add_filter
and bus_add_match
.
I am however experiencing a SIGSEGV when attempting to call bus_remove_match_action
. I was experiencing the same behavior when trying to use remove_filter
. I'm passing it the same rule and func. Thoughts?
Just a note, are you specifying private = True
on at least the second Connection.bus_get
call? Otherwise libdbus will return a common shared connection object.
I was not, I had it set to False, but I have attempted using True with the same result unfortunately. This is occurring with only a single instance as well, regardless of private state.
Here is an example script I put together:
import sys
import os
import asyncio
import dbussy as dbus
from dbussy import \
DBUS
def get_bool_env(name, default) :
val = os.environ.get(name, "").lower()
if default :
result = not any(val.startswith(c) for c in "0nf")
else :
result = any(val.startswith(c) for c in "1yt")
#end if
return \
result
#end get_bool_env
#+
# Mainline
#-
separate_conns = get_bool_env("SEPARATE_CONNS", True)
private_conn = get_bool_env("PRIVATE_CONN", True)
sys.stderr.write("Separate connections: %s; private conn: %s\n" % (separate_conns, private_conn))
loop = asyncio.get_event_loop()
async def mainline() :
conn1 = await dbus.Connection.bus_get_async(type = DBUS.BUS_SESSION, private = False)
conn1.enable_receive_message({DBUS.MESSAGE_TYPE_SIGNAL})
if separate_conns :
conn2 = await dbus.Connection.bus_get_async(type = DBUS.BUS_SESSION, private = private_conn)
conn2.enable_receive_message({DBUS.MESSAGE_TYPE_SIGNAL})
use_conn2 = conn2
else :
conn2 = None
use_conn2 = conn1
#end if
await conn1.bus_add_match_async \
(
{
"type" : "signal",
"interface": "org.freedesktop.DBus.Properties",
"path" : "/org/bluez/hci0/dev_D4_D2_34_E3_BC_C5/service001b/char001f",
"arg0": "org.bluez.GattCharacteristic1",
}
)
await use_conn2.bus_add_match_async \
(
{
"type" : "signal",
"interface": "org.freedesktop.DBus.Properties",
"path" : "/org/bluez/hci1/dev_D4_D2_34_E3_BC_C5/service001b/char001f",
"arg0": "org.bluez.GattCharacteristic2",
}
)
to_check = []
while True :
if len(to_check) == 0 :
to_check = [["conn1", conn1]] + ([], [["conn2", conn2]])[conn2 != None]
#end if
conn_name, conn = to_check.pop()
message = await conn.receive_message_async(timeout = 0.25)
if message != None :
sys.stderr.write("%s got a message, type %d, serial = %d\n" % (conn_name, message.type, message.serial))
if message.type == DBUS.MESSAGE_TYPE_METHOD_CALL :
sys.stderr.write(" destination = %s\n" % repr(message.destination))
#end if
if message.type in (DBUS.MESSAGE_TYPE_METHOD_CALL, DBUS.MESSAGE_TYPE_SIGNAL) :
sys.stderr.write(" path = %s, interface = %s, name = %s\n" % (repr(message.path), repr(message.interface), repr(message.member)))
#end if
sys.stderr.write(" contents = %s\n" % repr(list(message.objects)))
sys.stdout.flush()
#end if
#end while
#end mainline
loop.run_until_complete(mainline())
Does that look like it reproduces your situation? By default it opens two bus connections, sets a different filter on each, then sits in a loop polling them for incoming messages. When I run that (without setting any environment variables), and send it signals with commands like
dbus-send --session --type=signal /org/bluez/hci0/dev_D4_D2_34_E3_BC_C5/service001b/char001f org.freedesktop.DBus.Properties.PropertiesChanged string:org.bluez.GattCharacteristic1
and
dbus-send --session --type=signal /org/bluez/hci1/dev_D4_D2_34_E3_BC_C5/service001b/char001f org.freedesktop.DBus.Properties.PropertiesChanged string:org.bluez.GattCharacteristic2
I always get output which indicates that each message was received on exactly the right connection.
Feel free to point out what I have missed.
Thanks so much for putting that together, I will report back tomorrow!
This certainly works as expected, I now realize that I was incorrectly using add_filter
and bus_add_match
. I am now accomplishing the same objective with bus_add_match_action
, and it works as expected, so all good there.
My only remaining issue now is that I receive a SIGSEGV when attempting to call bus_remove_match_action
. It's happening at this line. Please let me know if examining some of the values in that func would help.
This example does bus_add
/remove_match_async
, and seems to work OK for me without crashes. Is that helpful to you?
I've done some more testing and continue to get the SIGSEGV when bus_remove_match_action_async
is called when no more match actions are remaining and self.remove_filter(self._rule_action_match, None)
is called here. You can see where I am adding and removing the match actions here add#1, add#2 and remove#1, remove#2. Apologies, the code is a bit rough still. I can't see any significant difference in my use and yours.
@ldo I am no longer experiencing the seg fault after your latest changes! Thanks so much for continuing to help me. I'll go ahead and close this.
Glad to hear it. All I did was realize that an explicit PendingCall.cancel()
call could cause an exception inside the pending_done
callback. It should also cause an exception in the await_reply()
call, but that one will be passed back to the caller.
Oops, sorry, that comment applied to a different bug. I have no idea why my recent changes would have fixed your problem. :)
Oh no, the mystery remains!
@ldo This may help narrow down which change solved it.. I am not experiencing the seg fault with the current master, but I am with the current wheel on pypi.
@kent-williams Unfortunately that upload is quite a few months old now. The best way to track the difference down is with git-bisect.