archive_work.py 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. """Archives that work running in this process holds by id (archive redesign P2).
  2. A merge moves an archive's runs, files and references to another and removes
  3. its folder. Work that holds the archive by its id in memory, and not in the
  4. database, must not have that happen under it: a queue dispatch between
  5. choosing the archive and recording it on its queue item, a timelapse
  6. conversion. Each holds the archive here for as long as it runs; a merge
  7. leaves a held archive out, and marks the archives it merges so a dispatch
  8. leaves them out in turn. Both happen on the one event loop with no await
  9. between the check and the mark, so they never pass each other.
  10. """
  11. from __future__ import annotations
  12. from collections import Counter
  13. from collections.abc import Iterable, Iterator
  14. from contextlib import contextmanager
  15. _held: Counter[int] = Counter()
  16. _merging: set[int] = set()
  17. def hold(archive_id: int) -> None:
  18. _held[archive_id] += 1
  19. def release(archive_id: int) -> None:
  20. if _held[archive_id] <= 1:
  21. _held.pop(archive_id, None)
  22. else:
  23. _held[archive_id] -= 1
  24. @contextmanager
  25. def held(archive_id: int) -> Iterator[None]:
  26. hold(archive_id)
  27. try:
  28. yield
  29. finally:
  30. release(archive_id)
  31. def is_held(archive_id: int) -> bool:
  32. return archive_id in _held
  33. def is_merging(archive_id: int) -> bool:
  34. return archive_id in _merging
  35. @contextmanager
  36. def merging(archive_ids: Iterable[int]) -> Iterator[bool]:
  37. """Mark ``archive_ids`` as being merged; yields False (and marks nothing)
  38. when one is held or already being merged."""
  39. ids = set(archive_ids)
  40. if any(i in _held or i in _merging for i in ids):
  41. yield False
  42. return
  43. _merging.update(ids)
  44. try:
  45. yield True
  46. finally:
  47. _merging.difference_update(ids)
  48. def held_while(archive_id: int, work):
  49. """``work`` (a coroutine) with the archive held until it ends. Held now,
  50. before the task it is spawned as starts, so no merge between the spawn
  51. and its first step can take the archive."""
  52. hold(archive_id)
  53. return _release_after(archive_id, work)
  54. async def _release_after(archive_id: int, work):
  55. try:
  56. return await work
  57. finally:
  58. release(archive_id)