func (c *Conn) sendPropertiesUpdate(unitPath dbus.ObjectPath, changedProps map[string]dbus.Variant) {
	c.propertiesSubscriber.Lock()
	defer c.propertiesSubscriber.Unlock()
	if c.propertiesSubscriber.updateCh == nil {
		return
	}
	update := &PropertiesUpdate{unitName(unitPath), changedProps}
	select {
	case c.propertiesSubscriber.updateCh <- update:
	default:
		msg := "update channel is full"
		select {
		case c.propertiesSubscriber.errCh <- errors.New(msg):
		default:
			log.Printf("full error channel while reporting: %s\n", msg)
		}
		return
	}
}
we don't need to worry about shouldIgnore() here because
sendPropertiesUpdate doesn't call GetProperties()
package dbus
import (
	"errors"
	"log"
	"time"
	"github.com/godbus/dbus/v5"
)
const (
	cleanIgnoreInterval = int64(10 * time.Second)
	ignoreInterval      = int64(30 * time.Millisecond)
)
func (c *Conn) Subscribe() error {
	c.sigconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
		"type='signal',interface='org.freedesktop.systemd1.Manager',member='UnitNew'")
	c.sigconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
		"type='signal',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'")
	return c.sigobj.Call("org.freedesktop.systemd1.Manager.Subscribe", 0).Store()
}
func (c *Conn) Unsubscribe() error {
	return c.sigobj.Call("org.freedesktop.systemd1.Manager.Unsubscribe", 0).Store()
}
func (c *Conn) dispatch() {
	ch := make(chan *dbus.Signal, signalBuffer)
	c.sigconn.Signal(ch)
	go func() {
		for {
			signal, ok := <-ch
			if !ok {
				return
			}
			if signal.Name == "org.freedesktop.systemd1.Manager.JobRemoved" {
				c.jobComplete(signal)
			}
			if c.subStateSubscriber.updateCh == nil &&
				c.propertiesSubscriber.updateCh == nil {
				continue
			}
			var unitPath dbus.ObjectPath
			switch signal.Name {
			case "org.freedesktop.systemd1.Manager.JobRemoved":
				unitName := signal.Body[2].(string)
				c.sysobj.Call("org.freedesktop.systemd1.Manager.GetUnit", 0, unitName).Store(&unitPath)
			case "org.freedesktop.systemd1.Manager.UnitNew":
				unitPath = signal.Body[1].(dbus.ObjectPath)
			case "org.freedesktop.DBus.Properties.PropertiesChanged":
				if signal.Body[0].(string) == "org.freedesktop.systemd1.Unit" {
					unitPath = signal.Path
					if len(signal.Body) >= 2 {
						if changed, ok := signal.Body[1].(map[string]dbus.Variant); ok {
							c.sendPropertiesUpdate(unitPath, changed)
						}
					}
				}
			}
			if unitPath == dbus.ObjectPath("") {
				continue
			}
			c.sendSubStateUpdate(unitPath)
		}
	}()
}
func (c *Conn) SubscribeUnits(interval time.Duration) (<-chan map[string]*UnitStatus, <-chan error) {
	return c.SubscribeUnitsCustom(interval, 0, func(u1, u2 *UnitStatus) bool { return *u1 != *u2 }, nil)
}
func (c *Conn) SubscribeUnitsCustom(interval time.Duration, buffer int, isChanged func(*UnitStatus, *UnitStatus) bool, filterUnit func(string) bool) (<-chan map[string]*UnitStatus, <-chan error) {
	old := make(map[string]*UnitStatus)
	statusChan := make(chan map[string]*UnitStatus, buffer)
	errChan := make(chan error, buffer)
	go func() {
		for {
			timerChan := time.After(interval)
			units, err := c.ListUnits()
			if err == nil {
				cur := make(map[string]*UnitStatus)
				for i := range units {
					if filterUnit != nil && filterUnit(units[i].Name) {
						continue
					}
					cur[units[i].Name] = &units[i]
				}
				changed := make(map[string]*UnitStatus)
				for n, u := range cur {
					if oldU, ok := old[n]; !ok || isChanged(oldU, u) {
						changed[n] = u
					}
					delete(old, n)
				}
				for oldN := range old {
					changed[oldN] = nil
				}
				old = cur
				if len(changed) != 0 {
					statusChan <- changed
				}
			} else {
				errChan <- err
			}
			<-timerChan
		}
	}()
	return statusChan, errChan
}
type SubStateUpdate struct {
	UnitName string
	SubState string
}
func (c *Conn) SetSubStateSubscriber(updateCh chan<- *SubStateUpdate, errCh chan<- error) {
	if c == nil {
		msg := "nil receiver"
		select {
		case errCh <- errors.New(msg):
		default:
			log.Printf("full error channel while reporting: %s\n", msg)
		}
		return
	}
	c.subStateSubscriber.Lock()
	defer c.subStateSubscriber.Unlock()
	c.subStateSubscriber.updateCh = updateCh
	c.subStateSubscriber.errCh = errCh
}
func (c *Conn) sendSubStateUpdate(unitPath dbus.ObjectPath) {
	c.subStateSubscriber.Lock()
	defer c.subStateSubscriber.Unlock()
	if c.subStateSubscriber.updateCh == nil {
		return
	}
	isIgnored := c.shouldIgnore(unitPath)
	defer c.cleanIgnore()
	if isIgnored {
		return
	}
	info, err := c.GetUnitPathProperties(unitPath)
	if err != nil {
		select {
		case c.subStateSubscriber.errCh <- err:
		default:
			log.Printf("full error channel while reporting: %s\n", err)
		}
		return
	}
	defer c.updateIgnore(unitPath, info)
	name, ok := info["Id"].(string)
	if !ok {
		msg := "failed to cast info.Id"
		select {
		case c.subStateSubscriber.errCh <- errors.New(msg):
		default:
			log.Printf("full error channel while reporting: %s\n", err)
		}
		return
	}
	substate, ok := info["SubState"].(string)
	if !ok {
		msg := "failed to cast info.SubState"
		select {
		case c.subStateSubscriber.errCh <- errors.New(msg):
		default:
			log.Printf("full error channel while reporting: %s\n", msg)
		}
		return
	}
	update := &SubStateUpdate{name, substate}
	select {
	case c.subStateSubscriber.updateCh <- update:
	default:
		msg := "update channel is full"
		select {
		case c.subStateSubscriber.errCh <- errors.New(msg):
		default:
			log.Printf("full error channel while reporting: %s\n", msg)
		}
		return
	}
}
func (c *Conn) shouldIgnore(path dbus.ObjectPath) bool {
	t, ok := c.subStateSubscriber.ignore[path]
	return ok && t >= time.Now().UnixNano()
}
func (c *Conn) updateIgnore(path dbus.ObjectPath, info map[string]interface{}) {
	loadState, ok := info["LoadState"].(string)
	if !ok {
		return
	}
	if loadState == "not-found" {
		c.subStateSubscriber.ignore[path] = time.Now().UnixNano() + ignoreInterval
	}
}
func (c *Conn) cleanIgnore() {
	now := time.Now().UnixNano()
	if c.subStateSubscriber.cleanIgnore < now {
		c.subStateSubscriber.cleanIgnore = now + cleanIgnoreInterval
		for p, t := range c.subStateSubscriber.ignore {
			if t < now {
				delete(c.subStateSubscriber.ignore, p)
			}
		}
	}
}
type PropertiesUpdate struct {
	UnitName string
	Changed  map[string]dbus.Variant
}
func (c *Conn) SetPropertiesSubscriber(updateCh chan<- *PropertiesUpdate, errCh chan<- error) {
	c.propertiesSubscriber.Lock()
	defer c.propertiesSubscriber.Unlock()
	c.propertiesSubscriber.updateCh = updateCh
	c.propertiesSubscriber.errCh = errCh
}