pgbus 0.14.1 → 0.14.2
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +28 -0
- data/app/models/pgbus/batch_entry.rb +78 -6
- data/app/models/pgbus/batch_execution.rb +28 -0
- data/app/models/pgbus/blocked_execution.rb +2 -2
- data/app/models/pgbus/uniqueness_key.rb +50 -4
- data/app/views/pgbus/batches/_batches_table.html.erb +3 -3
- data/app/views/pgbus/batches/show.html.erb +7 -7
- data/config/locales/da.yml +2 -2
- data/config/locales/de.yml +2 -2
- data/config/locales/en.yml +2 -2
- data/config/locales/es.yml +2 -2
- data/config/locales/fi.yml +2 -2
- data/config/locales/fr.yml +2 -2
- data/config/locales/it.yml +2 -2
- data/config/locales/ja.yml +2 -2
- data/config/locales/nb.yml +2 -2
- data/config/locales/nl.yml +2 -2
- data/config/locales/pt.yml +2 -2
- data/config/locales/sv.yml +2 -2
- data/lib/generators/pgbus/add_batch_callback_jobs_generator.rb +47 -0
- data/lib/generators/pgbus/add_batch_executions_generator.rb +44 -0
- data/lib/generators/pgbus/templates/add_batch_callback_jobs.rb.erb +13 -0
- data/lib/generators/pgbus/templates/add_batch_executions.rb.erb +50 -0
- data/lib/generators/pgbus/templates/initializer.rb.erb +2 -0
- data/lib/generators/pgbus/templates/migration.rb.erb +23 -2
- data/lib/pgbus/active_job/adapter.rb +142 -28
- data/lib/pgbus/active_job/batch_id.rb +48 -0
- data/lib/pgbus/active_job/executor.rb +45 -9
- data/lib/pgbus/batch/sweep.rb +163 -0
- data/lib/pgbus/batch.rb +448 -63
- data/lib/pgbus/client.rb +191 -15
- data/lib/pgbus/concurrency/blocked_execution.rb +14 -1
- data/lib/pgbus/configuration.rb +24 -1
- data/lib/pgbus/engine.rb +1 -0
- data/lib/pgbus/generators/migration_detector.rb +30 -0
- data/lib/pgbus/instrumentation.rb +5 -0
- data/lib/pgbus/outbox/poller.rb +6 -7
- data/lib/pgbus/process/dispatcher.rb +41 -17
- data/lib/pgbus/recurring/schedule.rb +12 -1
- data/lib/pgbus/uniqueness.rb +35 -7
- data/lib/pgbus/version.rb +1 -1
- data/lib/pgbus/web/data_source.rb +47 -9
- metadata +8 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6ba7f4aa13c44c41c30fb021cbb3e087d643b147895a80a84ddbc022f40088a7
|
|
4
|
+
data.tar.gz: 3015145e2f795d83d543d786c4f5d462854123f3a9e8eb946c2b74de059ee31a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dc4d0ccfac9ab0f4f57c271fe98e2a7716cfbac10daba7ca5b0ad81b59f43a51a775a1780e36faa395f23ed8084376a7e21645390e8a5f6463e1100a4db007d6
|
|
7
|
+
data.tar.gz: 684ce73b5e1e43be5d9616319da6d29ed22acf98e5384d662b63d7020ed234d8b94e2783f97a45062ae532633507b0cf44afe0007a68ea4894b55b23e631b018
|
data/CHANGELOG.md
CHANGED
|
@@ -1,7 +1,35 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
### Added
|
|
4
|
+
|
|
5
|
+
- **Batches v2: open batches, a `batch` accessor on jobs and callbacks, and configured callback instances (issue #415).** ⚠️ **Breaking (pre-1.0)**: `Pgbus::Batch.find` now returns a rehydrated `Pgbus::Batch` handle instead of the raw attributes Hash — read the values off the handle (`status`, `total_jobs`, `completed_jobs`, `failed_jobs`, `pending_jobs`, `progress_percentage`, `finished?`, `description`, `properties`), or query `Pgbus::BatchEntry` for a row. Three capabilities: **(1) Open batches.** `batch.enqueue` is re-callable while the batch is unfinished — the second and later calls add to the existing group (`BatchEntry.increment_total_jobs!`, guarded on an unfinished row) instead of hitting the unique index on `batch_id`, so multi-stage workflows are possible. Adding to a finished batch raises `Pgbus::Batch::AlreadyFinished`. Execution rows are inserted as the block enqueues, before `total_jobs` is bumped, so the single-winner finish always sees outstanding work; a `check_finished!` after the bump covers a block whose jobs all completed while it was still open. Membership stays explicit: only jobs enqueued inside an `enqueue` block join the batch, so a fan-out from a batched job never silently extends it. **(2) `batch` accessor.** New `Pgbus::ActiveJob::BatchId` mixin (included on `ActiveJob::Base` from the engine alongside `Concurrency` and `Uniqueness`) adds `batch_id` / `callback_batch_id` accessors, round-trips both through `serialize`/`deserialize` — omitted from the payload when unset, so an unbatched job's serialized hash is unchanged — and exposes a memoized `batch` reader. The executor assigns `batch_id` from the payload's `pgbus_batch_id` before `perform`, so a running job can call `batch.enqueue` to add siblings. **(3) Configured callback instances.** `on_finish:` / `on_success:` / `on_failure:` accept an ActiveJob instance as well as a class: `on_finish: ReportJob.new.set(queue: :critical, wait: 5.minutes)`. `.set` options resolve at batch-creation time (matching solid_queue) into new `on_finish_job` / `on_success_job` / `on_failure_job` jsonb columns; at fire time the job is deserialized, given `callback_batch_id`, has its own `batch_id` cleared (a callback is never a member of the batch it reports on) and is enqueued on its configured queue and `scheduled_at`. Legacy class-name callbacks keep the `perform_later(properties)` signature — deprecated at 1.0 in favour of `batch.properties`. The dashboard shows a configured callback's `job_class`. Existing installs: `rails generate pgbus:add_batch_callback_jobs` (or `pgbus:update`); apps that have not migrated keep storing bare classes only. Refs #415.
|
|
6
|
+
|
|
7
|
+
- **Batch completion is now self-healing via execution-row tracking (issue #414).** ⚠️ **Restart pgbus workers after `rails generate pgbus:add_batch_executions` / `db:migrate`** — a process that still writes `discarded_jobs` will error once that column is gone. `pgbus_batches` no longer finishes on `completed + discarded == total` — that arithmetic stalled a batch *forever* if a worker crashed between `archive_from` and `signal_batch_completed`, and a crash mid-`Batch#enqueue` left an immortal `pending` row (`cleanup_batches` only deletes `finished`). Each batched job now gets a `pgbus_batch_executions` row (identity = ActiveJob `job_id`) inserted *before* `send_message`, with `msg_id` + `queue_name` backfilled after send. The batch finishes when no rows remain (single-winner `UPDATE … AND NOT EXISTS`, plus a fresh `exists?` re-check for Postgres READ COMMITTED). A dispatcher sweep (`config.batch_sweep_interval`, default 5 minutes) repairs four crash windows: stale rows whose PGMQ message is gone (archived → completed, DLQ → failed, missing → completed with a warning); orphan rows with `msg_id` NULL older than 5 minutes (enqueue crashed between insert and send — blocked concurrency jobs are excluded); stalled `pending` batches; stalled `processing` batches with zero rows. `on_failure:` / `failed_jobs` are canonical (`on_discard:` / `discarded_jobs` remain deprecated aliases until 1.0). `config.batch_retention` (default 7 days, `nil` disables) replaces the hardcoded cleanup window. Existing installs: `rails generate pgbus:add_batch_executions` (or `pgbus:update`). Unmigrated apps keep the counter path via `Batch.executions_migrated?`. Refs #414.
|
|
8
|
+
|
|
3
9
|
### Fixed
|
|
4
10
|
|
|
11
|
+
- **A `retry_on` re-enqueue now stays in its batch — batch completion means terminal completion (issue #424).** `retry_on` rescues inside `perform_now`, re-enqueues the same job (new PGMQ message, same ActiveJob `job_id`, `executions > 0`) and returns normally; the executor then archived the original message and signalled `job_completed` — deleting the execution row and bumping `completed_jobs` — while the retry message carried no batch tag at all. `on_success` could fire with a retry still pending, and a retry that later dead-lettered never fired `on_failure`. The adapter now recognises a retry re-enqueue (a job with a `batch_id` from the `BatchId` mixin and `executions > 0`, outside any `Batch#enqueue` block), re-tags it into its batch **without** counting it again (`Batch.track_retry`: the existing row is kept via `ON CONFLICT DO NOTHING`, the backfill re-points it at the retry message) and records the `job_id` in a per-thread set once the retry is live (sent, or parked as a blocked execution); the executor consults that set after `perform` and skips the completion signal for an attempt that re-enqueued itself, clearing the set per execute. A first-attempt job that merely has a `batch_id` outside a block is still not tagged (membership stays explicit), and `callback_batch_id` never re-tags. Batches with `retry_on` jobs finish later — correctly. Refs #424.
|
|
12
|
+
|
|
13
|
+
- **Batched jobs count themselves in per job, in one transaction, before the send — `AlreadyFinished` now raises at `perform_later`, and no batch path can leave `total_jobs` out of step with its rows (issue #423).** Open batches (#422) inserted execution rows during the block and bumped `total_jobs` once at the end. Three ways that went wrong: a re-opened block that raised mid-way left rows counted but the total un-bumped, so the batch could never satisfy `completed + failed = total` and lived forever; `AlreadyFinished` was checked against a stale memoized row and only enforced after the block, so adding to a finished batch enqueued every job *and then* raised; and the stalled-pending sweep recomputed `total_jobs` from the row count, discarding `completed_jobs`/`failed_jobs` already incremented while the block ran — a crashed `pending` batch with any early completions was immortal too. Every batched enqueue (individual, bulk, blocked-promote) now runs `BatchEntry.increment_total_jobs!` — guarded on an unfinished row — and the execution-row insert inside one `BatchEntry.transaction`, *before* `send_message`; a `perform_all_later` increments once by N. The invariant `total_jobs == outstanding rows + completed_jobs + failed_jobs` holds at every commit point, so the first block only flips `pending → processing` (guarded) and re-checks, a re-opened block never bumps, the empty-batch path goes through the same single-winner finish (and now emits `pgbus.batch_finished`), and the sweep never rewrites totals. Discards at enqueue time (`uncount`) decrement and delete in one transaction. The legacy counter path only auto-finishes a `processing` batch (totals now grow mid-block). **Internal:** `Thread.current[:pgbus_batch_job_count]` is gone — nothing reads it; a spec that stubbed it should assert on `Pgbus::Batch.track_enqueue` instead. Cost: one extra `UPDATE pgbus_batches` per batched job (one per bulk send); plain enqueue is untouched. Refs #423, #415.
|
|
14
|
+
|
|
15
|
+
- **The stalled-batch sweep no longer un-counts a live job, and no longer flips a pending batch that is still actively enqueuing (issue #423).** Phase 2 deleted every `msg_id IS NULL` execution row older than the threshold and decremented `total_jobs`, assuming the enqueue died between row insert and send — but a send that landed whose backfill then failed leaves the same shape with a **live** message, and un-counting it let the batch finish (and fire `on_success`) before that job ran. The row now stores the logical queue at insert, and the sweep probes it by `job_id` first: message present → keep; probe inconclusive → keep; present in the DLQ → resolve as failed; nowhere → orphan as before. `Client#message_with_job_id?` expands a logical name to every `_pN` sub-queue under priority routing (it probed a single bare table that does not exist there and returned "unknown"). Phase 3 treats a `pending` batch as stalled only after `config.batch_stall_threshold` (new, default 5 minutes, replaces the hardcoded constant) with **no execution row inserted in that window**, so a long-running enqueue block is not closed under the caller. Refs #423.
|
|
16
|
+
|
|
17
|
+
- **A `:while_executing` job that fails once can retry — the lock is released on failure and re-acquired by the same message after a crash (issue #423).** The lock was acquired at execution start with `msg_id = 0` and released only on success or dead-letter. After an exception the row stayed, and the retry of the *same message* hit `INSERT … ON CONFLICT DO NOTHING` against its own row → `:skipped`, on every read, until `read_ct` exceeded `max_retries` and the job dead-lettered without a second real attempt; a process kill mid-job did the same. The executor now binds the lock to the message it is executing (`msg_id` + queue) and releases it in the failure path (`:until_executed` is untouched — that lock is held until success/DLQ by design); `UniquenessKey.acquire!(reacquire_same_message: true)` treats a conflict with a row pointing at the **same** `msg_id` as this message's own previous attempt (PGMQ's visibility timeout guarantees nobody else holds it) and re-acquires, while a different `msg_id` is still a genuine duplicate. Refs #423.
|
|
18
|
+
|
|
19
|
+
- **A batch callback configured as an ActiveJob instance on a database that has not run `add_batch_callback_jobs` is no longer silently dropped (issue #423).** It degrades to its class in the legacy `on_*_class` column — the callback still fires, on its default queue — and logs a WARN once per process that `.set` options are ignored until the migration runs. Refs #423.
|
|
20
|
+
|
|
21
|
+
- **A concurrency-blocked job keeps its priority when promoted (issue #423).** `BlockedExecution.release_next!` returned only `queue_name` and `payload`, so `promote_next` re-sent without `priority:` and, under `priority_levels > 1`, every promoted job landed on the default sub-queue regardless of what the enqueuer asked for. The row's `priority` is now returned and passed to `send_message` and to the execution-row backfill's target queue. Refs #423.
|
|
22
|
+
|
|
23
|
+
- **A batch whose `enqueue` block crashed before enqueuing anything is no longer immortal (#414 follow-up).** The stalled-batch sweep moved such a batch from `pending` to `processing` with `total_jobs = 0`, but `BatchEntry.finish_if_empty!` required `total_jobs > 0` and `Sweep#counters_terminal?` required `total_jobs.positive?` — guards added so a pre-migration in-flight batch (zero execution rows, counters short of `total_jobs`) is not closed empty. Nothing could then close the crashed batch: it sat in `processing` forever, `cleanup_batches` only deletes `finished` rows so it accumulated, and `on_finish` never fired — the same leak #414 set out to remove, relocated one status along. Both guards now test `completed_jobs + failed_jobs = total_jobs` alone: `total_jobs = 0` with zero counters *is* terminal, while a legacy in-flight batch still has counters short of a positive `total_jobs` and is still left on the counter path. Refs #414.
|
|
24
|
+
|
|
25
|
+
- **`ActiveJob.perform_all_later` no longer raises `PG::UndefinedTable` when priority routing is enabled.** `Client#send_batch` targeted `config.queue_name(queue)` — the bare `pgbus_<queue>` table — while the priority strategy only ever creates `_p0.._pN`, so every bulk enqueue under `priority_levels > 1` failed outright (and, had the table existed, no worker would have read it: consumers poll the sub-queues). `send_batch` now routes through the same `QueueFactory` strategy as `send_message` and takes an optional `priority:`, `Adapter#enqueue_all` groups bulk jobs by queue **and** priority so a mixed-priority `perform_all_later` produces one batch per level, and `Outbox::Poller#publish_queue_batch` — which already grouped entries by priority and then discarded it — passes it through (its single-send fallback now does too, so both paths place messages identically). Under the default single-queue strategy the target is unchanged. This also stopped bulk-enqueued batch jobs from leaking `pgbus_batch_executions` rows on the failed send. Refs #413.
|
|
26
|
+
|
|
27
|
+
- **`:until_executed` uniqueness keys no longer stick at `pending`/`msg_id=0` so the orphan reaper can now release true orphans (issue #418).** The adapter inserted the lock row as `queue_name: "pending", msg_id: 0` before `send_message` and never UPDATEd it with the real logical queue and PGMQ `msg_id`. The dispatcher's reaper then probed `pgmq.q_<prefix>_pending` — a table that does not exist — `Client#message_exists?` returned `nil` (UndefinedTable), and `nil` is treated as "still here" so leaked keys lived forever. Heartbeat jobs using `on_conflict: :discard` stayed discarded for hours or days with no matching queue message. Three cooperating fixes: **(1)** acquire with the logical queue name, then `UniquenessKey.bind!` after a successful send (adapter and recurring scheduler; retry re-enqueues do not re-bind — issue #333). A bind error is fail-soft. **(2)** Unbound rows (`pending` and/or `msg_id=0`) are reaped by scanning live `pgmq.q_*` tables via `Client#uniqueness_keys_present` — never by querying the synthetic pending queue. In-flight jobs that still look like `pending`/`0` are kept if any queue payload still carries the key. Age floor remains `2 * visibility_timeout`. **(3)** The executor releases the lock once, immediately after archive (fail-soft), so a kill during `pgbus.job_completed` cannot leak a lock whose message is already gone. There is deliberately **no** second `ensure` DELETE: a key-only DELETE after a committed release can drop a successor that re-acquired the same key; a failed release is instead healed by the reaper once the archived message is provably gone. Bound `message_exists?` lookups on a logical name also check priority `_pN` sub-queues. Do **not** bulk-delete `pending`/`msg_id=0` rows on upgrade — the reaper heals true orphans on the next cleanup cycle. Refs #418.
|
|
28
|
+
|
|
29
|
+
- **A job that combines `ensures_uniqueness` (`:until_executed`) with `limits_concurrency on_conflict: :discard` no longer leaves the uniqueness lock held forever.** Enqueue acquires the uniqueness lock first, then the semaphore; a `:discard` conflict dropped the job without sending a message, so no executor could ever release the lock and later equivalent jobs were blocked until someone deleted the `pgbus_uniqueness_keys` row. The discard path now rolls back the lock this enqueue acquired. A uniqueness *duplicate* discard still does not release — that lock belongs to the in-flight job. `:block` conflicts still keep the lock, because `BlockedExecution` stores the tagged payload and the job runs when the semaphore frees. Refs #413.
|
|
30
|
+
|
|
31
|
+
- **`ActiveJob.perform_all_later` no longer lets jobs escape batch tracking or bypass concurrency limits (issue #413).** `Adapter#enqueue_all`'s bulk path (`enqueue_immediate`) serialized payloads directly to `send_batch`, skipping the metadata-injection steps the individual `enqueue`/`enqueue_at` paths run. Two consequences: **(1)** jobs bulk-enqueued inside a `Pgbus::Batch#enqueue` block were neither tagged with `pgbus_batch_id` nor counted into `total_jobs`, so the batch could finish — and fire `on_finish`/`on_success` — before those jobs ever ran; **(2)** jobs whose class declares `pgbus_concurrency` were sent without semaphore acquisition, silently ignoring their concurrency limit. Bulk payloads now pass through `inject_batch_metadata` (tagging + counting), and concurrency-configured jobs are partitioned out of the bulk path into the individual enqueue path — exactly how uniqueness-configured jobs were already routed, and for the same reason: per-job locks cannot be acquired in a bulk send. Plain jobs keep the fast `send_batch` path unchanged. Refs #413.
|
|
32
|
+
|
|
5
33
|
- **The 0.14.0 purge/drop guard no longer crashes on Rails 8.0/8.1 — pool ownership is resolved version-tolerantly (issue #411).** `Pgbus::BusRecord.disconnect_all_pools!` (added in #410 for the #409 wedge) identified its own pools via `pool.connection_class` — an API that exists on Rails 7.1/7.2 but was replaced in Rails 8.0 by `ConnectionPool#connection_descriptor` (a `ConnectionDescriptor` whose `#name` is the owning class name; 8.x `PoolConfig` has no `connection_class` either, so the obvious fallback route doesn't exist). Since the guard is prepended onto every purge/drop path, the `NoMethodError` aborted **every** `db:test:purge` / `db:prepare` on current stable Rails, forcing consumers to cap at `< 0.14`. Ownership is now resolved against whichever API the running Rails exposes — `connection_descriptor&.name == name` on 8.0+ (a nil descriptor, e.g. a null pool, never matches), `connection_class == self` on 7.x — with identical semantics: only `connects_to`-created BusRecord pools are disconnected, `ActiveRecord::Base`'s pool is never touched. The reason CI missed this: the spec stubbed `connection_class` on plain RSpec doubles, which happily fake methods the running Rails doesn't define. The spec now also exercises verifying doubles (`instance_double`) against the real `ConnectionPool`, so both existing matrix legs (7.1 and latest 8.x) fail on any future drift in the ownership API. Refs #411.
|
|
6
34
|
|
|
7
35
|
- **Boot-time BusRecord connections no longer wedge `db:test:purge` — pgbus disconnects its own pools before every database purge/drop (issue #409).** With pgbus on a dedicated database (`config.connects_to`), any boot-time touch of a pgbus model left an idle session on that database for the life of the process. Rails' purge/drop only disconnects the connection it establishes for the target db_config — it knows nothing about gem-owned pools — so the rake process's own idle session blocked its own `DROP DATABASE`: a hang without a `statement_timeout`, a fast `ActiveRecord::QueryCanceled` with one, and either way `db:test:prepare` / `maintain_test_schema!` permanently broken on that machine (every retry re-boots, re-opens the connection, and re-blocks; terminating sessions externally can't help). Three changes: **(1)** `Pgbus::DatabaseTasksGuard` is prepended onto `ActiveRecord::Tasks::DatabaseTasks` at boot, so every route to a purge/drop — `db:test:purge` / `db:purge` / `db:drop` and their per-database variants, `maintain_test_schema!`'s in-process purge, parallel-testing's `TestDatabases` — first runs `Pgbus::BusRecord.disconnect_all_pools!` (all roles; a no-op on primary-database installs, `ActiveRecord::Base`'s pool is never touched). **(2)** The task-aware guard the event-bus registry already used internally is now public as **`Pgbus.database_task?`** — true while the process runs a `db:*` / `assets:*`-family rake task — so apps can skip their own boot-time warm-ups in the contexts where a database may legitimately not exist. **(3)** `pgbus:tune_autovacuum` (enhanced onto `db:schema:load`) no longer holds a permanent connection lease from `BusRecord.connection`: it checks out via `connection_pool.with_connection` and disconnects the pool afterward, leaving no idle session behind inside longer rake chains. Refs #409.
|
|
@@ -4,14 +4,79 @@ module Pgbus
|
|
|
4
4
|
class BatchEntry < BusRecord
|
|
5
5
|
self.table_name = "pgbus_batches"
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
# discarded_jobs remains valid until the add_batch_executions migration
|
|
8
|
+
# folds it into failed_jobs. Both names are accepted so gem-before-migrate
|
|
9
|
+
# and gem-after-migrate stay incrementable.
|
|
10
|
+
COUNTER_COLUMNS = %w[completed_jobs discarded_jobs failed_jobs].freeze
|
|
8
11
|
|
|
9
12
|
scope :finished, -> { where(status: "finished") }
|
|
10
13
|
scope :stale, ->(before:) { finished.where("finished_at < ?", before) }
|
|
14
|
+
scope :pending, -> { where(status: "pending") }
|
|
15
|
+
scope :processing, -> { where(status: "processing") }
|
|
16
|
+
scope :without_executions, -> { where.not(batch_id: BatchExecution.select(:batch_id)) }
|
|
11
17
|
|
|
12
|
-
#
|
|
13
|
-
|
|
18
|
+
# Deprecated alias until 1.0: after the column is dropped this reads failed_jobs.
|
|
19
|
+
def discarded_jobs
|
|
20
|
+
has_attribute?(:discarded_jobs) ? self[:discarded_jobs] : failed_jobs
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Atomically add n to total_jobs on an unfinished batch. Returns true.
|
|
24
|
+
# Raises Batch::AlreadyFinished when the row is already finished (0 rows
|
|
25
|
+
# updated) — the adder-before-insert contract open batches (#415) rely on.
|
|
26
|
+
def self.increment_total_jobs!(batch_id, count) # rubocop:disable Naming/PredicateMethod
|
|
27
|
+
updated = where(batch_id: batch_id, status: %w[pending processing])
|
|
28
|
+
.update_all(["total_jobs = total_jobs + ?", count])
|
|
29
|
+
raise Batch::AlreadyFinished, "Can't add jobs into an already finished batch" if updated.zero?
|
|
30
|
+
|
|
31
|
+
true
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Reverse of increment_total_jobs! for a job that will never run. Floored
|
|
35
|
+
# at zero; a finished row is left alone.
|
|
36
|
+
def self.decrement_total_jobs!(batch_id)
|
|
37
|
+
where(batch_id: batch_id, status: %w[pending processing])
|
|
38
|
+
.update_all(["total_jobs = GREATEST(total_jobs - 1, 0)"])
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Single-winner finish: status is processing AND no execution rows remain.
|
|
42
|
+
# Join-free NOT EXISTS so the subquery stays in this UPDATE's WHERE.
|
|
43
|
+
# Returns the number of rows updated (0 or 1).
|
|
44
|
+
def self.finish_if_empty!(batch_id)
|
|
45
|
+
# Counters must already be terminal so a pre-migration in-flight batch
|
|
46
|
+
# (zero execution rows, total_jobs = N, counters short of N) is not
|
|
47
|
+
# closed empty. total_jobs = 0 with zero counters IS terminal: that is a
|
|
48
|
+
# batch whose enqueue block crashed before it enqueued anything, and
|
|
49
|
+
# nothing else will ever close it.
|
|
50
|
+
where(batch_id: batch_id, status: "processing")
|
|
51
|
+
.without_executions
|
|
52
|
+
.where("completed_jobs + failed_jobs = total_jobs")
|
|
53
|
+
.update_all(status: "finished", finished_at: Time.current)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Finish the batch if every job already reached a terminal state. Used
|
|
57
|
+
# after total_jobs is published: completion signals that arrived while the
|
|
58
|
+
# enqueue block was still open saw total_jobs == 0 and could not finish
|
|
59
|
+
# the batch themselves (PR #417). Row lock + status guard keep it
|
|
60
|
+
# idempotent against concurrent completion signals.
|
|
14
61
|
# Returns { just_finished:, record: } or nil if batch not found.
|
|
62
|
+
def self.check_finished!(batch_id)
|
|
63
|
+
return Batch.try_finish!(batch_id) if Batch.executions_migrated?
|
|
64
|
+
|
|
65
|
+
transaction do
|
|
66
|
+
record = lock.find_by(batch_id: batch_id)
|
|
67
|
+
return nil unless record
|
|
68
|
+
return { record: record, just_finished: false } if record.status == "finished"
|
|
69
|
+
return { record: record, just_finished: false } unless record.completed_jobs + record.discarded_jobs == record.total_jobs
|
|
70
|
+
|
|
71
|
+
record.update!(status: "finished", finished_at: Time.current)
|
|
72
|
+
{ record: record, just_finished: true }
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Atomically increment the counter and, on the pre-migration path, detect
|
|
77
|
+
# if this update caused the batch to finish. Uses row-level locking to
|
|
78
|
+
# prevent duplicate callbacks. Returns { just_finished:, record: } or nil
|
|
79
|
+
# if batch not found.
|
|
15
80
|
def self.increment_counter!(batch_id, column)
|
|
16
81
|
raise ArgumentError, "Invalid column: #{column}" unless COUNTER_COLUMNS.include?(column)
|
|
17
82
|
|
|
@@ -21,10 +86,17 @@ module Pgbus
|
|
|
21
86
|
|
|
22
87
|
record.increment!(column)
|
|
23
88
|
|
|
24
|
-
|
|
25
|
-
|
|
89
|
+
return { record: record, just_finished: false } if Batch.executions_migrated?
|
|
90
|
+
|
|
91
|
+
# total_jobs grows per job while the block is still open (issue #423),
|
|
92
|
+
# so completed == total can be momentarily true on a pending batch.
|
|
93
|
+
# Only a processing batch may auto-finish; check_finished! at the end
|
|
94
|
+
# of the block covers the pending case.
|
|
95
|
+
counters_match = record.completed_jobs + record.discarded_jobs == record.total_jobs
|
|
96
|
+
just_finished = counters_match && record.status == "processing"
|
|
97
|
+
record.update!(status: "finished", finished_at: Time.current) if just_finished
|
|
26
98
|
|
|
27
|
-
{ record: record, just_finished: just_finished
|
|
99
|
+
{ record: record, just_finished: just_finished }
|
|
28
100
|
end
|
|
29
101
|
end
|
|
30
102
|
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pgbus
|
|
4
|
+
class BatchExecution < BusRecord
|
|
5
|
+
self.table_name = "pgbus_batch_executions"
|
|
6
|
+
|
|
7
|
+
# One row per outstanding batched job. Inserted before send_message so a
|
|
8
|
+
# crash cannot produce an untracked in-flight job. ON CONFLICT DO NOTHING
|
|
9
|
+
# makes a retry re-enqueue of the same ActiveJob id a no-op.
|
|
10
|
+
# Raw SQL rather than insert_all(unique_by:) — Rails resolves unique_by
|
|
11
|
+
# through the schema cache (issue #401).
|
|
12
|
+
# queue_name is the logical queue at insert (so the sweep can probe for a
|
|
13
|
+
# live message before the msg_id backfill lands); backfill! overwrites it
|
|
14
|
+
# with the physical target after send.
|
|
15
|
+
def self.insert_for!(batch_id:, job_id:, queue_name: nil)
|
|
16
|
+
connection.exec_query(
|
|
17
|
+
"INSERT INTO #{table_name} (batch_id, job_id, queue_name, created_at) " \
|
|
18
|
+
"VALUES ($1, $2, $3, $4) ON CONFLICT (job_id) DO NOTHING",
|
|
19
|
+
"BatchExecution Insert",
|
|
20
|
+
[batch_id, job_id, queue_name, Time.current]
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.backfill!(job_id, msg_id:, queue_name:)
|
|
25
|
+
where(job_id: job_id).update_all(msg_id: msg_id, queue_name: queue_name)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -22,7 +22,7 @@ module Pgbus
|
|
|
22
22
|
LIMIT 1
|
|
23
23
|
FOR UPDATE SKIP LOCKED
|
|
24
24
|
)
|
|
25
|
-
RETURNING queue_name, payload
|
|
25
|
+
RETURNING queue_name, payload, priority
|
|
26
26
|
SQL
|
|
27
27
|
"Pgbus Blocked Release",
|
|
28
28
|
[concurrency_key, now]
|
|
@@ -34,7 +34,7 @@ module Pgbus
|
|
|
34
34
|
payload = row["payload"]
|
|
35
35
|
payload = JSON.parse(payload) if payload.is_a?(String)
|
|
36
36
|
|
|
37
|
-
{ queue_name: row["queue_name"], payload: payload }
|
|
37
|
+
{ queue_name: row["queue_name"], payload: payload, priority: row["priority"] }
|
|
38
38
|
end
|
|
39
39
|
end
|
|
40
40
|
end
|
|
@@ -8,16 +8,62 @@ module Pgbus
|
|
|
8
8
|
# Atomically try to acquire a uniqueness lock via INSERT ... ON CONFLICT.
|
|
9
9
|
# PostgreSQL's unique index on lock_key guarantees at most one caller wins.
|
|
10
10
|
# Returns true if acquired (row inserted), false if already locked.
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
#
|
|
12
|
+
# reacquire_same_message: true (the :while_executing executor path) treats
|
|
13
|
+
# a conflict with a row that already points at THIS msg_id as acquired —
|
|
14
|
+
# it is this message's own previous attempt (PGMQ's visibility timeout
|
|
15
|
+
# guarantees nobody else holds the message), left behind by a crash. A
|
|
16
|
+
# conflict with a different msg_id is a genuine concurrent execution.
|
|
17
|
+
def self.acquire!(lock_key, queue_name:, msg_id:, reacquire_same_message: false) # rubocop:disable Naming/PredicateMethod
|
|
18
|
+
on_conflict = if reacquire_same_message && msg_id.to_i.positive?
|
|
19
|
+
"DO UPDATE SET queue_name = EXCLUDED.queue_name " \
|
|
20
|
+
"WHERE #{table_name}.msg_id = EXCLUDED.msg_id"
|
|
21
|
+
else
|
|
22
|
+
"DO NOTHING"
|
|
23
|
+
end
|
|
24
|
+
result = connection.exec_query(
|
|
13
25
|
"INSERT INTO #{table_name} (lock_key, queue_name, msg_id) " \
|
|
14
|
-
"VALUES ($1, $2, $3) ON CONFLICT (lock_key)
|
|
26
|
+
"VALUES ($1, $2, $3) ON CONFLICT (lock_key) #{on_conflict} RETURNING lock_key, created_at",
|
|
15
27
|
"UniquenessKey Acquire", [lock_key, queue_name, msg_id]
|
|
16
|
-
)
|
|
28
|
+
)
|
|
29
|
+
row = result.rows.first
|
|
30
|
+
return false unless row
|
|
31
|
+
|
|
32
|
+
# Ownership stamp for bind!: a successor acquire of the same key after
|
|
33
|
+
# this row is released must not inherit this enqueue's msg_id.
|
|
34
|
+
stamps = Thread.current[:pgbus_uniqueness_created_at] ||= {}
|
|
35
|
+
stamps[lock_key] = row[1]
|
|
36
|
+
true
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Bind a pre-produce lock to the real queue and PGMQ msg_id after send.
|
|
40
|
+
# Does not touch created_at — the reaper's age floor is from acquire time.
|
|
41
|
+
# Restricted to this enqueue's unbound row (msg_id=0, matching created_at
|
|
42
|
+
# when acquire! stamped one) so a completed job's bind cannot retarget a
|
|
43
|
+
# successor that re-acquired the key.
|
|
44
|
+
def self.bind!(lock_key, queue_name:, msg_id:)
|
|
45
|
+
stamps = Thread.current[:pgbus_uniqueness_created_at]
|
|
46
|
+
created_at = stamps&.delete(lock_key)
|
|
47
|
+
sql = "UPDATE #{table_name} SET queue_name = $2, msg_id = $3 " \
|
|
48
|
+
"WHERE lock_key = $1 AND msg_id = 0"
|
|
49
|
+
binds = [lock_key, queue_name, msg_id]
|
|
50
|
+
if created_at
|
|
51
|
+
sql += " AND created_at = $4"
|
|
52
|
+
binds << created_at
|
|
53
|
+
end
|
|
54
|
+
connection.exec_update(sql, "UniquenessKey Bind", binds)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Drop the bind ownership stamp without touching the lock row. Used when
|
|
58
|
+
# this thread acquired the key but will not bind (concurrency :block, or
|
|
59
|
+
# enqueue returning after a failed send already rolled the lock back).
|
|
60
|
+
def self.clear_bind_stamp!(lock_key)
|
|
61
|
+
Thread.current[:pgbus_uniqueness_created_at]&.delete(lock_key)
|
|
17
62
|
end
|
|
18
63
|
|
|
19
64
|
# Release a uniqueness lock after job completion or DLQ.
|
|
20
65
|
def self.release!(lock_key)
|
|
66
|
+
Thread.current[:pgbus_uniqueness_created_at]&.delete(lock_key)
|
|
21
67
|
connection.exec_delete(
|
|
22
68
|
"DELETE FROM #{table_name} WHERE lock_key = $1",
|
|
23
69
|
"UniquenessKey Release", [lock_key]
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
<td data-label="Progress" class="px-4 py-3 text-sm">
|
|
29
29
|
<div class="flex items-center space-x-2">
|
|
30
30
|
<div class="w-24 bg-gray-200 dark:bg-gray-700 rounded-full h-2">
|
|
31
|
-
<div class="h-2 rounded-full <%= batch[:
|
|
31
|
+
<div class="h-2 rounded-full <%= batch[:failed_jobs].to_i > 0 ? 'bg-amber-500' : 'bg-green-500' %>"
|
|
32
32
|
style="width: <%= batch[:progress_pct] %>%"></div>
|
|
33
33
|
</div>
|
|
34
34
|
<span class="text-xs text-gray-500 dark:text-gray-400 font-mono"><%= batch[:progress_pct] %>%</span>
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
</td>
|
|
37
37
|
<td data-label="Jobs" class="px-4 py-3 text-sm text-right font-mono text-gray-500 dark:text-gray-400">
|
|
38
38
|
<%= batch[:completed_jobs] %>/<%= batch[:total_jobs] %>
|
|
39
|
-
<% if batch[:
|
|
40
|
-
<span class="text-red-500">(<%= batch[:
|
|
39
|
+
<% if batch[:failed_jobs].to_i > 0 %>
|
|
40
|
+
<span class="text-red-500">(<%= batch[:failed_jobs] %> failed)</span>
|
|
41
41
|
<% end %>
|
|
42
42
|
</td>
|
|
43
43
|
<td data-label="Created" class="px-4 py-3 text-sm text-right text-gray-500 dark:text-gray-400">
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
<h2 class="text-sm font-semibold text-gray-900 dark:text-white mb-4"><%= t("pgbus.batches.show.progress") %></h2>
|
|
19
19
|
|
|
20
20
|
<div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-4 mb-3">
|
|
21
|
-
<div class="h-4 rounded-full transition-all duration-300 <%= @batch[:
|
|
21
|
+
<div class="h-4 rounded-full transition-all duration-300 <%= @batch[:failed_jobs].to_i > 0 ? 'bg-amber-500' : 'bg-green-500' %>"
|
|
22
22
|
style="width: <%= @batch[:progress_pct] %>%"></div>
|
|
23
23
|
</div>
|
|
24
24
|
|
|
@@ -32,12 +32,12 @@
|
|
|
32
32
|
<dd class="mt-1 text-2xl font-semibold text-green-600 dark:text-green-400 font-mono"><%= @batch[:completed_jobs] %></dd>
|
|
33
33
|
</div>
|
|
34
34
|
<div>
|
|
35
|
-
<dt class="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase"><%= t("pgbus.batches.show.
|
|
36
|
-
<dd class="mt-1 text-2xl font-semibold <%= @batch[:
|
|
35
|
+
<dt class="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase"><%= t("pgbus.batches.show.failed") %></dt>
|
|
36
|
+
<dd class="mt-1 text-2xl font-semibold <%= @batch[:failed_jobs].to_i > 0 ? 'text-red-600 dark:text-red-400' : 'text-gray-900 dark:text-white' %> font-mono"><%= @batch[:failed_jobs] %></dd>
|
|
37
37
|
</div>
|
|
38
38
|
<div>
|
|
39
39
|
<dt class="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase"><%= t("pgbus.batches.show.remaining") %></dt>
|
|
40
|
-
<dd class="mt-1 text-2xl font-semibold text-gray-900 dark:text-white font-mono"><%= @batch[:total_jobs] - @batch[:completed_jobs] - @batch[:
|
|
40
|
+
<dd class="mt-1 text-2xl font-semibold text-gray-900 dark:text-white font-mono"><%= @batch[:pending_jobs] || (@batch[:total_jobs] - @batch[:completed_jobs] - @batch[:failed_jobs]) %></dd>
|
|
41
41
|
</div>
|
|
42
42
|
</div>
|
|
43
43
|
</div>
|
|
@@ -74,10 +74,10 @@
|
|
|
74
74
|
<dd class="mt-1 text-sm font-mono text-gray-900 dark:text-white sm:col-span-2 sm:mt-0"><%= @batch[:on_success_class] %></dd>
|
|
75
75
|
</div>
|
|
76
76
|
<% end %>
|
|
77
|
-
<% if @batch[:on_discard_class] %>
|
|
77
|
+
<% if @batch[:on_failure_class] || @batch[:on_discard_class] %>
|
|
78
78
|
<div class="px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4">
|
|
79
|
-
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400"><%= t("pgbus.batches.show.
|
|
80
|
-
<dd class="mt-1 text-sm font-mono text-gray-900 dark:text-white sm:col-span-2 sm:mt-0"><%= @batch[:on_discard_class] %></dd>
|
|
79
|
+
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400"><%= t("pgbus.batches.show.on_failure") %></dt>
|
|
80
|
+
<dd class="mt-1 text-sm font-mono text-gray-900 dark:text-white sm:col-span-2 sm:mt-0"><%= @batch[:on_failure_class] || @batch[:on_discard_class] %></dd>
|
|
81
81
|
</div>
|
|
82
82
|
<% end %>
|
|
83
83
|
<% if @batch[:properties].present? %>
|
data/config/locales/da.yml
CHANGED
|
@@ -19,10 +19,10 @@ da:
|
|
|
19
19
|
completed: Fuldført
|
|
20
20
|
created_at: Oprettet den
|
|
21
21
|
details: Detaljer
|
|
22
|
-
|
|
22
|
+
failed: Mislykkedes
|
|
23
23
|
finished_at: Afsluttet den
|
|
24
24
|
not_found: Batch ikke fundet
|
|
25
|
-
|
|
25
|
+
on_failure: Ved fejl
|
|
26
26
|
on_finish: Ved afslutning
|
|
27
27
|
on_success: Ved succes
|
|
28
28
|
progress: Fremgang
|
data/config/locales/de.yml
CHANGED
|
@@ -19,10 +19,10 @@ de:
|
|
|
19
19
|
completed: Abgeschlossen
|
|
20
20
|
created_at: Erstellt am
|
|
21
21
|
details: Details
|
|
22
|
-
|
|
22
|
+
failed: Fehlgeschlagen
|
|
23
23
|
finished_at: Beendet am
|
|
24
24
|
not_found: Stapel nicht gefunden
|
|
25
|
-
|
|
25
|
+
on_failure: Im Fehlerfall
|
|
26
26
|
on_finish: Bei Beendigung
|
|
27
27
|
on_success: Bei Erfolg
|
|
28
28
|
progress: Fortschritt
|
data/config/locales/en.yml
CHANGED
|
@@ -19,10 +19,10 @@ en:
|
|
|
19
19
|
completed: Completed
|
|
20
20
|
created_at: Created At
|
|
21
21
|
details: Details
|
|
22
|
-
|
|
22
|
+
failed: Failed
|
|
23
23
|
finished_at: Finished At
|
|
24
24
|
not_found: Batch not found
|
|
25
|
-
|
|
25
|
+
on_failure: On Failure
|
|
26
26
|
on_finish: On Finish
|
|
27
27
|
on_success: On Success
|
|
28
28
|
progress: Progress
|
data/config/locales/es.yml
CHANGED
|
@@ -19,10 +19,10 @@ es:
|
|
|
19
19
|
completed: Completados
|
|
20
20
|
created_at: Creado el
|
|
21
21
|
details: Detalles
|
|
22
|
-
|
|
22
|
+
failed: Fallados
|
|
23
23
|
finished_at: Finalizado el
|
|
24
24
|
not_found: Lote no encontrado
|
|
25
|
-
|
|
25
|
+
on_failure: Al fallar
|
|
26
26
|
on_finish: Al finalizar
|
|
27
27
|
on_success: Al tener éxito
|
|
28
28
|
progress: Progreso
|
data/config/locales/fi.yml
CHANGED
|
@@ -19,10 +19,10 @@ fi:
|
|
|
19
19
|
completed: Valmiit
|
|
20
20
|
created_at: Luotu
|
|
21
21
|
details: Tiedot
|
|
22
|
-
|
|
22
|
+
failed: Epäonnistuneet
|
|
23
23
|
finished_at: Valmistunut
|
|
24
24
|
not_found: Erää ei löytynyt
|
|
25
|
-
|
|
25
|
+
on_failure: Epäonnistumisessa
|
|
26
26
|
on_finish: Valmistumisessa
|
|
27
27
|
on_success: Onnistumisessa
|
|
28
28
|
progress: Edistyminen
|
data/config/locales/fr.yml
CHANGED
|
@@ -19,10 +19,10 @@ fr:
|
|
|
19
19
|
completed: Terminées
|
|
20
20
|
created_at: Créé le
|
|
21
21
|
details: Détails
|
|
22
|
-
|
|
22
|
+
failed: Échouées
|
|
23
23
|
finished_at: Terminé le
|
|
24
24
|
not_found: Lot non trouvé
|
|
25
|
-
|
|
25
|
+
on_failure: En cas d'échec
|
|
26
26
|
on_finish: À la fin
|
|
27
27
|
on_success: Au succès
|
|
28
28
|
progress: Progression
|
data/config/locales/it.yml
CHANGED
|
@@ -19,10 +19,10 @@ it:
|
|
|
19
19
|
completed: Completati
|
|
20
20
|
created_at: Creato il
|
|
21
21
|
details: Dettagli
|
|
22
|
-
|
|
22
|
+
failed: Falliti
|
|
23
23
|
finished_at: Terminato il
|
|
24
24
|
not_found: Lotto non trovato
|
|
25
|
-
|
|
25
|
+
on_failure: Al fallimento
|
|
26
26
|
on_finish: Al termine
|
|
27
27
|
on_success: Al successo
|
|
28
28
|
progress: Progresso
|
data/config/locales/ja.yml
CHANGED
data/config/locales/nb.yml
CHANGED
|
@@ -19,10 +19,10 @@ nb:
|
|
|
19
19
|
completed: Fullført
|
|
20
20
|
created_at: Opprettet den
|
|
21
21
|
details: Detaljer
|
|
22
|
-
|
|
22
|
+
failed: Mislyktes
|
|
23
23
|
finished_at: Avsluttet den
|
|
24
24
|
not_found: Gruppe ikke funnet
|
|
25
|
-
|
|
25
|
+
on_failure: Ved feil
|
|
26
26
|
on_finish: Ved avslutning
|
|
27
27
|
on_success: Ved suksess
|
|
28
28
|
progress: Fremdrift
|
data/config/locales/nl.yml
CHANGED
|
@@ -19,10 +19,10 @@ nl:
|
|
|
19
19
|
completed: Voltooid
|
|
20
20
|
created_at: Aangemaakt op
|
|
21
21
|
details: Details
|
|
22
|
-
|
|
22
|
+
failed: Mislukt
|
|
23
23
|
finished_at: Voltooid op
|
|
24
24
|
not_found: Groep niet gevonden
|
|
25
|
-
|
|
25
|
+
on_failure: Bij mislukking
|
|
26
26
|
on_finish: Bij voltooiing
|
|
27
27
|
on_success: Bij succes
|
|
28
28
|
progress: Voortgang
|
data/config/locales/pt.yml
CHANGED
|
@@ -19,10 +19,10 @@ pt:
|
|
|
19
19
|
completed: Concluídos
|
|
20
20
|
created_at: Criado em
|
|
21
21
|
details: Detalhes
|
|
22
|
-
|
|
22
|
+
failed: Falhados
|
|
23
23
|
finished_at: Finalizado em
|
|
24
24
|
not_found: Lote não encontrado
|
|
25
|
-
|
|
25
|
+
on_failure: Ao falhar
|
|
26
26
|
on_finish: Ao finalizar
|
|
27
27
|
on_success: Ao ter sucesso
|
|
28
28
|
progress: Progresso
|
data/config/locales/sv.yml
CHANGED
|
@@ -19,10 +19,10 @@ sv:
|
|
|
19
19
|
completed: Slutförda
|
|
20
20
|
created_at: Skapad den
|
|
21
21
|
details: Detaljer
|
|
22
|
-
|
|
22
|
+
failed: Misslyckades
|
|
23
23
|
finished_at: Avslutad den
|
|
24
24
|
not_found: Grupp hittades inte
|
|
25
|
-
|
|
25
|
+
on_failure: Vid misslyckande
|
|
26
26
|
on_finish: Vid avslut
|
|
27
27
|
on_success: Vid framgång
|
|
28
28
|
progress: Framsteg
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
require "rails/generators/active_record"
|
|
5
|
+
require_relative "migration_path"
|
|
6
|
+
|
|
7
|
+
module Pgbus
|
|
8
|
+
module Generators
|
|
9
|
+
class AddBatchCallbackJobsGenerator < Rails::Generators::Base
|
|
10
|
+
include ActiveRecord::Generators::Migration
|
|
11
|
+
include MigrationPath
|
|
12
|
+
|
|
13
|
+
source_root File.expand_path("templates", __dir__)
|
|
14
|
+
|
|
15
|
+
desc "Add jsonb callback-job columns to pgbus_batches (issue #415)"
|
|
16
|
+
|
|
17
|
+
class_option :database,
|
|
18
|
+
type: :string,
|
|
19
|
+
default: nil,
|
|
20
|
+
desc: "Use a separate database for pgbus tables (e.g. --database=pgbus)"
|
|
21
|
+
|
|
22
|
+
def create_migration_file
|
|
23
|
+
migration_template "add_batch_callback_jobs.rb.erb",
|
|
24
|
+
File.join(pgbus_migrate_path, "add_pgbus_batch_callback_jobs.rb")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def display_post_install
|
|
28
|
+
say ""
|
|
29
|
+
say "Pgbus configured batch callbacks installed!", :green
|
|
30
|
+
say ""
|
|
31
|
+
say "Next steps:"
|
|
32
|
+
say " 1. Run: rails db:migrate#{migrate_command_suffix}"
|
|
33
|
+
say " 2. Restart pgbus: bin/pgbus start"
|
|
34
|
+
say ""
|
|
35
|
+
say "You can now pass a configured ActiveJob instance to a batch:"
|
|
36
|
+
say " Pgbus::Batch.new(on_finish: ReportJob.new.set(queue: :critical, wait: 5.minutes))"
|
|
37
|
+
say ""
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def migration_version
|
|
43
|
+
"[#{ActiveRecord::Migration.current_version}]"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
require "rails/generators/active_record"
|
|
5
|
+
require_relative "migration_path"
|
|
6
|
+
|
|
7
|
+
module Pgbus
|
|
8
|
+
module Generators
|
|
9
|
+
class AddBatchExecutionsGenerator < Rails::Generators::Base
|
|
10
|
+
include ActiveRecord::Generators::Migration
|
|
11
|
+
include MigrationPath
|
|
12
|
+
|
|
13
|
+
source_root File.expand_path("templates", __dir__)
|
|
14
|
+
|
|
15
|
+
desc "Add pgbus_batch_executions and rename batch failure columns (issue #414)"
|
|
16
|
+
|
|
17
|
+
class_option :database,
|
|
18
|
+
type: :string,
|
|
19
|
+
default: nil,
|
|
20
|
+
desc: "Use a separate database for pgbus tables (e.g. --database=pgbus)"
|
|
21
|
+
|
|
22
|
+
def create_migration_file
|
|
23
|
+
migration_template "add_batch_executions.rb.erb",
|
|
24
|
+
File.join(pgbus_migrate_path, "add_pgbus_batch_executions.rb")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def display_post_install
|
|
28
|
+
say ""
|
|
29
|
+
say "Pgbus batch execution-row tracking installed!", :green
|
|
30
|
+
say ""
|
|
31
|
+
say "Next steps:"
|
|
32
|
+
say " 1. Run: rails db:migrate#{migrate_command_suffix}"
|
|
33
|
+
say " 2. Restart pgbus: bin/pgbus start"
|
|
34
|
+
say ""
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def migration_version
|
|
40
|
+
"[#{ActiveRecord::Migration.current_version}]"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
class AddPgbusBatchCallbackJobs < ActiveRecord::Migration<%= migration_version %>
|
|
2
|
+
def up
|
|
3
|
+
add_column :pgbus_batches, :on_finish_job, :jsonb unless column_exists?(:pgbus_batches, :on_finish_job)
|
|
4
|
+
add_column :pgbus_batches, :on_success_job, :jsonb unless column_exists?(:pgbus_batches, :on_success_job)
|
|
5
|
+
add_column :pgbus_batches, :on_failure_job, :jsonb unless column_exists?(:pgbus_batches, :on_failure_job)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def down
|
|
9
|
+
remove_column :pgbus_batches, :on_finish_job if column_exists?(:pgbus_batches, :on_finish_job)
|
|
10
|
+
remove_column :pgbus_batches, :on_success_job if column_exists?(:pgbus_batches, :on_success_job)
|
|
11
|
+
remove_column :pgbus_batches, :on_failure_job if column_exists?(:pgbus_batches, :on_failure_job)
|
|
12
|
+
end
|
|
13
|
+
end
|