Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to use signals #7

Open
awarecan opened this issue Jun 14, 2018 · 21 comments
Open

How to use signals #7

awarecan opened this issue Jun 14, 2018 · 21 comments

Comments

@awarecan
Copy link

It looks like when you processing interface, you ignored signal at all.
https://github.com/facebookincubator/pystemd/blob/628655ae61dff66b39feed52c89e0bfda00c4f38/pystemd/base.py#L199-L219

So, is there any support for signal now? Any plan to add the support?
I am looking for use your library, but I would not use it without support of signal that is very important for my project (current base on pydbus, but I would prefer a library doesn't depends on python-gi)

@aleivag
Copy link
Collaborator

aleivag commented Jun 14, 2018

we have no plan to implement signals, because so far, we have not need it, but if you have a use case, and you would like to share it with us (tell us why you need it, and how you will use them), i'll be happy to look into it and try to support for it.

(also patches are always welcome) :D

@awarecan
Copy link
Author

My application is super simple, just a systemd services state monitor. User can set which services they want to monitor, and get "real time" notification when the service state changed.

@aleivag
Copy link
Collaborator

aleivag commented Jun 15, 2018

@awarecan cool, so i think you can get that now with pystemd in the current state, i actually do this in pystemd.run if you specify the option wait=True, what i do on that function, is start a transient unit, and then open a bus and subscribe to PropertiesChange dbusevent on that unit

you can check it out in:

https://github.com/facebookincubator/pystemd/blob/master/pystemd/run.py#L240-L244

but a quick example (that probably i should eventually add to the example folder could be):

Assuming that a unit name my_custom_sleep.service exists you can

import select

from pystemd.systemd1 import Unit
from pystemd.dbuslib import DBus
from pystemd.DBus import Manager as DBusManager

unit = Unit("my_custom_sleep.service")
mstr = (
    (
        "type='signal',"
        "sender='org.freedesktop.systemd1',"
        "path='{}',"
        "interface='org.freedesktop.DBus.Properties',"
        "member='PropertiesChanged'"
    )
    .format(unit.path.decode())
    .encode()
)

with DBus() as monbus, DBusManager(bus=monbus) as man:
    man.Monitoring.BecomeMonitor([mstr], 0)
    fd = monbus.get_fd()
    while True:
        select.select([fd], [], [])
        m = monbus.process()
        if m.is_empty():
            continue

        m.process_reply(False)
        print(m.body)
        if m.get_path() == unit.path:
            if m.body[1].get(b"SubState") in (b"exited", b"failed", b"dead"):
                break

and this will dump this lines, each time that one of the properties that emit change... well change...

[b'org.freedesktop.systemd1.Service', {b'MainPID': 3776, b'ControlPID': 0, b'StatusText': b'', b'StatusErrno': 0, b'Result': b'success', b'USBFunctionDescriptors': b'', b'USBFunctionStrings': b'', b'UID': 4294967295, b'GID': 4294967295, b'NRestarts': 0, b'ExecMainStartTimestamp': 1529038632232687, b'ExecMainStartTimestampMonotonic': 2466232687, b'ExecMainExitTimestamp': 0, b'ExecMainExitTimestampMonotonic': 0, b'ExecMainPID': 3776, b'ExecMainCode': 0, b'ExecMainStatus': 0}, [b'ExecStartPre', b'ExecStart', b'ExecStartPost', b'ExecReload', b'ExecStop', b'ExecStopPost']]


[b'org.freedesktop.systemd1.Unit', {b'ActiveState': b'active', b'SubState': b'running', b'StateChangeTimestamp': 1529038632232879, b'StateChangeTimestampMonotonic': 2466232879, b'InactiveExitTimestamp': 1529038632232879, b'InactiveExitTimestampMonotonic': 2466232879, b'ActiveEnterTimestamp': 1529038632232879, b'ActiveEnterTimestampMonotonic': 2466232879, b'ActiveExitTimestamp': 0, b'ActiveExitTimestampMonotonic': 0, b'InactiveEnterTimestamp': 0, b'InactiveEnterTimestampMonotonic': 0, b'Job': (0, b'/'), b'ConditionResult': True, b'AssertResult': True, b'ConditionTimestamp': 1529038632228522, b'ConditionTimestampMonotonic': 2466228523, b'AssertTimestamp': 1529038632228524, b'AssertTimestampMonotonic': 2466228524}, []]

[b'org.freedesktop.systemd1.Service', {b'MainPID': 0, b'ControlPID': 0, b'StatusText': b'', b'StatusErrno': 0, b'Result': b'success', b'USBFunctionDescriptors': b'', b'USBFunctionStrings': b'', b'UID': 4294967295, b'GID': 4294967295, b'NRestarts': 0, b'ExecMainStartTimestamp': 1529038632232687, b'ExecMainStartTimestampMonotonic': 2466232687, b'ExecMainExitTimestamp': 1529038692242131, b'ExecMainExitTimestampMonotonic': 2526242131, b'ExecMainPID': 3776, b'ExecMainCode': 1, b'ExecMainStatus': 0}, [b'ExecStartPre', b'ExecStart', b'ExecStartPost', b'ExecReload', b'ExecStop', b'ExecStopPost']]

[b'org.freedesktop.systemd1.Unit', {b'ActiveState': b'inactive', b'SubState': b'dead', b'StateChangeTimestamp': 1529038692242636, b'StateChangeTimestampMonotonic': 2526242636, b'InactiveExitTimestamp': 1529038632232879, b'InactiveExitTimestampMonotonic': 2466232879, b'ActiveEnterTimestamp': 1529038632232879, b'ActiveEnterTimestampMonotonic': 2466232879, b'ActiveExitTimestamp': 1529038692242636, b'ActiveExitTimestampMonotonic': 2526242636, b'InactiveEnterTimestamp': 1529038692242636, b'InactiveEnterTimestampMonotonic': 2526242636, b'Job': (0, b'/'), b'ConditionResult': True, b'AssertResult': True, b'ConditionTimestamp': 1529038632228522, b'ConditionTimestampMonotonic': 2466228523, b'AssertTimestamp': 1529038632228524, b'AssertTimestampMonotonic': 2466228524}, []]

the code "looks" complex, but its actually quite easy to follow... i think this accomplish what you need, right?

@aleivag
Copy link
Collaborator

aleivag commented Jun 15, 2018

i forgot, but i actually "ported" busctl monitor to pystemd in this example https://github.com/facebookincubator/pystemd/blob/master/examples/monitor.py its rudimentary and only use the DBus class, you should probably stick with the example i just gave you in the previous comment, but for educational purposes i mention this here.

@awarecan
Copy link
Author

@aleivag Thank you very much for the sample code. I am just following your code and hit that error. Do I have to be root? I don't need that for signal listen in pydbus lib.

pystemd.dbusexc.DBusAccessDeniedError: [err -13]: b'Rejected send message, 1 matched rules; type="method_call", sender=":1.576" (uid=1000 pid=25748 comm="/home/jason/ha/home-assistant/venv/bin/python /hom" label="unconfined") interface="org.freedesktop.DBus.Monitoring" member="BecomeMonitor" error name="(unset)" requested_reply="0" destination="org.freedesktop.DBus" (bus)'

@awarecan
Copy link
Author

awarecan commented Jun 15, 2018

Googling told me that: ref

Unfortunately, the default D-Bus policy (at least on Ubuntu) prevents most of the messages (except signals) that goes through system bus from being viewable by dbus-monitor

To change that we need to set a global policy to be able to eavesdrop anything after the individual /etc/dbus-1/system.d/*.conf files applied their restrictions,

@aleivag
Copy link
Collaborator

aleivag commented Jun 15, 2018

@awarecan yes you have to be root, i dont think you can subscribe to the system event bus with a regular user (but i may be wrong). if your service belong to the user bus (e.g started with systemd-run --user) you can pass True to DBus class constructor to ask for a user bus.

pystemd rely heavily in systemd/sd_bus.h so all integration with dbus are done by systemd internal libraries.

When run as root i can see mayor events, maybe you can share how you do it with pybuslib and i check if anything like that is possible with pystemd.

best regards!

@aleivag
Copy link
Collaborator

aleivag commented Jun 15, 2018

cool, thanks @awarecan i will look at it in the afternoon and see if i can came up with ideas

thank you for sharing this!!

@aleivag
Copy link
Collaborator

aleivag commented Jun 18, 2018

hey, so the good news is that it can be done 🎉 ... the bad-ish news is that i need to expose sd_bus_match_signal (https://github.com/systemd/systemd/blob/master/src/systemd/sd-bus.h#L362 ) from systemd/sd-bus to pystemd...

i was planing to do it either way, the hard part about that is that its implemented as callbacks, and python callbacks in c land are not as trivial as they sound basically because c has no concept of exception, so a python exception can became a c memory leak.

once i finish coding this, the result code may look like

import select
from pystemd.dbuslib import DBus
from pystemd.systemd1 import Unit

class STATE:
    EXIT = False

def process(msg, err=None):
    print(msg.body)
    if msg.body[1].get(b"SubState") in (b"exited", b"failed", b"dead"):
        STATE.EXIT = True

with DBus(False) as dbus:
    myunit = Unit('mysleep.service')
    dbus.match_signal(
        myunit.destination,
        myunit.path,
        b"org.freedesktop.DBus.Properties",
        b"PropertiesChanged",
        process, #  <-- your function
        None, #  maybe pass custom python objects as userdata?
    )

    fd = dbus.get_fd()
    while not STATE.EXIT:
        select.select([fd], [], []) # wait for message
        dbus.process() # execute the whole message

please notice that that is just an idea, it may end up looking a little bit different. will post here when the code is merged

@aleivag
Copy link
Collaborator

aleivag commented Jun 20, 2018

hey @awarecan thanks for the patience... the code has been merged, you can test it... a working example can be found in https://github.com/facebookincubator/pystemd/blob/master/examples/monitor_from_signal.py

i test this, and i can see properties changes on units from the system bus without been root...

Hope this helps... (please notice that the example has detail implementation, that may or may not fit your project)... :D

@aleivag
Copy link
Collaborator

aleivag commented Jun 20, 2018

Heads up! by implementing it as sd_bus_match_signal i made the code only run on systemd v237 and above.

@awarecan
Copy link
Author

I am following your example, had met several bumps

  1. The callback parameter only accept function, not method.

  2. Several different error I got

Assertion 'm->n_ref > 0' failed at ../src/libsystemd/sd-bus/bus-message.c:934, function sd_bus_message_unref(). Aborting.
Aborted (core dumped)
corrupted double-linked list
Aborted (core dumped)
  1. How to monitor several Unit at same time? Do I need several loop?

@awarecan
Copy link
Author

awarecan commented Jun 20, 2018

For Assertion 'm->n_ref > 0' failed issue, I already reduced my code to very simple level, but it still happens when I stop or start service. Meanwhile the try/catch didn't work, the whole program crashed

        def process_message(message, error=None, userdata=None):
            pass

        # some other codes

            try:
                with DBus() as bus:
                    bus.match_signal(
                        b'org.freedesktop.systemd1',
                        b'/org/freedesktop/systemd1/unit/bluetooth_2eservice',
                        b'org.freedesktop.DBus.Properties',
                        b'PropertiesChanged',
                        process_message,
                    )
                    fd = bus.get_fd()
                    while True:
                        select.select([fd], [], [])
                        bus.process()
            except Exception as error:
                _LOGGER.error(error)

My systemd is 237 by the way.

> systemd --version
systemd 237
+PAM +AUDIT +SELINUX +IMA +APPARMOR +SMACK +SYSVINIT +UTMP +LIBCRYPTSETUP +GCRYPT +GNUTLS +ACL +XZ +LZ4 +SECCOMP +BLKID +ELFUTILS +KMOD -IDN2 +IDN -PCRE2 default-hierarchy=hybrid

@aleivag
Copy link
Collaborator

aleivag commented Jun 20, 2018

yeah segfaults are not catchable... that is super weird, let me try to reproduce...

@aleivag
Copy link
Collaborator

aleivag commented Jun 20, 2018

so i was not able to reproduce, but i'm running v238, will try get a hold of a 237 machine to test, so far the only theory i have is that the dealloc methoc is breaking you.
https://github.com/facebookincubator/pystemd/blob/master/pystemd/dbuslib.pyx#L77-L78

you can even try to remove it yourself, just keep in mind that that is the only thing that is preventing a big memory leak on each new message

anyway i will do more digging one a have a v237 in my hands!

@awarecan
Copy link
Author

Shall we follow the principle "whoever alloc the memory should dealloc it" (Don't know if there is a fancy word for this) here?

https://github.com/facebookincubator/pystemd/blob/ef980e702053555a3f1577be612c34a54630fda2/pystemd/dbuslib.pyx#L776-L782

m was alloc-ed in libsystemd, so that we should not dealloc it IMO.

@awarecan
Copy link
Author

awarecan commented Jun 21, 2018

Comment out

https://github.com/facebookincubator/pystemd/blob/ef980e702053555a3f1577be612c34a54630fda2/pystemd/dbuslib.pyx#L77-L78

Everything worked

@aleivag
Copy link
Collaborator

aleivag commented Jun 21, 2018

i'm both happy (that commenting out makes it work) and sad (i have to make this work on systemd <237 and make a clever de-allocation).

The reason why we did the deallocate, is because normally "we allocated" the msg struct. in the callback case that is not the case anymore, since we have no real way of allocating the struct and tehn hand it over to systemd... i have some ideas, but will sleep on them...

thanks for helping debug!

@aleivag
Copy link
Collaborator

aleivag commented Jun 21, 2018

hi @awarecan 👍
so i was finally able to reliable reproduce your issue on systemd v237. the issue happens because you dont call msg.process_reply(True). so this happen when your callback is

def process_message(message, error=None, userdata=None):
            pass

and not when your callback is

def process_message(message, error=None, userdata=None):
            message.process_reply(True)

when i implemented this, my logic was to give the user the chance to call msg.process_reply(True) or msg.process_reply(False). the diference is that True will also process the headers. i think that i was wrong and i should give the callback always a processed reply with headers . i'll put a change to do that... will probably do it tomorrow morning tho .

with that change you will not see the problem, and the we return the ownership of the msg to the signal_callback .

note: to reproduce i got a debian strech and isntalled systemd with the backports

Thanks!

@awarecan
Copy link
Author

It sounds right. I don't have access to my work now, but I called msg.process_reply(False) actually.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants