579dbe0c71
cron/ledger.py (e24c8499) existed so a long-running scheduler that lazily imports
notepad/incidents AFTER `hermes update` never needs new names from a module it already has
cached. The dedup deleted it and imported open_db/transaction from hermes_cli.sqlite_util at
module level; a pre-upgrade daemon has the OLD sqlite_util cached (executions imported
add_column_if_missing from it), so the first job tick after an upgrade would ImportError in
scheduler_prompt._build_job_prompt until restart.
- cron/{notepad,incidents,executions,delivery_queue}: import open_db/transaction/
add_column_if_missing and cron.jobs._ensure_cron_dir inside _connect/_transaction/
_initialize_schema. This also stops the 3.8k-line cron.jobs being pulled eagerly by
importing a store (it was lazy in cron/ledger.open_ledger).
- gateway/hosted_rooms_common, hosted_room_policy_checkpoint: same treatment; the gateway
imports hosted_rooms lazily from request handlers, so it has the same skew exposure.
- tests/cron/test_upgrade_module_skew.py: simulate the real skew (delete open_db/transaction
from the cached sqlite_util, then import each store). The previous repoint deleted names
from cron.executions, which notepad/incidents do not import from, so it passed regardless.
Sabotage: a module-level `from hermes_cli.sqlite_util import open_db` in notepad fails it
with "cannot import name 'open_db'".
43 lines
1.3 KiB
Python
43 lines
1.3 KiB
Python
"""Cron imports remain usable when a daemon spans an on-disk upgrade.
|
|
|
|
A long-running scheduler already has ``hermes_cli.sqlite_util`` and ``cron.jobs`` cached from
|
|
BEFORE the upgrade; the first lazy import of a cron store afterwards must not need names those
|
|
stale modules lack (``scheduler_prompt._build_job_prompt`` imports ``cron.notepad`` unguarded, so
|
|
an ``ImportError`` there fails every job tick until restart).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
_SKEW_SCRIPT = """
|
|
import sys, types
|
|
import hermes_cli.sqlite_util as sqlite_util
|
|
import cron.jobs as jobs
|
|
|
|
# The pre-upgrade sqlite_util only had add_column_if_missing / write_txn.
|
|
for name in ("open_db", "transaction"):
|
|
delattr(sqlite_util, name)
|
|
sys.modules.pop("cron.{store}", None)
|
|
|
|
import cron.{store}
|
|
"""
|
|
|
|
|
|
@pytest.mark.parametrize("store", ["notepad", "incidents", "executions", "delivery_queue"])
|
|
def test_lazy_cron_stores_import_against_pre_upgrade_sqlite_util(store):
|
|
repo_root = Path(__file__).resolve().parents[2]
|
|
result = subprocess.run(
|
|
[sys.executable, "-c", _SKEW_SCRIPT.format(store=store)],
|
|
cwd=repo_root,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
|
|
assert result.returncode == 0, result.stderr
|