pgbus 0.20.0 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +59 -6
  3. data/README.md +3 -2
  4. data/app/frontend/pgbus/modules/charts.js +18 -0
  5. data/app/frontend/pgbus/modules/charts.min.js +1 -1
  6. data/app/frontend/pgbus/modules/charts.min.js.map +1 -1
  7. data/app/helpers/pgbus/workflow_insights_helper.rb +8 -0
  8. data/app/helpers/pgbus/workflows_helper.rb +2 -2
  9. data/app/models/pgbus/blocked_execution.rb +12 -0
  10. data/app/models/pgbus/uniqueness_key.rb +88 -3
  11. data/app/models/pgbus/workflow_entry.rb +3 -2
  12. data/app/models/pgbus/workflow_step.rb +7 -0
  13. data/app/views/pgbus/workflows/_timeline.html.erb +11 -1
  14. data/app/views/pgbus/workflows/definitions/_step_card.html.erb +3 -0
  15. data/config/locales/da.yml +7 -1
  16. data/config/locales/de.yml +7 -1
  17. data/config/locales/en.yml +7 -1
  18. data/config/locales/es.yml +7 -1
  19. data/config/locales/fi.yml +7 -1
  20. data/config/locales/fr.yml +7 -1
  21. data/config/locales/it.yml +7 -1
  22. data/config/locales/ja.yml +7 -1
  23. data/config/locales/nb.yml +7 -1
  24. data/config/locales/nl.yml +7 -1
  25. data/config/locales/pt.yml +7 -1
  26. data/config/locales/sv.yml +7 -1
  27. data/lib/generators/pgbus/templates/add_workflows.rb.erb +3 -1
  28. data/lib/generators/pgbus/templates/migration.rb.erb +3 -1
  29. data/lib/pgbus/active_job/adapter.rb +81 -3
  30. data/lib/pgbus/active_job/delivery.rb +20 -6
  31. data/lib/pgbus/active_job/executor.rb +18 -8
  32. data/lib/pgbus/concurrency/blocked_execution.rb +14 -6
  33. data/lib/pgbus/configuration/workflow_settings.rb +1 -1
  34. data/lib/pgbus/configuration.rb +43 -1
  35. data/lib/pgbus/dead_letter_header.rb +9 -1
  36. data/lib/pgbus/instrumentation.rb +3 -2
  37. data/lib/pgbus/integrations/appsignal/dashboard.json +30 -1
  38. data/lib/pgbus/mcp/tools/workflow_stats_tool.rb +1 -1
  39. data/lib/pgbus/mcp/tools/workflows_attention_tool.rb +1 -1
  40. data/lib/pgbus/metrics/workflow_metrics.rb +11 -1
  41. data/lib/pgbus/process/consumer.rb +2 -1
  42. data/lib/pgbus/process/dispatcher.rb +13 -3
  43. data/lib/pgbus/process/worker.rb +10 -1
  44. data/lib/pgbus/recurring/schedule.rb +1 -1
  45. data/lib/pgbus/streams/presence.rb +65 -36
  46. data/lib/pgbus/uniqueness.rb +23 -6
  47. data/lib/pgbus/version.rb +1 -1
  48. data/lib/pgbus/web/data_source/workflow_list.rb +52 -7
  49. data/lib/pgbus/web/data_source.rb +123 -15
  50. data/lib/pgbus/web/workflow_insights_report.rb +11 -3
  51. data/lib/pgbus/web/workflow_state.rb +19 -3
  52. data/lib/pgbus/web/workflow_timeline.rb +23 -6
  53. data/lib/pgbus/workflow/instance.rb +36 -3
  54. data/lib/pgbus/workflow/runner.rb +2 -2
  55. data/lib/pgbus/workflow/signal_peek.rb +47 -0
  56. data/lib/pgbus/workflow/signals.rb +31 -10
  57. data/lib/pgbus/workflow/step_definition.rb +49 -5
  58. data/lib/pgbus/workflow/step_execution.rb +27 -10
  59. data/lib/pgbus/workflow/wait_timeout.rb +56 -4
  60. data/lib/pgbus/workflow.rb +11 -5
  61. data/sig/pgbus/configuration.rbs +1 -0
  62. data/sig/pgbus.rbs +43 -0
  63. metadata +2 -1
@@ -10,6 +10,9 @@
10
10
  </h3>
11
11
  <%= pgbus_insight_kind(step) %>
12
12
  </div>
13
+ <% if step[:wait_timeout] %>
14
+ <p class="mt-2 text-sm"><span class="text-gray-600 dark:text-gray-300"><%= t("pgbus.workflow_insights.show.pipeline.wait_timeout") %></span> <span class="font-mono text-gray-900 dark:text-white"><%= pgbus_insight_wait_timeout(step[:wait_timeout]) %></span></p>
15
+ <% end %>
13
16
  <ul class="mt-3 grid grid-cols-2 gap-x-3 gap-y-1 text-sm" aria-label="<%= t("pgbus.workflow_insights.show.pipeline.now_label") %>">
14
17
  <% Pgbus::WorkflowInsightsHelper::LIVE_STATUSES.each do |status| %>
15
18
  <% count = step[:counts][status.to_sym] %>
@@ -1167,6 +1167,8 @@ da:
1167
1167
  skipped: Sprunget over
1168
1168
  step_link: Igangværende kørsler ved %{step}
1169
1169
  title: Pipeline
1170
+ wait_timeout: Ventetidsgrænse
1171
+ wait_timeout_dynamic: dynamisk (pr. kørsel)
1170
1172
  window_label: Forsøg der sluttede i vinduet
1171
1173
  summary:
1172
1174
  attention: Kræver handling
@@ -1349,6 +1351,9 @@ da:
1349
1351
  unknown_html: 'Status: %{status}.'
1350
1352
  waiting_batch_html: Venter på batch %{batch} (%{done} af %{total} jobs færdige).
1351
1353
  waiting_signal_html: Venter på signalet %{signal} siden %{ago}.
1354
+ waiting_signal_late_html: Venter på signalet %{signal} siden %{ago} (forsinket siden %{late}). Næste forsinkelsesbesked sendes %{time}.
1355
+ waiting_signal_late_no_deadline_html: Venter på signalet %{signal} siden %{ago} (forsinket siden %{late}). Der sendes ikke flere forsinkelsesbeskeder.
1356
+ waiting_signal_notify_html: Venter på signalet %{signal} siden %{ago}. En forsinkelsesbesked sendes %{time}, og ventetiden fortsætter.
1352
1357
  waiting_signal_timeout_html: Venter på signalet %{signal} siden %{ago}. Ventetiden udløber %{time}.
1353
1358
  waiting_step_html: Venter på %{step}.
1354
1359
  not_found:
@@ -1451,7 +1456,7 @@ da:
1451
1456
  ready: Klar
1452
1457
  waiting: Venter
1453
1458
  timeline:
1454
- caption: 'Hvert forsøg, ældste øverst: ventede (dets ventetid), i kø (forfalden, endnu ikke hentet), kørte (farvet efter udfald) og venter (på et signal eller sin batch), med pauserne i en egen række. Forsøgstabellen nedenfor viser de samme varigheder.'
1459
+ caption: 'Hvert forsøg, ældste øverst: ventede (dets ventetid), i kø (forfalden, endnu ikke hentet), kørte (farvet efter udfald) og venter (på et signal eller sin batch), med pauserne i en egen række. Forsøgstabellen nedenfor viser de samme varigheder. En stiplet linje markerer hver gang en ventetid passerede sin frist.'
1455
1460
  empty: Ingen forsøg endnu.
1456
1461
  paused_period_html: Sat på pause %{from} i %{duration}
1457
1462
  paused_row: På pause
@@ -1465,3 +1470,4 @@ da:
1465
1470
  waited: Ventede
1466
1471
  waiting: Venter
1467
1472
  title: Tidslinje
1473
+ wait_timeout_html: 'Ventetiden for %{step} passerede sin frist %{time} (on_timeout: %{action}).'
@@ -1167,6 +1167,8 @@ de:
1167
1167
  skipped: Übersprungen
1168
1168
  step_link: Laufende Läufe bei %{step}
1169
1169
  title: Pipeline
1170
+ wait_timeout: Warte-Timeout
1171
+ wait_timeout_dynamic: dynamisch (pro Lauf)
1170
1172
  window_label: Im Zeitfenster beendete Versuche
1171
1173
  summary:
1172
1174
  attention: Brauchen Aufmerksamkeit
@@ -1349,6 +1351,9 @@ de:
1349
1351
  unknown_html: 'Status: %{status}.'
1350
1352
  waiting_batch_html: Wartet auf Stapel %{batch} (%{done} von %{total} Jobs erledigt).
1351
1353
  waiting_signal_html: Wartet auf Signal %{signal} seit %{ago}.
1354
+ waiting_signal_late_html: Wartet auf Signal %{signal} seit %{ago} (verspätet seit %{late}). Die nächste Verspätungsmeldung folgt %{time}.
1355
+ waiting_signal_late_no_deadline_html: Wartet auf Signal %{signal} seit %{ago} (verspätet seit %{late}). Es folgt keine weitere Verspätungsmeldung.
1356
+ waiting_signal_notify_html: Wartet auf Signal %{signal} seit %{ago}. Eine Verspätungsmeldung folgt %{time}, und das Warten geht weiter.
1352
1357
  waiting_signal_timeout_html: Wartet auf Signal %{signal} seit %{ago}. Das Warten läuft ab %{time}.
1353
1358
  waiting_step_html: Wartet auf %{step}.
1354
1359
  not_found:
@@ -1451,7 +1456,7 @@ de:
1451
1456
  ready: Bereit
1452
1457
  waiting: Wartend
1453
1458
  timeline:
1454
- caption: 'Jeder Versuch, der älteste oben: gewartet (seine Wartezeit), eingereiht (fällig, noch nicht abgeholt), gelaufen (nach Ausgang eingefärbt) und wartend (auf ein Signal oder seinen Stapel), die Pausen in einer eigenen Zeile. Die Versuchstabelle darunter listet dieselben Dauern.'
1459
+ caption: 'Jeder Versuch, der älteste oben: gewartet (seine Wartezeit), eingereiht (fällig, noch nicht abgeholt), gelaufen (nach Ausgang eingefärbt) und wartend (auf ein Signal oder seinen Stapel), die Pausen in einer eigenen Zeile. Die Versuchstabelle darunter listet dieselben Dauern. Eine gestrichelte Linie markiert jedes Mal, wenn ein Warten seine Frist überschritt.'
1455
1460
  empty: Noch keine Versuche.
1456
1461
  paused_period_html: Pausiert ab %{from} für %{duration}
1457
1462
  paused_row: Pausiert
@@ -1465,3 +1470,4 @@ de:
1465
1470
  waited: Gewartet
1466
1471
  waiting: Wartend
1467
1472
  title: Zeitachse
1473
+ wait_timeout_html: 'Das Warten von %{step} überschritt um %{time} seine Frist (on_timeout: %{action}).'
@@ -1167,6 +1167,8 @@ en:
1167
1167
  skipped: Skipped
1168
1168
  step_link: Ongoing runs at %{step}
1169
1169
  title: Pipeline
1170
+ wait_timeout: Wait timeout
1171
+ wait_timeout_dynamic: dynamic (per run)
1170
1172
  window_label: Attempts that finished in the window
1171
1173
  summary:
1172
1174
  attention: Need attention
@@ -1349,6 +1351,9 @@ en:
1349
1351
  unknown_html: 'Status: %{status}.'
1350
1352
  waiting_batch_html: Waiting for batch %{batch} (%{done} of %{total} jobs done).
1351
1353
  waiting_signal_html: Waiting for signal %{signal} since %{ago}.
1354
+ waiting_signal_late_html: Waiting for signal %{signal} since %{ago} (late since %{late}). The next late notice goes out %{time}.
1355
+ waiting_signal_late_no_deadline_html: Waiting for signal %{signal} since %{ago} (late since %{late}). No further late notice is due.
1356
+ waiting_signal_notify_html: Waiting for signal %{signal} since %{ago}. A late notice goes out %{time}, and the wait goes on.
1352
1357
  waiting_signal_timeout_html: Waiting for signal %{signal} since %{ago}. The wait times out %{time}.
1353
1358
  waiting_step_html: Waiting on %{step}.
1354
1359
  not_found:
@@ -1451,7 +1456,7 @@ en:
1451
1456
  ready: Ready
1452
1457
  waiting: Waiting
1453
1458
  timeline:
1454
- caption: 'Each attempt, oldest at the top: waited (its wait), queued (due, not yet picked up), ran (coloured by how it ended) and waiting (for a signal or its batch), with the pauses on their own row. The attempts table below lists the same durations.'
1459
+ caption: 'Each attempt, oldest at the top: waited (its wait), queued (due, not yet picked up), ran (coloured by how it ended) and waiting (for a signal or its batch), with the pauses on their own row. The attempts table below lists the same durations. A dashed line marks each time a wait passed its deadline.'
1455
1460
  empty: No attempts yet.
1456
1461
  paused_period_html: Paused at %{from} for %{duration}
1457
1462
  paused_row: Paused
@@ -1465,3 +1470,4 @@ en:
1465
1470
  waited: Waited
1466
1471
  waiting: Waiting
1467
1472
  title: Timeline
1473
+ wait_timeout_html: "%{step}'s wait passed its deadline at %{time} (on_timeout: %{action})."
@@ -1167,6 +1167,8 @@ es:
1167
1167
  skipped: Omitidos
1168
1168
  step_link: Ejecuciones en curso en %{step}
1169
1169
  title: Pipeline
1170
+ wait_timeout: Tiempo de espera
1171
+ wait_timeout_dynamic: dinámico (por ejecución)
1170
1172
  window_label: Intentos terminados en la ventana
1171
1173
  summary:
1172
1174
  attention: Requieren atención
@@ -1349,6 +1351,9 @@ es:
1349
1351
  unknown_html: 'Estado: %{status}.'
1350
1352
  waiting_batch_html: Esperando el lote %{batch} (%{done} de %{total} trabajos terminados).
1351
1353
  waiting_signal_html: Esperando la señal %{signal} desde %{ago}.
1354
+ waiting_signal_late_html: Esperando la señal %{signal} desde %{ago} (con retraso desde %{late}). El próximo aviso de retraso sale %{time}.
1355
+ waiting_signal_late_no_deadline_html: Esperando la señal %{signal} desde %{ago} (con retraso desde %{late}). No sale ningún otro aviso de retraso.
1356
+ waiting_signal_notify_html: Esperando la señal %{signal} desde %{ago}. Un aviso de retraso sale %{time} y la espera continúa.
1352
1357
  waiting_signal_timeout_html: Esperando la señal %{signal} desde %{ago}. La espera caduca %{time}.
1353
1358
  waiting_step_html: Esperando a %{step}.
1354
1359
  not_found:
@@ -1451,7 +1456,7 @@ es:
1451
1456
  ready: Listos
1452
1457
  waiting: En espera
1453
1458
  timeline:
1454
- caption: 'Cada intento, el más antiguo arriba: esperó (su espera), en cola (vencido, aún sin tomar), se ejecutó (coloreado según cómo terminó) y en espera (de una señal o de su lote), con las pausas en su propia fila. La tabla de intentos de abajo muestra las mismas duraciones.'
1459
+ caption: 'Cada intento, el más antiguo arriba: esperó (su espera), en cola (vencido, aún sin tomar), se ejecutó (coloreado según cómo terminó) y en espera (de una señal o de su lote), con las pausas en su propia fila. La tabla de intentos de abajo muestra las mismas duraciones. Una línea discontinua marca cada vez que una espera superó su plazo.'
1455
1460
  empty: Aún no hay intentos.
1456
1461
  paused_period_html: En pausa desde %{from} durante %{duration}
1457
1462
  paused_row: En pausa
@@ -1465,3 +1470,4 @@ es:
1465
1470
  waited: Esperó
1466
1471
  waiting: En espera
1467
1472
  title: Cronología
1473
+ wait_timeout_html: 'La espera de %{step} superó su plazo en %{time} (on_timeout: %{action}).'
@@ -1167,6 +1167,8 @@ fi:
1167
1167
  skipped: Ohitettu
1168
1168
  step_link: Käynnissä olevat ajot vaiheessa %{step}
1169
1169
  title: Putki
1170
+ wait_timeout: Odotuksen aikaraja
1171
+ wait_timeout_dynamic: dynaaminen (ajokohtainen)
1170
1172
  window_label: Ikkunassa päättyneet yritykset
1171
1173
  summary:
1172
1174
  attention: Vaatii toimia
@@ -1349,6 +1351,9 @@ fi:
1349
1351
  unknown_html: 'Tila: %{status}.'
1350
1352
  waiting_batch_html: Odottaa erää %{batch} (%{done} / %{total} tehtävää valmis).
1351
1353
  waiting_signal_html: Odottaa signaalia %{signal}, alkoi %{ago}.
1354
+ waiting_signal_late_html: Odottaa signaalia %{signal}, alkoi %{ago} (myöhässä alkaen %{late}). Seuraava myöhästymisilmoitus lähtee %{time}.
1355
+ waiting_signal_late_no_deadline_html: Odottaa signaalia %{signal}, alkoi %{ago} (myöhässä alkaen %{late}). Uusia myöhästymisilmoituksia ei lähde.
1356
+ waiting_signal_notify_html: Odottaa signaalia %{signal}, alkoi %{ago}. Myöhästymisilmoitus lähtee %{time}, ja odotus jatkuu.
1352
1357
  waiting_signal_timeout_html: Odottaa signaalia %{signal}, alkoi %{ago}. Odotus aikakatkaistaan %{time}.
1353
1358
  waiting_step_html: Odottaa vaihetta %{step}.
1354
1359
  not_found:
@@ -1451,7 +1456,7 @@ fi:
1451
1456
  ready: Valmiina
1452
1457
  waiting: Odottaa
1453
1458
  timeline:
1454
- caption: 'Jokainen yritys, vanhin ylimpänä: odotettu (sen odotus), jonossa (erääntynyt, ei vielä otettu), ajettu (väri kertoo lopputuloksen) ja odottaa (signaalia tai erää), keskeytykset omalla rivillään. Alla oleva yritystaulukko listaa samat kestot.'
1459
+ caption: 'Jokainen yritys, vanhin ylimpänä: odotettu (sen odotus), jonossa (erääntynyt, ei vielä otettu), ajettu (väri kertoo lopputuloksen) ja odottaa (signaalia tai erää), keskeytykset omalla rivillään. Alla oleva yritystaulukko listaa samat kestot. Katkoviiva merkitsee jokaista kertaa, kun odotus ylitti määräaikansa.'
1455
1460
  empty: Ei vielä yrityksiä.
1456
1461
  paused_period_html: Keskeytetty %{from} ajaksi %{duration}
1457
1462
  paused_row: Keskeytetty
@@ -1465,3 +1470,4 @@ fi:
1465
1470
  waited: Odotettu
1466
1471
  waiting: Odottaa
1467
1472
  title: Aikajana
1473
+ wait_timeout_html: 'Vaiheen %{step} odotus ylitti määräaikansa %{time} (on_timeout: %{action}).'
@@ -1167,6 +1167,8 @@ fr:
1167
1167
  skipped: Ignorées
1168
1168
  step_link: Exécutions en cours à %{step}
1169
1169
  title: Pipeline
1170
+ wait_timeout: Délai d'attente
1171
+ wait_timeout_dynamic: dynamique (par exécution)
1170
1172
  window_label: Tentatives terminées dans la fenêtre
1171
1173
  summary:
1172
1174
  attention: À traiter
@@ -1349,6 +1351,9 @@ fr:
1349
1351
  unknown_html: 'Statut : %{status}.'
1350
1352
  waiting_batch_html: En attente du lot %{batch} (%{done} travaux sur %{total} terminés).
1351
1353
  waiting_signal_html: En attente du signal %{signal} depuis %{ago}.
1354
+ waiting_signal_late_html: En attente du signal %{signal} depuis %{ago} (en retard depuis %{late}). Le prochain avis de retard part %{time}.
1355
+ waiting_signal_late_no_deadline_html: En attente du signal %{signal} depuis %{ago} (en retard depuis %{late}). Aucun autre avis de retard ne part.
1356
+ waiting_signal_notify_html: En attente du signal %{signal} depuis %{ago}. Un avis de retard part %{time} et l'attente continue.
1352
1357
  waiting_signal_timeout_html: En attente du signal %{signal} depuis %{ago}. L'attente expire %{time}.
1353
1358
  waiting_step_html: En attente de %{step}.
1354
1359
  not_found:
@@ -1451,7 +1456,7 @@ fr:
1451
1456
  ready: Prêts
1452
1457
  waiting: En attente
1453
1458
  timeline:
1454
- caption: 'Chaque tentative, la plus ancienne en haut : attente (son délai), en file (due, pas encore prise), exécution (colorée selon son issue) et en attente (d''un signal ou de son lot), avec les pauses sur leur propre ligne. Le tableau des tentatives ci-dessous donne les mêmes durées.'
1459
+ caption: 'Chaque tentative, la plus ancienne en haut : attente (son délai), en file (due, pas encore prise), exécution (colorée selon son issue) et en attente (d''un signal ou de son lot), avec les pauses sur leur propre ligne. Le tableau des tentatives ci-dessous donne les mêmes durées. Une ligne pointillée marque chaque fois qu''une attente a dépassé son échéance.'
1455
1460
  empty: Aucune tentative pour l'instant.
1456
1461
  paused_period_html: En pause à %{from} pendant %{duration}
1457
1462
  paused_row: En pause
@@ -1465,3 +1470,4 @@ fr:
1465
1470
  waited: Attendu
1466
1471
  waiting: En attente
1467
1472
  title: Chronologie
1473
+ wait_timeout_html: 'L''attente de %{step} a dépassé son échéance à %{time} (on_timeout: %{action}).'
@@ -1167,6 +1167,8 @@ it:
1167
1167
  skipped: Saltati
1168
1168
  step_link: Esecuzioni in corso a %{step}
1169
1169
  title: Pipeline
1170
+ wait_timeout: Timeout di attesa
1171
+ wait_timeout_dynamic: dinamico (per esecuzione)
1170
1172
  window_label: Tentativi terminati nella finestra
1171
1173
  summary:
1172
1174
  attention: Richiedono attenzione
@@ -1349,6 +1351,9 @@ it:
1349
1351
  unknown_html: 'Stato: %{status}.'
1350
1352
  waiting_batch_html: In attesa del lotto %{batch} (%{done} lavori su %{total} completati).
1351
1353
  waiting_signal_html: In attesa del segnale %{signal} da %{ago}.
1354
+ waiting_signal_late_html: In attesa del segnale %{signal} da %{ago} (in ritardo da %{late}). Il prossimo avviso di ritardo parte %{time}.
1355
+ waiting_signal_late_no_deadline_html: In attesa del segnale %{signal} da %{ago} (in ritardo da %{late}). Non parte nessun altro avviso di ritardo.
1356
+ waiting_signal_notify_html: In attesa del segnale %{signal} da %{ago}. Un avviso di ritardo parte %{time} e l'attesa continua.
1352
1357
  waiting_signal_timeout_html: In attesa del segnale %{signal} da %{ago}. L'attesa scade %{time}.
1353
1358
  waiting_step_html: In attesa di %{step}.
1354
1359
  not_found:
@@ -1451,7 +1456,7 @@ it:
1451
1456
  ready: Pronti
1452
1457
  waiting: In attesa
1453
1458
  timeline:
1454
- caption: 'Ogni tentativo, il più vecchio in alto: atteso (la sua attesa), in coda (previsto, non ancora preso), eseguito (colorato in base a come è finito) e in attesa (di un segnale o del suo lotto), con le pause su una riga a parte. La tabella dei tentativi qui sotto riporta le stesse durate.'
1459
+ caption: 'Ogni tentativo, il più vecchio in alto: atteso (la sua attesa), in coda (previsto, non ancora preso), eseguito (colorato in base a come è finito) e in attesa (di un segnale o del suo lotto), con le pause su una riga a parte. La tabella dei tentativi qui sotto riporta le stesse durate. Una linea tratteggiata segna ogni volta che un''attesa ha superato la scadenza.'
1455
1460
  empty: Ancora nessun tentativo.
1456
1461
  paused_period_html: In pausa dalle %{from} per %{duration}
1457
1462
  paused_row: In pausa
@@ -1465,3 +1470,4 @@ it:
1465
1470
  waited: Atteso
1466
1471
  waiting: In attesa
1467
1472
  title: Cronologia
1473
+ wait_timeout_html: 'L''attesa di %{step} ha superato la scadenza alle %{time} (on_timeout: %{action}).'
@@ -1167,6 +1167,8 @@ ja:
1167
1167
  skipped: スキップ
1168
1168
  step_link: "%{step} の進行中の実行"
1169
1169
  title: パイプライン
1170
+ wait_timeout: 待機タイムアウト
1171
+ wait_timeout_dynamic: 動的(実行ごと)
1170
1172
  window_label: 期間内に終了した試行
1171
1173
  summary:
1172
1174
  attention: 要対応
@@ -1349,6 +1351,9 @@ ja:
1349
1351
  unknown_html: 'ステータス: %{status}。'
1350
1352
  waiting_batch_html: バッチ %{batch} を待機中です(%{total} 件中 %{done} 件のジョブが完了)。
1351
1353
  waiting_signal_html: シグナル %{signal} を %{ago} から待機中です。
1354
+ waiting_signal_late_html: シグナル %{signal} を %{ago} から待機中です(%{late} から遅延)。次の遅延通知は %{time} に送られます。
1355
+ waiting_signal_late_no_deadline_html: シグナル %{signal} を %{ago} から待機中です(%{late} から遅延)。これ以上の遅延通知は送られません。
1356
+ waiting_signal_notify_html: シグナル %{signal} を %{ago} から待機中です。遅延通知は %{time} に送られ、待機は続きます。
1352
1357
  waiting_signal_timeout_html: シグナル %{signal} を %{ago} から待機中です。待機は %{time} にタイムアウトします。
1353
1358
  waiting_step_html: "%{step} を待機中です。"
1354
1359
  not_found:
@@ -1451,7 +1456,7 @@ ja:
1451
1456
  ready: 実行待ち
1452
1457
  waiting: 待機中
1453
1458
  timeline:
1454
- caption: 各試行を古い順に表示します。待機(待ち時間)、キュー(期限到来、未取得)、実行(終了状態で色分け)、待機中(シグナルまたはバッチ待ち)で、一時停止は別の行に表示します。下の試行テーブルにも同じ時間が載っています。
1459
+ caption: 各試行を古い順に表示します。待機(待ち時間)、キュー(期限到来、未取得)、実行(終了状態で色分け)、待機中(シグナルまたはバッチ待ち)で、一時停止は別の行に表示します。下の試行テーブルにも同じ時間が載っています。破線は待機が期限を過ぎた時点を示します。
1455
1460
  empty: 試行はまだありません。
1456
1461
  paused_period_html: "%{from} から %{duration} 一時停止"
1457
1462
  paused_row: 一時停止
@@ -1465,3 +1470,4 @@ ja:
1465
1470
  waited: 待機済み
1466
1471
  waiting: 待機中
1467
1472
  title: タイムライン
1473
+ wait_timeout_html: "%{step} の待機が %{time} に期限を過ぎました(on_timeout: %{action})。"
@@ -1167,6 +1167,8 @@ nb:
1167
1167
  skipped: Hoppet over
1168
1168
  step_link: Pågående kjøringer ved %{step}
1169
1169
  title: Pipeline
1170
+ wait_timeout: Tidsgrense for venting
1171
+ wait_timeout_dynamic: dynamisk (per kjøring)
1170
1172
  window_label: Forsøk som ble avsluttet i vinduet
1171
1173
  summary:
1172
1174
  attention: Trenger tiltak
@@ -1349,6 +1351,9 @@ nb:
1349
1351
  unknown_html: 'Status: %{status}.'
1350
1352
  waiting_batch_html: Venter på gruppen %{batch} (%{done} av %{total} jobber ferdige).
1351
1353
  waiting_signal_html: Venter på signalet %{signal} siden %{ago}.
1354
+ waiting_signal_late_html: Venter på signalet %{signal} siden %{ago} (forsinket siden %{late}). Neste forsinkelsesvarsel sendes %{time}.
1355
+ waiting_signal_late_no_deadline_html: Venter på signalet %{signal} siden %{ago} (forsinket siden %{late}). Det sendes ingen flere forsinkelsesvarsler.
1356
+ waiting_signal_notify_html: Venter på signalet %{signal} siden %{ago}. Et forsinkelsesvarsel sendes %{time}, og ventingen fortsetter.
1352
1357
  waiting_signal_timeout_html: Venter på signalet %{signal} siden %{ago}. Ventetiden utløper %{time}.
1353
1358
  waiting_step_html: Venter på %{step}.
1354
1359
  not_found:
@@ -1451,7 +1456,7 @@ nb:
1451
1456
  ready: Klare
1452
1457
  waiting: Venter
1453
1458
  timeline:
1454
- caption: 'Hvert forsøk, eldst øverst: ventet (ventetiden), i kø (forfalt, ikke hentet ennå), kjørte (farget etter utfall) og venter (på et signal eller sin gruppe), med pausene på en egen rad. Forsøkstabellen under viser de samme varighetene.'
1459
+ caption: 'Hvert forsøk, eldst øverst: ventet (ventetiden), i kø (forfalt, ikke hentet ennå), kjørte (farget etter utfall) og venter (på et signal eller sin gruppe), med pausene på en egen rad. Forsøkstabellen under viser de samme varighetene. En stiplet linje markerer hver gang en ventetid passerte fristen.'
1455
1460
  empty: Ingen forsøk ennå.
1456
1461
  paused_period_html: Satt på pause %{from} i %{duration}
1457
1462
  paused_row: På pause
@@ -1465,3 +1470,4 @@ nb:
1465
1470
  waited: Ventet
1466
1471
  waiting: Venter
1467
1472
  title: Tidslinje
1473
+ wait_timeout_html: 'Ventetiden for %{step} passerte fristen %{time} (on_timeout: %{action}).'
@@ -1167,6 +1167,8 @@ nl:
1167
1167
  skipped: Overgeslagen
1168
1168
  step_link: Lopende runs bij %{step}
1169
1169
  title: Pipeline
1170
+ wait_timeout: Wachttime-out
1171
+ wait_timeout_dynamic: dynamisch (per run)
1170
1172
  window_label: Pogingen die in het venster eindigden
1171
1173
  summary:
1172
1174
  attention: Aandacht nodig
@@ -1349,6 +1351,9 @@ nl:
1349
1351
  unknown_html: 'Status: %{status}.'
1350
1352
  waiting_batch_html: Wacht op groep %{batch} (%{done} van %{total} taken klaar).
1351
1353
  waiting_signal_html: Wacht op signaal %{signal}, gestart %{ago}.
1354
+ waiting_signal_late_html: Wacht op signaal %{signal}, gestart %{ago} (te laat sinds %{late}). De volgende melding over de vertraging gaat %{time} uit.
1355
+ waiting_signal_late_no_deadline_html: Wacht op signaal %{signal}, gestart %{ago} (te laat sinds %{late}). Er gaat geen nieuwe melding over de vertraging meer uit.
1356
+ waiting_signal_notify_html: Wacht op signaal %{signal}, gestart %{ago}. Een melding over de vertraging gaat %{time} uit, en het wachten gaat door.
1352
1357
  waiting_signal_timeout_html: Wacht op signaal %{signal}, gestart %{ago}. De wachttijd verloopt %{time}.
1353
1358
  waiting_step_html: Wacht op %{step}.
1354
1359
  not_found:
@@ -1451,7 +1456,7 @@ nl:
1451
1456
  ready: Klaar
1452
1457
  waiting: Wachtend
1453
1458
  timeline:
1454
- caption: 'Elke poging, oudste bovenaan: gewacht (de wachttijd), in wachtrij (aan de beurt, nog niet opgepakt), uitgevoerd (gekleurd naar hoe het afliep) en wachtend (op een signaal of de groep), met de pauzes op een eigen rij. De pogingentabel hieronder toont dezelfde duren.'
1459
+ caption: 'Elke poging, oudste bovenaan: gewacht (de wachttijd), in wachtrij (aan de beurt, nog niet opgepakt), uitgevoerd (gekleurd naar hoe het afliep) en wachtend (op een signaal of de groep), met de pauzes op een eigen rij. De pogingentabel hieronder toont dezelfde duren. Een stippellijn markeert elke keer dat een wachttijd zijn deadline passeerde.'
1455
1460
  empty: Nog geen pogingen.
1456
1461
  paused_period_html: Gepauzeerd op %{from} gedurende %{duration}
1457
1462
  paused_row: Gepauzeerd
@@ -1465,3 +1470,4 @@ nl:
1465
1470
  waited: Gewacht
1466
1471
  waiting: Wachtend
1467
1472
  title: Tijdlijn
1473
+ wait_timeout_html: 'De wachttijd van %{step} verstreek op %{time} (on_timeout: %{action}).'
@@ -1167,6 +1167,8 @@ pt:
1167
1167
  skipped: Ignoradas
1168
1168
  step_link: Execuções em curso em %{step}
1169
1169
  title: Pipeline
1170
+ wait_timeout: Tempo limite de espera
1171
+ wait_timeout_dynamic: dinâmico (por execução)
1170
1172
  window_label: Tentativas terminadas na janela
1171
1173
  summary:
1172
1174
  attention: Precisam de atenção
@@ -1349,6 +1351,9 @@ pt:
1349
1351
  unknown_html: 'Status: %{status}.'
1350
1352
  waiting_batch_html: Aguardando o lote %{batch} (%{done} de %{total} trabalhos concluídos).
1351
1353
  waiting_signal_html: Aguardando o sinal %{signal} desde %{ago}.
1354
+ waiting_signal_late_html: Aguardando o sinal %{signal} desde %{ago} (atrasado desde %{late}). O próximo aviso de atraso sai %{time}.
1355
+ waiting_signal_late_no_deadline_html: Aguardando o sinal %{signal} desde %{ago} (atrasado desde %{late}). Não sai mais nenhum aviso de atraso.
1356
+ waiting_signal_notify_html: Aguardando o sinal %{signal} desde %{ago}. Um aviso de atraso sai %{time} e a espera continua.
1352
1357
  waiting_signal_timeout_html: Aguardando o sinal %{signal} desde %{ago}. A espera expira %{time}.
1353
1358
  waiting_step_html: Aguardando %{step}.
1354
1359
  not_found:
@@ -1451,7 +1456,7 @@ pt:
1451
1456
  ready: Prontos
1452
1457
  waiting: Aguardando
1453
1458
  timeline:
1454
- caption: 'Cada tentativa, a mais antiga no topo: esperou (sua espera), na fila (vencida, ainda não pega), executou (colorida conforme terminou) e aguardando (um sinal ou seu lote), com as pausas em linha própria. A tabela de tentativas abaixo lista as mesmas durações.'
1459
+ caption: 'Cada tentativa, a mais antiga no topo: esperou (sua espera), na fila (vencida, ainda não pega), executou (colorida conforme terminou) e aguardando (um sinal ou seu lote), com as pausas em linha própria. A tabela de tentativas abaixo lista as mesmas durações. Uma linha tracejada marca cada vez que uma espera passou do prazo.'
1455
1460
  empty: Ainda não há tentativas.
1456
1461
  paused_period_html: Em pausa desde %{from} durante %{duration}
1457
1462
  paused_row: Em pausa
@@ -1465,3 +1470,4 @@ pt:
1465
1470
  waited: Esperou
1466
1471
  waiting: Aguardando
1467
1472
  title: Linha do tempo
1473
+ wait_timeout_html: 'A espera de %{step} passou do prazo em %{time} (on_timeout: %{action}).'
@@ -1167,6 +1167,8 @@ sv:
1167
1167
  skipped: Överhoppade
1168
1168
  step_link: Pågående körningar vid %{step}
1169
1169
  title: Pipeline
1170
+ wait_timeout: Väntetidsgräns
1171
+ wait_timeout_dynamic: dynamisk (per körning)
1170
1172
  window_label: Försök som avslutades i fönstret
1171
1173
  summary:
1172
1174
  attention: Behöver åtgärd
@@ -1349,6 +1351,9 @@ sv:
1349
1351
  unknown_html: 'Status: %{status}.'
1350
1352
  waiting_batch_html: Väntar på gruppen %{batch} (%{done} av %{total} jobb klara).
1351
1353
  waiting_signal_html: Väntar på signalen %{signal} sedan %{ago}.
1354
+ waiting_signal_late_html: Väntar på signalen %{signal} sedan %{ago} (sen sedan %{late}). Nästa förseningsavisering går ut %{time}.
1355
+ waiting_signal_late_no_deadline_html: Väntar på signalen %{signal} sedan %{ago} (sen sedan %{late}). Ingen fler förseningsavisering går ut.
1356
+ waiting_signal_notify_html: Väntar på signalen %{signal} sedan %{ago}. En förseningsavisering går ut %{time}, och väntan fortsätter.
1352
1357
  waiting_signal_timeout_html: Väntar på signalen %{signal} sedan %{ago}. Väntan tar slut %{time}.
1353
1358
  waiting_step_html: Väntar på %{step}.
1354
1359
  not_found:
@@ -1451,7 +1456,7 @@ sv:
1451
1456
  ready: Redo
1452
1457
  waiting: Väntar
1453
1458
  timeline:
1454
- caption: 'Varje försök, äldst överst: väntade (dess väntetid), köad (förfallen, ännu inte hämtad), körde (färgad efter utfall) och väntar (på en signal eller sin grupp), med pauserna på en egen rad. Försökstabellen nedan visar samma tider.'
1459
+ caption: 'Varje försök, äldst överst: väntade (dess väntetid), köad (förfallen, ännu inte hämtad), körde (färgad efter utfall) och väntar (på en signal eller sin grupp), med pauserna på en egen rad. Försökstabellen nedan visar samma tider. En streckad linje markerar varje gång en väntan passerade sin tidsgräns.'
1455
1460
  empty: Inga försök ännu.
1456
1461
  paused_period_html: Pausad %{from} i %{duration}
1457
1462
  paused_row: Pausad
@@ -1465,3 +1470,4 @@ sv:
1465
1470
  waited: Väntade
1466
1471
  waiting: Väntar
1467
1472
  title: Tidslinje
1473
+ wait_timeout_html: 'Väntan för %{step} passerade sin tidsgräns %{time} (on_timeout: %{action}).'
@@ -32,8 +32,10 @@ class AddPgbusWorkflows < ActiveRecord::Migration<%= migration_version %>
32
32
 
33
33
  add_index :pgbus_workflows, :workflow_id,
34
34
  unique: true, name: "idx_pgbus_workflows_workflow_id", if_not_exists: true
35
+ # status::text keeps the predicate stable in schema.rb: without the cast, a
36
+ # migrated and a schema-loaded database dump it differently (issue #573).
35
37
  add_index :pgbus_workflows, [:workflow_class, :subject_type, :subject_id],
36
- unique: true, where: "status IN ('ready','performing','waiting','paused')",
38
+ unique: true, where: "status::text IN ('ready','performing','waiting','paused')",
37
39
  name: "idx_pgbus_workflows_one_ongoing", if_not_exists: true
38
40
  add_index :pgbus_workflows, [:workflow_class, :idempotency_key],
39
41
  unique: true, where: "idempotency_key IS NOT NULL",
@@ -238,8 +238,10 @@ class CreatePgbusTables < ActiveRecord::Migration<%= migration_version %>
238
238
 
239
239
  add_index :pgbus_workflows, :workflow_id,
240
240
  unique: true, name: "idx_pgbus_workflows_workflow_id"
241
+ # status::text keeps the predicate stable in schema.rb: without the cast, a
242
+ # migrated and a schema-loaded database dump it differently (issue #573).
241
243
  add_index :pgbus_workflows, [:workflow_class, :subject_type, :subject_id],
242
- unique: true, where: "status IN ('ready','performing','waiting','paused')",
244
+ unique: true, where: "status::text IN ('ready','performing','waiting','paused')",
243
245
  name: "idx_pgbus_workflows_one_ongoing"
244
246
  add_index :pgbus_workflows, [:workflow_class, :idempotency_key],
245
247
  unique: true, where: "idempotency_key IS NOT NULL",
@@ -80,6 +80,7 @@ module Pgbus
80
80
  # reset: once the send returns, msg_id is set and the rescue routes
81
81
  # on that instead.
82
82
  sending = false
83
+ retry_lock = hand_lock_to_retry(active_job, payload_hash, queue)
83
84
 
84
85
  if key && concurrency
85
86
  # The check and the park commit together, under the semaphore row
@@ -115,6 +116,9 @@ module Pgbus
115
116
  # Bind before backfill so a live message is never left with an unbound
116
117
  # uniqueness row if execution-row bookkeeping raises.
117
118
  bind_acquired_uniqueness_lock(queue, msg_id) if msg_id
119
+ bind_retry_lock(retry_lock, queue, msg_id) if msg_id
120
+ # Dropped by its concurrency limit: no retry will run, the attempt does.
121
+ return_retry_lock(retry_lock) unless msg_id || blocked
118
122
  Batch.backfill_execution(payload_hash, msg_id, physical_queue(queue, priority)) if msg_id
119
123
  # A retry re-enqueue that is now live (sent, or parked as a blocked
120
124
  # execution) must stop the original attempt from signalling completion.
@@ -135,6 +139,7 @@ module Pgbus
135
139
  # database error from the slot upsert or the park is not ambiguous.
136
140
  if msg_id.nil? && !(sending && ambiguous_delivery?(e))
137
141
  rollback_acquired_uniqueness_lock
142
+ return_retry_lock(retry_lock)
138
143
  uncount_batch_job(payload_hash, active_job)
139
144
  else
140
145
  # Drop the thread-local so a later discard on this thread cannot
@@ -247,9 +252,10 @@ module Pgbus
247
252
  # the executor still holds the key (it releases only on success/DLQ). So
248
253
  # a re-enqueue with executions > 0 would otherwise hit its own held key,
249
254
  # be rejected as a duplicate (JobNotUnique), and dead-letter the original
250
- # while losing the retry. Let the retry through; the existing key row
251
- # correctly stays held until the job finally succeeds or dead-letters.
252
- # See issue #333.
255
+ # while losing the retry. Let the retry through (issue #333); the key
256
+ # row is handed to it (hand_lock_to_retry, then bind_retry_lock once
257
+ # sent), so the attempt's release leaves it held until the job finally
258
+ # succeeds or dead-letters (issue #564).
253
259
  return false if active_job.executions.to_i.positive?
254
260
 
255
261
  result = Uniqueness.acquire_enqueue_lock(uniqueness_key, active_job, queue_name: queue)
@@ -285,6 +291,78 @@ module Pgbus
285
291
  Pgbus.logger.warn { "[Pgbus] Uniqueness bind failed: #{e.message}" }
286
292
  end
287
293
 
294
+ # The job's :until_executed key and the delivery of the attempt running
295
+ # it, when this enqueue is that attempt's retry: the retry is let
296
+ # through the key the attempt holds and never acquires one, so the key
297
+ # row has to follow it. nil for a first attempt (it acquired its own
298
+ # row), a retry enqueued outside the executor (no attempt to take it
299
+ # from), or a job run inline inside another job, whose delivery it sees.
300
+ def retry_uniqueness_lock(active_job, payload_hash)
301
+ key = Uniqueness.extract_key(payload_hash)
302
+ return unless key && Uniqueness.extract_strategy(payload_hash) == :until_executed
303
+ return unless active_job.executions.to_i.positive?
304
+
305
+ delivery = Delivery.current
306
+ [key, delivery] if delivery && delivery[:job_id] == active_job.job_id
307
+ end
308
+
309
+ # Take the attempt's row for its retry before the retry exists — sent or
310
+ # parked — the way a first enqueue acquires before its send (see
311
+ # UniquenessKey.unbind!). Returns [key, delivery, stamp] for
312
+ # bind_retry_lock / return_retry_lock, or nil when there is nothing to
313
+ # hand over. A failure is logged and the retry goes out without the row,
314
+ # which then stays with the attempt.
315
+ def hand_lock_to_retry(active_job, payload_hash, queue)
316
+ key, delivery = retry_uniqueness_lock(active_job, payload_hash)
317
+ return unless key
318
+
319
+ stamp = UniquenessKey.unbind!(key, from_queue_name: Uniqueness.lock_queue_name(delivery[:queue_name]),
320
+ from_msg_id: delivery[:msg_id], from_enqueued_at: delivery[:enqueued_at],
321
+ queue_name: Uniqueness.lock_queue_name(queue))
322
+ return [key, delivery, stamp] if stamp
323
+
324
+ warn_retry_lock_not_held(key)
325
+ nil
326
+ rescue StandardError => e
327
+ Pgbus.logger.warn { "[Pgbus] Uniqueness handoff to retry failed for #{key}: #{e.message}" }
328
+ nil
329
+ end
330
+
331
+ # Point the handed-over row at the sent retry. The retry is live, so a
332
+ # failure is logged, not raised: the row stays unbound and stamped
333
+ # before the send, and the retry's own release takes it.
334
+ def bind_retry_lock(retry_lock, queue, msg_id)
335
+ return unless retry_lock
336
+
337
+ key, _delivery, stamp = retry_lock
338
+ UniquenessKey.bind!(key, queue_name: Uniqueness.lock_queue_name(queue), msg_id: msg_id, acquired_at: stamp)
339
+ rescue StandardError => e
340
+ Pgbus.logger.warn { "[Pgbus] Uniqueness bind to retry failed for #{key}: #{e.message}" }
341
+ end
342
+
343
+ # No retry came of it (dropped by its concurrency limit, or the send
344
+ # failed before any message existed): bind the row back to the attempt,
345
+ # which runs on and releases it when it finishes.
346
+ def return_retry_lock(retry_lock)
347
+ return unless retry_lock
348
+
349
+ key, delivery, stamp = retry_lock
350
+ UniquenessKey.bind!(key, queue_name: Uniqueness.lock_queue_name(delivery[:queue_name]),
351
+ msg_id: delivery[:msg_id], acquired_at: stamp)
352
+ rescue StandardError => e
353
+ Pgbus.logger.warn { "[Pgbus] Uniqueness return to the attempt failed for #{key}: #{e.message}" }
354
+ end
355
+
356
+ # The row is gone (released by an operator, reaped) or a successor's:
357
+ # the retry runs without the key, and a fresh enqueue is not deduped
358
+ # against it.
359
+ def warn_retry_lock_not_held(key)
360
+ Pgbus.logger.warn do
361
+ "[Pgbus] Uniqueness key #{key} is no longer held by the attempt that enqueued its retry; " \
362
+ "the retry runs without it"
363
+ end
364
+ end
365
+
288
366
  # Reverses inject_batch_metadata for a job discarded at enqueue time
289
367
  # (uniqueness duplicate or concurrency :discard conflict): the message is
290
368
  # never sent, so it can never signal completion. Only applies to a job
@@ -4,23 +4,30 @@ module Pgbus
4
4
  module ActiveJob
5
5
  # The PGMQ delivery of the job running in this fiber:
6
6
  #
7
- # Pgbus::ActiveJob::Delivery.current # => { msg_id: 42, read_ct: 2, queue_name: "default" }
7
+ # Pgbus::ActiveJob::Delivery.current
8
+ # # => { msg_id: 42, read_ct: 2, queue_name: "default",
9
+ # # enqueued_at: "2026-10-18 10:30:00+00", job_id: "8f0c…" }
8
10
  #
9
11
  # Set by Executor#execute around perform_now and cleared after, also when
10
12
  # the job raises; nil outside the executor (perform_now in a test, a
11
13
  # top-level inline-adapter job). A job run inline from inside an executing
12
14
  # job sees the enclosing job's delivery; StepJob only acts on it when the
13
- # step row's msg_id matches, so that is harmless. Fiber-local (Thread.current[]), so concurrent jobs under
15
+ # step row's msg_id matches, and the adapter only when job_id is its own,
16
+ # so that is harmless. Fiber-local (Thread.current[]), so concurrent jobs under
14
17
  # execution_mode: :async each see their own. Pgbus::Workflow::StepJob reads
15
- # read_ct to tell a redelivery after a worker died from a first delivery.
18
+ # read_ct to tell a redelivery after a worker died from a first delivery;
19
+ # the adapter reads the message identity and enqueued_at to move the
20
+ # job's uniqueness lock onto a retry the job enqueues (issue #564).
16
21
  #
17
- # The three values sit in three fiber-locals and the Hash is built only
22
+ # The values sit in one fiber-local each and the Hash is built only
18
23
  # when read: setting them allocates nothing, which keeps the executor's
19
24
  # per-job allocation budget (spec/pgbus/active_job/executor_allocation_budget_spec.rb).
20
25
  module Delivery
21
26
  MSG_ID_KEY = :pgbus_delivery_msg_id
22
27
  READ_CT_KEY = :pgbus_delivery_read_ct
23
28
  QUEUE_NAME_KEY = :pgbus_delivery_queue_name
29
+ ENQUEUED_AT_KEY = :pgbus_delivery_enqueued_at
30
+ JOB_ID_KEY = :pgbus_delivery_job_id
24
31
 
25
32
  module_function
26
33
 
@@ -28,21 +35,28 @@ module Pgbus
28
35
  msg_id = Thread.current[MSG_ID_KEY]
29
36
  return nil if msg_id.nil?
30
37
 
31
- { msg_id: msg_id, read_ct: Thread.current[READ_CT_KEY], queue_name: Thread.current[QUEUE_NAME_KEY] }
38
+ { msg_id: msg_id, read_ct: Thread.current[READ_CT_KEY], queue_name: Thread.current[QUEUE_NAME_KEY],
39
+ enqueued_at: Thread.current[ENQUEUED_AT_KEY], job_id: Thread.current[JOB_ID_KEY] }
32
40
  end
33
41
 
34
- def with(msg_id:, read_ct:, queue_name:)
42
+ def with(msg_id:, read_ct:, queue_name:, enqueued_at: nil, job_id: nil)
35
43
  previous_msg_id = Thread.current[MSG_ID_KEY]
36
44
  previous_read_ct = Thread.current[READ_CT_KEY]
37
45
  previous_queue_name = Thread.current[QUEUE_NAME_KEY]
46
+ previous_enqueued_at = Thread.current[ENQUEUED_AT_KEY]
47
+ previous_job_id = Thread.current[JOB_ID_KEY]
38
48
  Thread.current[MSG_ID_KEY] = msg_id
39
49
  Thread.current[READ_CT_KEY] = read_ct
40
50
  Thread.current[QUEUE_NAME_KEY] = queue_name
51
+ Thread.current[ENQUEUED_AT_KEY] = enqueued_at
52
+ Thread.current[JOB_ID_KEY] = job_id
41
53
  yield
42
54
  ensure
43
55
  Thread.current[MSG_ID_KEY] = previous_msg_id
44
56
  Thread.current[READ_CT_KEY] = previous_read_ct
45
57
  Thread.current[QUEUE_NAME_KEY] = previous_queue_name
58
+ Thread.current[ENQUEUED_AT_KEY] = previous_enqueued_at
59
+ Thread.current[JOB_ID_KEY] = previous_job_id
46
60
  end
47
61
  end
48
62
  end