queue_position.py 1.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445
  1. """The queue's position sequence.
  2. Positions are one sequence across every pending item, not one per printer
  3. (#3200). The queue page lists and reorders pending items as a single list, and
  4. the scheduler dispatches in that order, so a pinned job and an "Any <model>" job
  5. compete for a printer by position. A per-printer sequence gave each lane its
  6. own 1, 2, 3 and made a job added last land in the middle of the list.
  7. """
  8. from sqlalchemy import func, select, text
  9. from sqlalchemy.ext.asyncio import AsyncSession
  10. from backend.app.models.print_queue import PrintQueueItem
  11. # Advisory lock key for the shared sequence, in the 1625 namespace. The key
  12. # used to be the printer id, back when every printer had a sequence of its own.
  13. QUEUE_POSITION_LOCK_KEY = 0
  14. async def lock_queue_positions(db: AsyncSession) -> None:
  15. """Serialize inserts that read MAX(position) (#1625-followup).
  16. Two concurrent inserts into an empty queue would otherwise both read
  17. MAX(position) as 0 and land on position 1. The lock is transaction-scoped
  18. and released at commit/rollback. SQLite serializes writes implicitly and
  19. needs no equivalent.
  20. The dialect is read from the session binding, not the ``is_sqlite()``
  21. helper: the test fixture binds a SQLite engine while
  22. ``settings.database_url`` may still point at Postgres.
  23. """
  24. if db.get_bind().dialect.name == "postgresql":
  25. await db.execute(text("SELECT pg_advisory_xact_lock(1625, :k)"), {"k": QUEUE_POSITION_LOCK_KEY})
  26. async def max_queue_position(db: AsyncSession) -> int:
  27. """Highest position among pending items, or 0 for an empty queue."""
  28. result = await db.execute(select(func.max(PrintQueueItem.position)).where(PrintQueueItem.status == "pending"))
  29. return result.scalar() or 0
  30. async def next_queue_position(db: AsyncSession) -> int:
  31. """Position for an item appended to the end of the queue, under the lock."""
  32. await lock_queue_positions(db)
  33. return await max_queue_position(db) + 1