175 lines
6.8 KiB
Python
175 lines
6.8 KiB
Python
"""Tests for RC2: pre-publication lease refresh in publish_compression_child.
|
|
|
|
When the lease refresher stopped due to transient DB failures, the final
|
|
pre-publication refresh inside the same transaction gives one last chance
|
|
to extend the lease before the expiry check.
|
|
"""
|
|
import sqlite3
|
|
import threading
|
|
import time
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
from hermes_state import SessionDB, CompressionSessionBusyError
|
|
|
|
|
|
def _setup_db(tmp_path):
|
|
db = SessionDB(tmp_path / "state.db")
|
|
return db
|
|
|
|
|
|
def _seed_lock(conn, session_id, holder, expired=False):
|
|
now = time.time()
|
|
conn.execute(
|
|
"INSERT INTO compression_locks (session_id, holder, acquired_at, expires_at) VALUES (?, ?, ?, ?)",
|
|
(session_id, holder, now, (now - 10.0) if expired else (now + 300.0)),
|
|
)
|
|
|
|
|
|
class TestLeaseRefreshBeforePublish:
|
|
|
|
def test_refresher_stopped_final_refresh_succeeds(self, tmp_path):
|
|
db = _setup_db(tmp_path)
|
|
db.create_session("parent-1", source="test")
|
|
_seed_lock(db._conn, "parent-1", "holder-1", expired=True)
|
|
|
|
with patch.object(db, "_execute_write", side_effect=lambda fn: fn(db._conn)):
|
|
db.publish_compression_child(
|
|
parent_session_id="parent-1",
|
|
child_session_id="child-1",
|
|
source="test",
|
|
messages=[{"role": "user", "content": "hello"}],
|
|
compression_lock_holder="holder-1",
|
|
require_compression_lease=True,
|
|
require_lease_refresh=True,
|
|
lease_ttl_seconds=300.0,
|
|
)
|
|
|
|
lock = db._conn.execute(
|
|
"SELECT expires_at FROM compression_locks WHERE session_id = ?",
|
|
("parent-1",),
|
|
).fetchone()
|
|
assert lock is not None
|
|
assert lock[0] > time.time()
|
|
|
|
parent = db._conn.execute(
|
|
"SELECT ended_at FROM sessions WHERE id = ?",
|
|
("parent-1",),
|
|
).fetchone()
|
|
assert parent is not None
|
|
assert parent[0] is not None
|
|
|
|
def test_refresher_stopped_final_refresh_fails_wrong_holder(self, tmp_path):
|
|
db = _setup_db(tmp_path)
|
|
_seed_lock(db._conn, "parent-1", "other-holder", expired=True)
|
|
|
|
with patch.object(db, "_execute_write", side_effect=lambda fn: fn(db._conn)):
|
|
with pytest.raises(CompressionSessionBusyError, match="lease lost"):
|
|
db.publish_compression_child(
|
|
parent_session_id="parent-1",
|
|
child_session_id="child-1",
|
|
source="test",
|
|
messages=[{"role": "user", "content": "hello"}],
|
|
compression_lock_holder="holder-1",
|
|
require_compression_lease=True,
|
|
require_lease_refresh=True,
|
|
lease_ttl_seconds=300.0,
|
|
)
|
|
|
|
def test_refresher_healthy_no_duplicate_behavior(self, tmp_path):
|
|
db = _setup_db(tmp_path)
|
|
db.create_session("parent-1", source="test")
|
|
now = time.time()
|
|
future = now + 300.0
|
|
conn = db._conn
|
|
conn.execute(
|
|
"INSERT INTO compression_locks (session_id, holder, acquired_at, expires_at) VALUES (?, ?, ?, ?)",
|
|
("parent-1", "holder-1", now, future),
|
|
)
|
|
|
|
with patch.object(db, "_execute_write", side_effect=lambda fn: fn(db._conn)):
|
|
db.publish_compression_child(
|
|
parent_session_id="parent-1",
|
|
child_session_id="child-1",
|
|
source="test",
|
|
messages=[{"role": "user", "content": "hello"}],
|
|
compression_lock_holder="holder-1",
|
|
require_compression_lease=True,
|
|
require_lease_refresh=True,
|
|
lease_ttl_seconds=300.0,
|
|
)
|
|
|
|
lock = conn.execute(
|
|
"SELECT expires_at FROM compression_locks WHERE session_id = ?",
|
|
("parent-1",),
|
|
).fetchone()
|
|
assert lock is not None
|
|
assert lock[0] >= future
|
|
|
|
def test_stale_holder_cannot_refresh_and_publish(self, tmp_path):
|
|
db = _setup_db(tmp_path)
|
|
_seed_lock(db._conn, "parent-1", "new-holder", expired=False)
|
|
|
|
with patch.object(db, "_execute_write", side_effect=lambda fn: fn(db._conn)):
|
|
with pytest.raises(CompressionSessionBusyError, match="lease lost"):
|
|
db.publish_compression_child(
|
|
parent_session_id="parent-1",
|
|
child_session_id="child-1",
|
|
source="test",
|
|
messages=[{"role": "user", "content": "hello"}],
|
|
compression_lock_holder="old-holder",
|
|
require_compression_lease=True,
|
|
require_lease_refresh=True,
|
|
lease_ttl_seconds=300.0,
|
|
)
|
|
|
|
def test_no_refresh_when_require_lease_refresh_false(self, tmp_path):
|
|
db = _setup_db(tmp_path)
|
|
_seed_lock(db._conn, "parent-1", "holder-1", expired=True)
|
|
|
|
with patch.object(db, "_execute_write", side_effect=lambda fn: fn(db._conn)):
|
|
with pytest.raises(CompressionSessionBusyError, match="lease lost"):
|
|
db.publish_compression_child(
|
|
parent_session_id="parent-1",
|
|
child_session_id="child-1",
|
|
source="test",
|
|
messages=[{"role": "user", "content": "hello"}],
|
|
compression_lock_holder="holder-1",
|
|
require_compression_lease=True,
|
|
require_lease_refresh=False,
|
|
lease_ttl_seconds=300.0,
|
|
)
|
|
|
|
def test_refresh_and_lease_check_are_atomic(self, tmp_path):
|
|
db = _setup_db(tmp_path)
|
|
db.create_session("parent-1", source="test")
|
|
_seed_lock(db._conn, "parent-1", "holder-1", expired=True)
|
|
|
|
real_execute_write = SessionDB._execute_write
|
|
|
|
def intercepted_execute_write(self, fn, patience_s=None):
|
|
original_fn = fn
|
|
def wrapper(conn):
|
|
result = original_fn(conn)
|
|
lock = conn.execute(
|
|
"SELECT expires_at FROM compression_locks WHERE session_id = ?",
|
|
("parent-1",),
|
|
).fetchone()
|
|
assert lock is not None
|
|
assert lock[0] > time.time()
|
|
return result
|
|
return real_execute_write(self, wrapper, patience_s)
|
|
|
|
with patch.object(SessionDB, "_execute_write", intercepted_execute_write):
|
|
db.publish_compression_child(
|
|
parent_session_id="parent-1",
|
|
child_session_id="child-1",
|
|
source="test",
|
|
messages=[{"role": "user", "content": "hello"}],
|
|
compression_lock_holder="holder-1",
|
|
require_compression_lease=True,
|
|
require_lease_refresh=True,
|
|
lease_ttl_seconds=300.0,
|
|
)
|