mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
deliver() and _persist_and_deliver() ran inside the caller's open transaction: the notification row was flushed but not committed, yet NOTIFICATION_SENT was published to the Redis bus immediately. A commit failure (DB hiccup, constraint, asyncpg error) rolled the row back while connected WebSocket clients had already received a push for an id that no longer existed — a phantom notification (notify_get -> NotFoundError). Added a deferred-publish (transactional-outbox) helper: defer_bus_publish enqueues the event on session.info and registers one-shot after_commit / after_rollback listeners on session.sync_session the first time it is called for that session. On commit, the after_commit listener schedules the async drain via asyncio.create_task on the running loop (the listener fires synchronously inside await AsyncSession.commit, so the loop is active); the task handles are stashed on the session so callers/tests can await them. On rollback, after_rollback drops the pending queue — a rolled-back txn emits nothing. deliver() now builds the per-recipient events up front (data materialized to strings, so deferral is safe even if the ORM object later expires) and defers each; the delivered_at DB marker stays in-tx (rolls back with the row). The bus block stays best-effort (try/except + log) so a bus-init failure never propagates or rolls back the notification row — matching the prior inline semantics. This fixes every deliver/_persist_and_deliver caller at once (the two cited in F107 plus the orchestrator + task.py deliver sites), since they all commit the session afterward (the deferred publish fires on that commit; the row is durable by the time the event goes out).