postburner 1.0.0.rc.4 → 1.0.0.rc.6
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 +81 -0
- data/README.md +78 -11
- data/app/models/postburner/job.rb +15 -0
- data/app/models/postburner/orphaned_job.rb +26 -1
- data/app/models/postburner/schedule.rb +260 -17
- data/app/models/postburner/schedule_execution.rb +201 -53
- data/lib/generators/postburner/install/templates/migrations/create_postburner_schedules.rb.erb +10 -3
- data/lib/postburner/active_job/adapter.rb +16 -2
- data/lib/postburner/instrumentation.rb +3 -1
- data/lib/postburner/scheduler.rb +68 -69
- data/lib/postburner/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b48c13a76dd9b1b6e54f825bbc50d95574c0decd4a57b8b6ba3d3fc8c5ea369c
|
|
4
|
+
data.tar.gz: 5a867eef5d36cce234b2874b5c18ff95d0ddb648fd1576e71c0a8c5946ddc12c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 82e60e8af9d983550d7a07dac46b1c5611eb11245944e2ddac5a7411cf2d0b63a59697cab1a83965379bb92c274c1abd33a1a475498f981cbcb8589d520ac61c
|
|
7
|
+
data.tar.gz: 430829a786da22122f8ce4a47e8e966feb524877539f7a0e4086db912731235064c6888d0662d67c438e0c2e2d590a2a4496e33a56658b3022f70fbbb58ca050
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,86 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v1.0.0.rc.6 - 2026-06-27
|
|
4
|
+
|
|
5
|
+
### Highlights
|
|
6
|
+
|
|
7
|
+
- `Postburner::Schedule#reconcile!` — guarantees exactly one live future execution per enabled schedule and zero future executions for a disabled schedule. The watchdog delegates to it.
|
|
8
|
+
- `Postburner::Schedule#enable!` / `#disable!` — convenience wrappers that drive reconciliation.
|
|
9
|
+
- Auto-reconcile on schedule edits — an `after_update_commit` hook reconciles whenever a scheduling/snapshot attribute changes.
|
|
10
|
+
- `Postburner::ScheduleExecution#supersede!` — tears down an execution (Beanstalkd job + `Postburner::Job` AR row) and marks it `superseded`.
|
|
11
|
+
- `superseded` status (enum value `201`) on `Postburner::ScheduleExecution`, distinct from `skipped`. Neither counts as a live future execution.
|
|
12
|
+
- `live` and `future_live` scopes on `Postburner::ScheduleExecution` (`pending`/`scheduled` only; `future_live` adds `run_at > now`).
|
|
13
|
+
- Instrumentation events `supersede.schedule_execution.postburner` and `reconcile.schedule.postburner`.
|
|
14
|
+
- **`ScheduleExecution#skip!` now skips one occurrence, then resumes -- tearing down down the associated `Postburner::Job` **and** the Beanstalkd job. The skipped row is retained as history.
|
|
15
|
+
- **`schedule.destroy` now tears down each execution's Beanstalkd job and `Postburner::Job` (no orphans)
|
|
16
|
+
- The watchdog sweeps **disabled** schedules with a lingering live future execution and supersedes them.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Scheduled **tracked ActiveJob** executions had a `nil` `bkid`: the adapter stored the entire Beanstalkd put response hash (`{status:, id:}`) instead of the id, which cast to `nil` in the `bigint` column. Scheduled **non-tracked ActiveJob** executions discarded the Beanstalkd id entirely (`beanstalk_job_id` was always `nil`), so they could not be cancelled. Both now capture the real id via the adapter's `provider_job_id`.
|
|
21
|
+
- Scheduled **`Postburner::Job`** executions stored the wrong value in `ScheduleExecution#beanstalk_job_id` (the return value of `queue!`, not the Beanstalkd id). It now holds the real `job.bkid`.
|
|
22
|
+
- `Postburner::OrphanedJob#destroy` can be soft-removed (`remove!`) and destroyed (`destroy`), with Beanstalkd job, while `readonly?` still blocks changes.
|
|
23
|
+
|
|
24
|
+
### Upgrade notes
|
|
25
|
+
|
|
26
|
+
- This is a **template mutation** (like rc.4), not an additive migration. Existing installs must add a migration that:
|
|
27
|
+
1. Replaces the total unique index on `(schedule_id, run_at)` with a **live-only partial** unique index (`WHERE status IN (0, 11)`), so a superseded/skipped row can coexist with a freshly recreated live execution at the same `run_at`.
|
|
28
|
+
2. Drops `ON DELETE CASCADE` on the `schedule_id` foreign key (teardown is now owned by `dependent: :destroy` + `before_destroy`).
|
|
29
|
+
- **Behavior change — deleting a schedule.** With `ON DELETE CASCADE` removed, deleting a schedule by any path other than ActiveRecord `destroy` — raw SQL, `Schedule.delete`, `where(...).delete_all` — now raises a foreign-key violation when the schedule has executions, instead of silently cascading. Always use `schedule.destroy` (which runs `dependent: :destroy` + the `before_destroy` teardown). This is intentional: it prevents orphaned Beanstalkd jobs and `Postburner::Job` rows.
|
|
30
|
+
|
|
31
|
+
Standalone upgrade migration (written for zero-downtime on large `postburner_schedule_executions` tables — builds the index `CONCURRENTLY` and adds the FK unvalidated then validates it, neither of which blocks writes; requires `disable_ddl_transaction!`):
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
class UpgradePostburnerSchedulesForReconcile < ActiveRecord::Migration[7.2]
|
|
35
|
+
disable_ddl_transaction!
|
|
36
|
+
|
|
37
|
+
# Existing installs created the unique index via the generator with Rails'
|
|
38
|
+
# default name. Adjust if your install named it differently.
|
|
39
|
+
OLD_INDEX = 'index_postburner_schedule_executions_on_schedule_id_and_run_at'
|
|
40
|
+
NEW_INDEX = 'index_pb_sched_exec_live_schedule_run_at'
|
|
41
|
+
|
|
42
|
+
def up
|
|
43
|
+
# 1. Add the live-only partial unique index CONCURRENTLY (no write lock) so a
|
|
44
|
+
# superseded/skipped row can coexist with a live recreate at the same run_at.
|
|
45
|
+
# Narrowing uniqueness can't conflict with existing data (the old total
|
|
46
|
+
# index was stricter), so this build cannot fail on valid rows.
|
|
47
|
+
add_index :postburner_schedule_executions, [:schedule_id, :run_at],
|
|
48
|
+
unique: true, where: 'status IN (0, 11)',
|
|
49
|
+
name: NEW_INDEX, algorithm: :concurrently, if_not_exists: true
|
|
50
|
+
|
|
51
|
+
# Drop the old total unique index. Target by NAME: with the new index added,
|
|
52
|
+
# two indexes now match the (schedule_id, run_at) columns.
|
|
53
|
+
remove_index :postburner_schedule_executions,
|
|
54
|
+
name: OLD_INDEX, algorithm: :concurrently, if_exists: true
|
|
55
|
+
|
|
56
|
+
# 2. Replace the cascading FK with a plain one. Add unvalidated first (brief
|
|
57
|
+
# metadata-only lock), then validate separately (does not block writes).
|
|
58
|
+
# dependent: :destroy + before_destroy now own teardown.
|
|
59
|
+
remove_foreign_key :postburner_schedule_executions, column: :schedule_id
|
|
60
|
+
add_foreign_key :postburner_schedule_executions, :postburner_schedules,
|
|
61
|
+
column: :schedule_id, validate: false
|
|
62
|
+
validate_foreign_key :postburner_schedule_executions, column: :schedule_id
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def down
|
|
66
|
+
remove_foreign_key :postburner_schedule_executions, column: :schedule_id
|
|
67
|
+
add_foreign_key :postburner_schedule_executions, :postburner_schedules,
|
|
68
|
+
column: :schedule_id, on_delete: :cascade, validate: false
|
|
69
|
+
validate_foreign_key :postburner_schedule_executions, column: :schedule_id
|
|
70
|
+
|
|
71
|
+
add_index :postburner_schedule_executions, [:schedule_id, :run_at],
|
|
72
|
+
unique: true, name: OLD_INDEX, algorithm: :concurrently, if_not_exists: true
|
|
73
|
+
remove_index :postburner_schedule_executions,
|
|
74
|
+
name: NEW_INDEX, algorithm: :concurrently, if_exists: true
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## v1.0.0.rc.5 - 2026-06-26
|
|
80
|
+
|
|
81
|
+
### Fixed
|
|
82
|
+
- `Postburner::Scheduler#ensure_future_execution!` no longer accumulates duplicate future `Postburner::ScheduleExecution` rows. The watchdog (every `scheduler_interval`, default 300s) created and enqueued one extra future execution on **every** cycle, building up dozens or hundreds of Beanstalkd delayed jobs that all eventually fire.
|
|
83
|
+
|
|
3
84
|
## v1.0.0.rc.4 - 2026-06-10
|
|
4
85
|
|
|
5
86
|
### Added
|
data/README.md
CHANGED
|
@@ -126,7 +126,7 @@ Postburner [beanstalkd](https://beanstalkd.github.io/) is used with PostgreSQL t
|
|
|
126
126
|
|
|
127
127
|
```ruby
|
|
128
128
|
# Gemfile
|
|
129
|
-
gem 'postburner', '~> 1.0.0.
|
|
129
|
+
gem 'postburner', '~> 1.0.0.rc.6'
|
|
130
130
|
|
|
131
131
|
# config/application.rb
|
|
132
132
|
config.active_job.queue_adapter = :postburner
|
|
@@ -499,8 +499,7 @@ The scheduler uses **immediate enqueue** combined with a **watchdog safety net**
|
|
|
499
499
|
```
|
|
500
500
|
4. When a worker reserves the watchdog, it instantiates `Postburner::Scheduler` which:
|
|
501
501
|
- Acquires a PostgreSQL advisory lock for coordination
|
|
502
|
-
-
|
|
503
|
-
- Ensures each schedule has a future execution queued
|
|
502
|
+
- Reconciles every schedule (see [Editing & Pausing Schedules](#editing--pausing-schedules)) — auto-bootstrapping unstarted schedules, ensuring each enabled schedule has exactly one future execution queued, and tearing down lingering futures on disabled schedules
|
|
504
503
|
- Re-queues a new watchdog with delay for the next interval
|
|
505
504
|
|
|
506
505
|
NOTE: The watchdog is ephemeral data in Beanstalkd, not a database record. `Postburner::Scheduler` is the handler class that does the work. This design requires no dedicated scheduler process - existing workers handle everything.
|
|
@@ -687,8 +686,9 @@ Postburner::Schedule.create!(
|
|
|
687
686
|
schedule = Postburner::Schedule.find_by(name: 'daily_cleanup')
|
|
688
687
|
Postburner::Schedule.enabled # All enabled schedules
|
|
689
688
|
|
|
690
|
-
#
|
|
691
|
-
schedule.
|
|
689
|
+
# Pause / resume (preferred — see "Editing & Pausing Schedules" below)
|
|
690
|
+
schedule.disable! # Tears down the queued future execution
|
|
691
|
+
schedule.enable! # Resumes from the next grid point after now
|
|
692
692
|
|
|
693
693
|
# Change catch-up policy
|
|
694
694
|
schedule.update!(catch_up: true)
|
|
@@ -700,11 +700,68 @@ schedule.next_run_at # => 2025-01-02 09:00:00 -0500
|
|
|
700
700
|
schedule.next_run_at_times(5) # Next 5 run times
|
|
701
701
|
|
|
702
702
|
# View executions
|
|
703
|
+
schedule.executions.live # pending + scheduled (real queued work)
|
|
704
|
+
schedule.executions.future_live # the live future execution(s)
|
|
703
705
|
schedule.executions.pending
|
|
704
706
|
schedule.executions.scheduled
|
|
705
|
-
schedule.executions.skipped
|
|
707
|
+
schedule.executions.skipped # cancelled occurrences (history)
|
|
708
|
+
schedule.executions.superseded # replaced by a recreate (history)
|
|
709
|
+
|
|
710
|
+
# Delete a schedule (ALWAYS use destroy — see note below)
|
|
711
|
+
schedule.destroy # Tears down every execution's Beanstalkd job + Postburner::Job row
|
|
706
712
|
```
|
|
707
713
|
|
|
714
|
+
**Deleting schedules — always use `destroy`.** `schedule.destroy` runs `dependent: :destroy` plus a `before_destroy` teardown that removes each execution's Beanstalkd job and any `Postburner::Job` row, so nothing is orphaned. Deleting a schedule by any other path — raw SQL, `Schedule.delete`, `where(...).delete_all` — raises a foreign-key violation when the schedule has executions, by design. Reach for `schedule.destroy` (or `disable!` to keep the record and history).
|
|
715
|
+
|
|
716
|
+
#### Editing & Pausing Schedules
|
|
717
|
+
|
|
718
|
+
Postburner keeps a schedule's queued work in sync with its configuration through a single, idempotent convergence path: **reconciliation**. The guarantee is simple:
|
|
719
|
+
|
|
720
|
+
- An **enabled** schedule always has **exactly one** live future execution, sitting on the current grid with a snapshot matching the live config.
|
|
721
|
+
- A **disabled** schedule has **zero** future executions.
|
|
722
|
+
|
|
723
|
+
You rarely call reconciliation directly (`schedule.reconcile!` exists if you need it). Instead it runs for you in three places: the watchdog reconciles every schedule on each pass, schedule edits trigger it automatically, and `enable!` / `disable!` drive it.
|
|
724
|
+
|
|
725
|
+
##### Pausing & resuming
|
|
726
|
+
|
|
727
|
+
Use `disable!` / `enable!` rather than `update!(enabled: ...)` (both work, but these read better and make intent obvious):
|
|
728
|
+
|
|
729
|
+
```ruby
|
|
730
|
+
schedule.disable! # Supersedes the queued future execution → zero future executions
|
|
731
|
+
schedule.enable! # Recreates a future execution at the next grid point after now
|
|
732
|
+
```
|
|
733
|
+
|
|
734
|
+
Disabling now actually tears down the queued future execution in Beanstalkd — it doesn't just stop creating new ones. Enabling resumes from the **next grid point after now**; it does **not** backfill the gap while the schedule was paused (`catch_up` only governs the running-job chaining path, not resume).
|
|
735
|
+
|
|
736
|
+
##### Editing a schedule
|
|
737
|
+
|
|
738
|
+
Editing a scheduling/snapshot attribute — `args`, `queue`, `priority`, the grid (`anchor`, `interval`, `interval_unit`, `cron`), `timezone`, `enabled`, or `name` — automatically supersedes the stale queued execution and recreates it from the current config (via an `after_update_commit` hook):
|
|
739
|
+
|
|
740
|
+
```ruby
|
|
741
|
+
# Payload-only edit: recreates the future execution at the SAME run_at
|
|
742
|
+
schedule.update!(args: { report_type: 'weekly' })
|
|
743
|
+
|
|
744
|
+
# Grid edit: recreates the future execution at the NEW grid point
|
|
745
|
+
schedule.update!(interval: 2, interval_unit: 'hours')
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
> **Best-effort, not synchronous.** This inline reconcile is non-blocking: it *tries* the global scheduler advisory lock without waiting, so it never holds up a web request. If the watchdog happens to hold the lock mid-sweep, the inline reconcile is skipped and the watchdog converges on its next pass. For UI-managed schedules, **don't assume the next run is updated immediately** — read it back after the watchdog interval (`scheduler_interval`) rather than right after the save.
|
|
749
|
+
|
|
750
|
+
##### Skipping a single occurrence
|
|
751
|
+
|
|
752
|
+
`skip!` cancels **one** upcoming occurrence and then resumes at the next grid point after the skipped slot. It tears down both the Beanstalkd job and the associated `Postburner::Job` row, marks the execution `skipped` (kept as history), and triggers a best-effort reconcile. The skipped occurrence does not run and is not recreated:
|
|
753
|
+
|
|
754
|
+
```ruby
|
|
755
|
+
execution = schedule.executions.future_live.first
|
|
756
|
+
execution.skip! # This one won't run; the schedule continues at the next grid point
|
|
757
|
+
```
|
|
758
|
+
|
|
759
|
+
To pause every upcoming run instead of just one, use `disable!`.
|
|
760
|
+
|
|
761
|
+
##### Superseding (internal)
|
|
762
|
+
|
|
763
|
+
`ScheduleExecution#supersede!` performs the same teardown as `skip!` but records a `superseded` status, meaning the execution was replaced by a recreate (config/grid drift resolved by reconciliation) rather than cancelled by an operator. Reconciliation uses it internally; you'll mostly encounter `superseded` rows when reading execution history. Like skipped rows, superseded rows are excluded from `live` / `future_live`.
|
|
764
|
+
|
|
708
765
|
#### Starting Schedules
|
|
709
766
|
|
|
710
767
|
When you create a schedule, it won't run until the first execution is created. You have two options:
|
|
@@ -748,19 +805,27 @@ Each scheduled run creates an execution record for tracking:
|
|
|
748
805
|
```ruby
|
|
749
806
|
execution = Postburner::ScheduleExecution.find(123)
|
|
750
807
|
|
|
751
|
-
execution.status # pending, scheduled, skipped
|
|
808
|
+
execution.status # pending, scheduled, skipped, superseded
|
|
752
809
|
execution.run_at # Scheduled time
|
|
753
810
|
execution.enqueued_at # When job was queued
|
|
754
811
|
execution.beanstalk_job_id # Beanstalkd job ID
|
|
755
812
|
execution.job_id # Postburner::Job ID (if using Postburner::Job)
|
|
756
813
|
```
|
|
757
814
|
|
|
815
|
+
**Status values:**
|
|
816
|
+
- `pending` — created, not yet enqueued to Beanstalkd
|
|
817
|
+
- `scheduled` — enqueued to Beanstalkd, waiting for `run_at`
|
|
818
|
+
- `skipped` — one occurrence cancelled by an operator (`skip!`); retained as history
|
|
819
|
+
- `superseded` — replaced by a recreate during reconciliation (`supersede!`); retained as history
|
|
820
|
+
|
|
821
|
+
Only `pending` and `scheduled` count as a live future execution (the `live` / `future_live` scopes). `skipped` and `superseded` are inert history.
|
|
822
|
+
|
|
758
823
|
**Execution lifecycle:**
|
|
759
824
|
1. Execution created with `pending` status and immediately enqueued to Beanstalkd
|
|
760
825
|
2. Status changes to `scheduled` once enqueued
|
|
761
826
|
3. At `run_at` time, Beanstalkd releases job to worker
|
|
762
827
|
4. For `Postburner::Job` (and `ActiveJob` with `Postburner::Tracked`) schedules: `before_attempt` callback creates next execution
|
|
763
|
-
5. Watchdog periodically
|
|
828
|
+
5. Watchdog periodically reconciles each schedule, guaranteeing future executions exist (safety net)
|
|
764
829
|
|
|
765
830
|
#### Timezone Handling
|
|
766
831
|
|
|
@@ -1572,7 +1637,8 @@ Postburner emits ActiveSupport::Notifications events following Rails conventions
|
|
|
1572
1637
|
|-------|------|--------------|
|
|
1573
1638
|
| `create.schedule.postburner` | When schedule is created | `:schedule` |
|
|
1574
1639
|
| `update.schedule.postburner` | When schedule is updated | `:schedule`, `:changes` |
|
|
1575
|
-
| `audit.schedule.postburner` | When scheduler audits a schedule | `:schedule` |
|
|
1640
|
+
| `audit.schedule.postburner` | When scheduler audits a schedule | `:schedule`, `:execution_created`, `:orphans_enqueued` |
|
|
1641
|
+
| `reconcile.schedule.postburner` | When a schedule is reconciled (executions converged to config) | `:schedule`, `:superseded`, `:created` |
|
|
1576
1642
|
|
|
1577
1643
|
**Schedule Payload Structure:**
|
|
1578
1644
|
|
|
@@ -1595,7 +1661,8 @@ Postburner emits ActiveSupport::Notifications events following Rails conventions
|
|
|
1595
1661
|
|-------|------|--------------|
|
|
1596
1662
|
| `create.schedule_execution.postburner` | When execution is created | `:schedule`, `:execution` |
|
|
1597
1663
|
| `enqueue.schedule_execution.postburner` | When execution is enqueued to Beanstalkd | `:schedule`, `:execution`, `:beanstalk_job_id` |
|
|
1598
|
-
| `skip.schedule_execution.postburner` | When
|
|
1664
|
+
| `skip.schedule_execution.postburner` | When an operator skips one occurrence | `:schedule`, `:execution` |
|
|
1665
|
+
| `supersede.schedule_execution.postburner` | When an execution is superseded (replaced by a recreate during reconciliation) | `:schedule`, `:execution` |
|
|
1599
1666
|
|
|
1600
1667
|
**Execution Payload Structure:**
|
|
1601
1668
|
|
|
@@ -2368,7 +2435,7 @@ Key changes in v1.0:
|
|
|
2368
2435
|
|
|
2369
2436
|
1. **Update Gemfile:**
|
|
2370
2437
|
```ruby
|
|
2371
|
-
gem 'postburner', '~> 1.0.0.
|
|
2438
|
+
gem 'postburner', '~> 1.0.0.rc.6'
|
|
2372
2439
|
```
|
|
2373
2440
|
|
|
2374
2441
|
2. **Remove Backburner config:**
|
|
@@ -88,6 +88,21 @@ module Postburner
|
|
|
88
88
|
|
|
89
89
|
validates :sid, presence: {strict: true}
|
|
90
90
|
|
|
91
|
+
# Jobs that have been picked up by a worker but have not yet completed or
|
|
92
|
+
# been removed, i.e. currently in-flight.
|
|
93
|
+
#
|
|
94
|
+
# @note A job whose worker crashed mid-run can remain in this scope even
|
|
95
|
+
# though nothing is executing. Beanstalkd's reserved count
|
|
96
|
+
# ({Postburner.stats}) is the ground truth for what is actually running.
|
|
97
|
+
#
|
|
98
|
+
# @example
|
|
99
|
+
# Postburner::Job.processing # all in-flight tracked jobs
|
|
100
|
+
# Postburner::TrackedJob.processing # in-flight tracked ActiveJobs
|
|
101
|
+
#
|
|
102
|
+
scope :processing, -> {
|
|
103
|
+
where.not(processing_at: nil).where(processed_at: nil, removed_at: nil)
|
|
104
|
+
}
|
|
105
|
+
|
|
91
106
|
# Resolves an STI type name to a class, falling back to OrphanedJob when the
|
|
92
107
|
# original class no longer exists. This tolerates rows whose `type` column
|
|
93
108
|
# references a deleted or renamed job class, preventing
|
|
@@ -84,6 +84,10 @@ module Postburner
|
|
|
84
84
|
# through instance save/update and therefore does not trigger
|
|
85
85
|
# `ensure_proper_type` either).
|
|
86
86
|
#
|
|
87
|
+
# Uses the STI base class for the relation so the row's missing `type` value
|
|
88
|
+
# (which differs from OrphanedJob's sti_name) does not scope it out — a
|
|
89
|
+
# `self.class.where` would match zero rows and silently fail to persist.
|
|
90
|
+
#
|
|
87
91
|
# Idempotent: does nothing if already removed.
|
|
88
92
|
#
|
|
89
93
|
# @return [void]
|
|
@@ -93,10 +97,31 @@ module Postburner
|
|
|
93
97
|
|
|
94
98
|
self.delete!
|
|
95
99
|
now = Time.current
|
|
96
|
-
self.class.where(id: self.id).update_all(removed_at: now)
|
|
100
|
+
self.class.base_class.where(id: self.id).update_all(removed_at: now)
|
|
97
101
|
self.removed_at = now
|
|
98
102
|
end
|
|
99
103
|
|
|
104
|
+
# Hard-deletes this orphaned row, removing BOTH its Beanstalkd job and its AR
|
|
105
|
+
# row, while keeping `readonly?` true for saves/updates.
|
|
106
|
+
#
|
|
107
|
+
# The base ActiveRecord#destroy raises ReadOnlyRecord on a readonly record,
|
|
108
|
+
# which would make ScheduleExecution#teardown_job! (which calls `job.destroy`
|
|
109
|
+
# uniformly across job shapes) fail for an execution whose job class was
|
|
110
|
+
# deleted/renamed. This override mirrors {#remove!}: it removes the Beanstalkd
|
|
111
|
+
# job via {#delete!} and deletes the row at the relation level — using the STI
|
|
112
|
+
# base class so the missing `type` value doesn't scope the row out — bypassing
|
|
113
|
+
# both `readonly?` and instance callbacks. The row is genuinely removed (not a
|
|
114
|
+
# soft-delete), matching destroy semantics.
|
|
115
|
+
#
|
|
116
|
+
# @return [self] frozen, with destroyed? == true
|
|
117
|
+
#
|
|
118
|
+
def destroy
|
|
119
|
+
self.delete!
|
|
120
|
+
self.class.base_class.where(id: self.id).delete_all
|
|
121
|
+
@destroyed = true
|
|
122
|
+
freeze
|
|
123
|
+
end
|
|
124
|
+
|
|
100
125
|
# Prevents saves that would allow Rails' ensure_proper_type to overwrite the
|
|
101
126
|
# original `type` value with 'Postburner::OrphanedJob'.
|
|
102
127
|
#
|