Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions src/dron/dron.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,15 @@ def unit(self) -> str:
# TODO ugh. not sure how to verify them?


def _delete_order(a: Delete) -> int:
# systemd warns if we stop/disable a service while its triggering timer is still active.
if a.unit.endswith('.timer'):
return 0
if a.unit.endswith('.service'):
return 1
return 2


def compute_plan(*, current: State, pending: State) -> Plan:
# eh, I feel like i'm reinventing something already existing here...
currentd = OrderedDict((x.unit_file, unwrap(x.body)) for x in current)
Expand Down Expand Up @@ -239,14 +248,15 @@ def is_always_running(unit_path: Path) -> bool:
logger.info(f'updating : {len(updates)}')
logger.info(f'adding : {len(adds)}')

for a in deletes:
deletes_ordered = sorted(deletes, key=_delete_order)

for a in deletes_ordered:
if IS_SYSTEMD:
# TODO stop timer first?
check_call(_systemctl('stop', a.unit))
check_call(_systemctl('disable', a.unit))
else:
launchd.launchctl_unload(unit=Path(a.unit).stem)
for a in deletes:
for a in deletes_ordered:
(DRON_UNITS_DIR / a.unit).unlink()

for u, diff in updates:
Expand Down
5 changes: 5 additions & 0 deletions src/dron/monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@ def update_ui(self) -> None:
# hmm seems like DataTable is a bit dumb and even if value is the same, it does costly UI updates...
# this is quite noticeable optimization
if curr_value != new_value:
# For ~200 rows, update_width=True costs a few extra ms per refresh.
# Keeping it for now because status/next/schedule/command widths can grow, but if refresh
# gets sluggish this is a good first knob to turn off for ordinary cell updates.
self.update_cell(row_key=key, column_key=col, value=new_value, update_width=True)

def sort_key(row: list[str]):
Expand All @@ -161,6 +164,8 @@ def sort_key(row: list[str]):
# TODO hmm kinda annoying, doesn't look like it preserves cursor position
# if the item pops on top of the list when a service is running?
# but I guess not a huge deal now
# Sorting 200-ish rows is cheap compared with the DataTable update/render itself
# (rough local benchmark: about 1 ms extra), so keep this simple unless row count grows a lot.
self.sort(key=sort_key)

def show_details_in_pager(self, unit_name: RowKey) -> None:
Expand Down
16 changes: 7 additions & 9 deletions src/dron/systemd.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import importlib
import json
import os
import re
Expand All @@ -12,7 +13,7 @@
from pathlib import Path
from subprocess import PIPE, Popen, run
from tempfile import TemporaryDirectory
from typing import Any
from typing import Any, cast
from zoneinfo import ZoneInfo

from .api import (
Expand Down Expand Up @@ -235,21 +236,18 @@ def _sd(s: str) -> str:

class BusManager:
def __init__(self) -> None:
# unused-ignore because on macos there is no dbus (but this code is still running mypy on CI)
from dbus import ( # type: ignore[import-untyped,import-not-found,unused-ignore] # ty: ignore[unresolved-import]
Interface,
SessionBus,
)
# Keep this dynamic because dbus-python is missing on macos; and it's very difficult to convince type checkers to handle that
dbus = importlib.import_module('dbus')

self.Interface = Interface # meh
self.Interface = cast(Any, dbus).Interface # meh

# NOTE: private=True is important here! Otherwise SessionBus() returns a shared connection.
# If that connection gets into a broken state (e.g. timeouts), it will persist across BusManager instantiations,
# and result in DBusException: org.freedesktop.DBus.Error.NoReply
# Note that we instantiate BusManager every time we get systemd state, but seems like there is no need to cleanup/close bus, it doesn't seem to leak fds.
self.bus = SessionBus(private=True) # note: SystemBus is for system-wide services
self.bus = cast(Any, dbus).SessionBus(private=True) # note: SystemBus is for system-wide services
systemd = self.bus.get_object(_sd(''), '/org/freedesktop/systemd1')
self.manager = Interface(systemd, dbus_interface=_sd('.Manager'))
self.manager = self.Interface(systemd, dbus_interface=_sd('.Manager'))

def properties(self, u: Unit):
service_unit = self.manager.GetUnit(u)
Expand Down
9 changes: 8 additions & 1 deletion src/dron/tests/test_dron.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import pytest

from ..common import UnitState
from ..dron import Add, Delete, Update, compute_plan, do_lint, load_jobs
from ..dron import Add, Delete, Update, _delete_order, compute_plan, do_lint, load_jobs


@pytest.fixture
Expand Down Expand Up @@ -134,6 +134,13 @@ def unit(name: str, body: str) -> UnitState:
]


def test_delete_order_deletes_timers_before_services() -> None:
timer = Delete(unit_file=Path('/units/example.timer'))
service = Delete(unit_file=Path('/units/example.service'))

assert sorted([service, timer], key=_delete_order) == [timer, service]


def test_jobs_auto_naming(tmp_pythonpath: Path) -> None:
tpath = Path(tmp_pythonpath) / 'test_drontab.py'
tpath.write_text(
Expand Down
Loading