fix: address PR #836 round-2 review (connect/timeout/observability)
🟡 Document why ProcrastinateTaskProducer.connect() uses `await app.open_async()` (AwaitableContext: await opens a long-lived pool, closed by drain()) and add a connect()/drain() lifecycle unit test (InMemoryConnector) asserting the pool is opened by connect and closed by drain — previously untested. 🟡 get_procrastinate_conninfo: forward connect_timeout from DATABASE_URL or default 10s so an unreachable DB can't hang worker/API startup indefinitely; warn only on other dropped query params. + tests. 🟢 INGEST_DELETE_SUCCEEDED_JOBS (default true) makes the worker's succeeded-job deletion configurable for audit retention. 🟢 Worker startup logs via logger.info (structured/OTel) instead of click.echo. 🟢 INGEST_STALLED_JOB_SECONDS (default 300) makes the crash-reclaim threshold tunable for slow embedding backends; reclaim reads it per-run. The broad `except` in _apply_ingest_queue_schema_open is kept deliberately: procrastinate wraps psycopg errors, so narrowing to psycopg.errors.* would miss the wrapped DDL-conflict and turn a benign concurrent-apply race into a failure; the presence re-check re-raises genuine errors. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
cfdef3c2c5
commit
820b98dac1
@@ -116,6 +116,30 @@ class TestProcrastinateConninfo:
|
||||
assert parsed["dbname"] == "mcp"
|
||||
assert parsed.get("sslmode") == expected_sslmode
|
||||
|
||||
def test_conninfo_connect_timeout_defaults_to_10(self, monkeypatch):
|
||||
from psycopg.conninfo import conninfo_to_dict
|
||||
|
||||
monkeypatch.setattr(
|
||||
config_module,
|
||||
"get_database_url",
|
||||
lambda: "postgresql+asyncpg://mcp:s@db/mcp",
|
||||
)
|
||||
monkeypatch.setattr(config_module, "get_database_ssl", lambda: None)
|
||||
parsed = conninfo_to_dict(config_module.get_procrastinate_conninfo())
|
||||
assert parsed["connect_timeout"] == "10"
|
||||
|
||||
def test_conninfo_honors_url_connect_timeout(self, monkeypatch):
|
||||
from psycopg.conninfo import conninfo_to_dict
|
||||
|
||||
monkeypatch.setattr(
|
||||
config_module,
|
||||
"get_database_url",
|
||||
lambda: "postgresql+asyncpg://mcp:s@db/mcp?connect_timeout=3",
|
||||
)
|
||||
monkeypatch.setattr(config_module, "get_database_ssl", lambda: None)
|
||||
parsed = conninfo_to_dict(config_module.get_procrastinate_conninfo())
|
||||
assert parsed["connect_timeout"] == "3"
|
||||
|
||||
def test_conninfo_ssl_mapping(self, monkeypatch):
|
||||
from psycopg.conninfo import conninfo_to_dict
|
||||
|
||||
|
||||
@@ -67,6 +67,22 @@ class TestProcrastinateTaskProducer:
|
||||
producer = pq.ProcrastinateTaskProducer(app)
|
||||
assert producer.clone() is producer
|
||||
|
||||
async def test_connect_opens_pool_and_drain_closes(self, app, monkeypatch):
|
||||
# connect() resolves the process-wide app; point it at our in-memory one.
|
||||
monkeypatch.setattr(pq, "get_procrastinate_app", lambda: app)
|
||||
|
||||
producer = await pq.ProcrastinateTaskProducer.connect()
|
||||
# `await app.open_async()` must actually open the connector (regression
|
||||
# guard for the await-vs-`async with` form on the long-lived pool).
|
||||
assert app.connector.states == ["open_async"]
|
||||
|
||||
# An open pool means send() works end-to-end.
|
||||
await producer.send(_task())
|
||||
assert len(app.connector.jobs) == 1
|
||||
|
||||
await producer.drain()
|
||||
assert "closed_async" in app.connector.states
|
||||
|
||||
|
||||
class TestProcessDocumentTask:
|
||||
async def test_runs_pipeline_and_closes_client(self, monkeypatch):
|
||||
|
||||
Reference in New Issue
Block a user