SubtaskEventQueue.vala 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. using Invercargill;
  2. using Invercargill.DataStructures;
  3. using Inversion;
  4. using ClankerSaddle.Data;
  5. namespace ClankerSaddle.Services {
  6. /** One subtask status event awaiting delivery to the parent's daemon. */
  7. public class SubtaskStatusEvent : Object {
  8. public int64 parent_task_id { get; set; }
  9. public int64 subtask_id { get; set; }
  10. public string title { get; set; default = ""; }
  11. public string status { get; set; default = ""; }
  12. public string summary { get; set; default = ""; }
  13. }
  14. /**
  15. * Process-wide queue of subtask status events for daemons hosting
  16. * the parent's live or parked session (wire contract:
  17. * `POST /task-update {kind: "subtask-status"}`). A singleton by
  18. * necessity —
  19. * {@link TaskService} records events from any request or poll while
  20. * {@link SaddleService} drains them on the dispatcher tick, mirroring
  21. * the pending-comment bookkeeping in {@link SaddlePollState}.
  22. *
  23. * Coalescing: only the LATEST event per subtask stays queued — a
  24. * burst of transitions collapses to what the parent's daemon needs
  25. * (the current state), keeping the queue bounded by subtask count.
  26. * Ids are stringified: the Invercargill collections cannot key on
  27. * value types.
  28. */
  29. public class SubtaskEventQueue : Object {
  30. private Vector<SubtaskStatusEvent> pending = new Vector<SubtaskStatusEvent>();
  31. private HashSet<string> queued_ids = new HashSet<string>();
  32. private Mutex queue_lock = Mutex();
  33. /**
  34. * Enqueues `event`, replacing any earlier event for the same
  35. * subtask (the replacement moves to the back — newest last).
  36. */
  37. public void enqueue(SubtaskStatusEvent event) {
  38. queue_lock.lock();
  39. string key = event.subtask_id.to_string();
  40. if (queued_ids.has(key)) {
  41. pending.remove_first_where(e => e.subtask_id == event.subtask_id);
  42. } else {
  43. queued_ids.add(key);
  44. }
  45. pending.add(event);
  46. queue_lock.unlock();
  47. }
  48. /** A copy of the pending events, oldest first. */
  49. public ImmutableLot<SubtaskStatusEvent> snapshot() {
  50. queue_lock.lock();
  51. var copy = new Vector<SubtaskStatusEvent>();
  52. foreach (var event in pending) {
  53. copy.add(event);
  54. }
  55. queue_lock.unlock();
  56. return copy.to_immutable_buffer();
  57. }
  58. /** Removes the (delivered or dropped) event for `subtask_id`. */
  59. public void discard(int64 subtask_id) {
  60. queue_lock.lock();
  61. pending.remove_first_where(e => e.subtask_id == subtask_id);
  62. queued_ids.remove(subtask_id.to_string());
  63. queue_lock.unlock();
  64. }
  65. /** The number of distinct subtasks awaiting delivery. */
  66. public uint length {
  67. get {
  68. queue_lock.lock();
  69. uint count = pending.length;
  70. queue_lock.unlock();
  71. return count;
  72. }
  73. }
  74. /**
  75. * Records a dispatched subtask (the allocation claim writes the
  76. * assigned transition directly, bypassing
  77. * {@link TaskService.set_status} — this is its hook). The wire
  78. * value is `working`.
  79. */
  80. public void record_dispatch(TaskEntity subtask) {
  81. if (subtask.parent_task_id <= 0) {
  82. return;
  83. }
  84. enqueue(new SubtaskStatusEvent() {
  85. parent_task_id = subtask.parent_task_id,
  86. subtask_id = subtask.id,
  87. title = subtask.title,
  88. status = "working",
  89. summary = subtask.summary ?? ""
  90. });
  91. }
  92. }
  93. }